Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Jetpack Compose A to Z (full)

Ji Sungbin
February 23, 2023

Jetpack Compose A to Z (full)

Jetpack Compose A to Z 리허설 전 full 자료 (70분)

Ji Sungbin

February 23, 2023
Tweet

More Decks by Ji Sungbin

Other Decks in Programming

Transcript

  1. Jetpack Compose A to Z
    ✨૑ࢿ࠼

    View Slide

  2. 👋૑ࢿ࠼
    🏠TVOHCJO
    🏰TVOHCJOMBOE
    🐙HJUIVCDPNKJTVOHCJO

    View Slide

  3. -PPLBIFBE-BZPVU
    "OJNBUJPO
    *OUFSOBM4ZTUFN
    $PNQJMFS%FCVHHJOH
    #FTU1SBDUJDF
    8SBQVQ
    📖ݾର

    View Slide

  4. ↟ BMQIBӝળ
    ↟ ௏٘ࣁࠗࢎ೦ࢤۚ
    📝द੘ೞӝ੹ী

    View Slide

  5. 1. LookaheadLayout

    View Slide

  6. View Slide

  7. View Slide

  8. start
    fi
    nish
    animation frame

    View Slide

  9. $PMVNO3PX🙅
    #PY#PY8JUI$POTUSBJOUT🙅
    -B[Z-JTU🙅
    $POTUSBJOU-BZPVU🙅
    -BZPVU🙅
    -PPLBIFBE-BZPVU🥳
    BOJNBUJPOGSBNFਸ୶੸ೞӝਤೠۨ੉ইਓ

    View Slide

  10. start
    fi
    nish
    1st intermediate layout
    2nd intermediate layout
    3rd intermediate layout
    Lookahead


    1. Modi
    fi
    er.onPlaced


    2. Modi
    fi
    er.intermediateLayout

    View Slide

  11. /**


    * ۨ੉ইਓਸ Ѿ੿ೞӝ ਤ೧ measure ܳ ޷ܻ ೞҊ ୶റ placement ױ҅ܳ प೯ೞח ۨ੉ইਓੑפ׮. => measure ܳ ޷ܻ ೞח ױ҅: lookahead ױ҅


    * lookahead ױ҅о ՘աݶ ୶റ [LookaheadLayoutScope.intermediateLayout] ਸ ా೧


    * lookahead Ѿҗܳ ӝ߈ਵ۽ ۨ੉ইਓ੄ measure ߂ placement ܳ ઑ੿ೡ ࣻ ੓ח ژ ׮ܲ measure ߂ placement ױ҅о द੘ؾפ׮. => intermediate layout


    *


    * ੉ܳ ੉ਊೞৈ ۨ੉ইਓ੉ ޷ܻ measure ػ intermediate layout ਸ ೱ೧ ௼ӝ৬ ਤ஖ܳ ੼ର੸ਵ۽ ߸҃ೡ ࣻ ੓णפ׮.


    */


    @ExperimentalComposeUiApi


    @UiComposable


    @Composable


    fun LookaheadLayout(


    content: @Composable @UiComposable LookaheadLayoutScope.() -> Unit,


    modifier: Modifier = Modifier,


    measurePolicy: MeasurePolicy


    )

    View Slide

  12. /**


    * [LookaheadLayout] ੄ ݽٚ(૒੽ ߂ р੽) ೞਤ ۨ੉ইਓী ؀ೠ receiver ߧਤܳ ઁҕ೤פ׮.


    * lookahead ױ҅ীࢲ ҅࢑ػ ۨ੉ইਓ੄ measurement ߂ placement ח


    * [LookaheadLayoutScope] ীࢲ пп [Modifier.intermediateLayout] ߂ [Modifier.onPlaced] ܳ ా೧ observe ೡ ࣻ ੓णפ׮.


    */


    @ExperimentalComposeUiApi


    interface LookaheadLayoutScope {


    fun Modifier.onPlaced(


    onPlaced: (


    lookaheadScopeCoordinates: LookaheadLayoutCoordinates,


    layoutCoordinates: LookaheadLayoutCoordinates


    ) -> Unit


    ): Modifier


    fun Modifier.intermediateLayout(


    measure: MeasureScope.(


    measurable: Measurable,


    constraints: Constraints,


    lookaheadSize: IntSize


    ) -> MeasureResult


    ): Modifier


    }

    View Slide

  13. @ExperimentalComposeUiApi


    interface LookaheadLayoutScope {


    /**


    * intermediate layout ੉ ߓ஖ؼ ਤ஖о ҅࢑ػ റ ഐ୹ؾפ׮.


    *


    * [LookaheadLayoutCoordinates] о ઱য૑ݶ ҅࢑ػ intermediate layout ੄ য়೐ࣇҗ അ੤ ߓ஖ظ ੓ח ஹನ੷࠶੄ য়೐ࣇਸ


    * [LookaheadLayoutCoordinates.localLookaheadPositionOf] ৬ [LookaheadLayoutCoordinates.localPositionOf] ܳ ࢎਊೞৈ ঳ਸ ࣻ ੓णפ׮.


    * ੉ܳ ా೧ ҅࢑ػ intermediate layout ੄ য়೐ࣇਸ ӝ߈ਵ۽ ஹನ੷࠶੄ ߓ஖ܳ ઑ੿ೡ ࣻ ੓णפ׮.


    *


    * [onPlaced ۈ׮ ੋ੗]


    *


    * @param lookaheadScopeCoordinates [LookaheadLayout] ੉ ࢎਊೞח [LookaheadLayoutCoordinates]


    * @param layoutCoordinates ੉ modifier ੄ ஹನ੷࠶੉ ࢎਊೞח [LookaheadLayoutCoordinates]


    */


    fun Modifier.onPlaced(


    onPlaced: (


    lookaheadScopeCoordinates: LookaheadLayoutCoordinates,


    layoutCoordinates: LookaheadLayoutCoordinates


    ) -> Unit


    ): Modifier


    fun Modifier.intermediateLayout(


    measure: MeasureScope.(


    measurable: Measurable,


    constraints: Constraints,


    lookaheadSize: IntSize






    View Slide

  14. start
    fi
    nish
    Lookahead
    1st intermediate layout
    2nd intermediate layout
    will be 3rd intermediate layout
    OEJOUFSNFEJBUFMBZPVUীࢲप೯غח࢚ട
    JOUFSNFEJBUFMBZPVU੉ߓ஖ؼਤ஖ܳ҅࢑ೣ
    ੉റ೧׼ਤ஖ܳModifier.onPlaced۽ࠁն

    View Slide

  15. @ExperimentalComposeUiApi


    interface LookaheadLayoutScope {


    /**


    * intermediate layout ੉ ߓ஖ؼ ਤ஖о ҅࢑ػ റ ഐ୹ؾפ׮.


    *


    * [LookaheadLayoutCoordinates] о ઱য૑ݶ ҅࢑ػ intermediate layout ੄ য়೐ࣇҗ അ੤ ߓ஖ظ ੓ח ஹನ੷࠶੄ য়೐ࣇਸ


    * [LookaheadLayoutCoordinates.localLookaheadPositionOf] ৬ [LookaheadLayoutCoordinates.localPositionOf] ܳ ࢎਊೞৈ ঳ਸ ࣻ ੓णפ׮.


    * ੉ܳ ా೧ ҅࢑ػ intermediate layout ੄ য়೐ࣇਸ ӝ߈ਵ۽ ஹನ੷࠶੄ ߓ஖ܳ ઑ੿ೡ ࣻ ੓णפ׮.


    *


    * [onPlaced ۈ׮ ੋ੗]


    *


    * @param lookaheadScopeCoordinates [LookaheadLayout] ੉ ࢎਊೞח [LookaheadLayoutCoordinates]


    * @param layoutCoordinates ੉ modifier ੄ ஹನ੷࠶੉ ࢎਊೞח [LookaheadLayoutCoordinates]


    */


    fun Modifier.onPlaced(


    onPlaced: (


    lookaheadScopeCoordinates: LookaheadLayoutCoordinates,


    layoutCoordinates: LookaheadLayoutCoordinates


    ) -> Unit


    ): Modifier


    fun Modifier.intermediateLayout(


    measure: MeasureScope.(


    measurable: Measurable,


    constraints: Constraints,


    lookaheadSize: IntSize






    View Slide

  16. /**


    * ۨ੉ইਓী ؀೧ measure ػ bounds ੄ ഓ؊ੑפ׮.


    */


    @JvmDefaultWithCompatibility


    interface LayoutCoordinates {


    // … ࢤۚ


    /**


    * [sourceCoordinates] ҕр੄ [relativeToSource] ܳ ۽ஸ coordinate ۽ ߸ജ೤פ׮.


    * [sourceCoordinates] ח زੌೠ ۨ੉ইਓ ҅கী ࣘೞח ݽٚ [LayoutCoordinates] ੌ ࣻ ੓णפ׮.


    *


    * @param sourceCoordinates ߸ജೡ [Offset] ੉ ੓ח [LayoutCoordinates]


    * @param relativeToSource ߸ഝೡ [Offset]


    *


    * @return ۽ஸ coordinate ۽ ߸ജػ [Offset]


    */


    fun localPositionOf(sourceCoordinates: LayoutCoordinates, relativeToSource: Offset): Offset


    }

    View Slide

  17. /**


    * lookhead ױ҅ ૓೯ ੹җ റ੄ ۨ੉ইਓ ݽف੄ [LayoutCoordinates] ܳ ࠁਬ೤פ׮.


    */


    @ExperimentalComposeUiApi


    sealed interface LookaheadLayoutCoordinates : LayoutCoordinates {


    /**


    * [sourceCoordinates] ҕр੄ [relativeToSource] ܳ ۽ஸ coordinate ۽ ߸ജ೤פ׮.


    * [sourceCoordinates] ח زੌೠ ۨ੉ইਓ ҅கী ࣘೞח ݽٚ [LookaheadLayoutCoordinates] ੌ ࣻ ੓णפ׮.


    *


    * [localPositionOf] ৬ ׳ܻ [localLookaheadPositionOf] ח coordinate ҅࢑ਸ ਤ೧ lookahead ਤ஖ܳ ࢎਊ೤פ׮.


    *


    * @param sourceCoordinates ߸ജೡ [Offset] ੉ ੓ח [LookaheadLayoutCoordinates]


    * @param relativeToSource ߸ഝೡ [Offset]


    *


    * @return ۽ஸ coordinate ۽ ߸ജػ [Offset]


    */


    fun localLookaheadPositionOf(


    sourceCoordinates: LookaheadLayoutCoordinates,


    relativeToSource: Offset = Offset.Zero


    ): Offset


    }

    View Slide



  18. // interface LookaheadLayoutScope


    /**


    * lookahead ױ҅ীࢲ ҅࢑ػ ੿ࠁܳ ӝ߈ਵ۽ intermediate layout ܳ ߓ஖೤פ׮.


    * intermediate layout ੄ ௼ӝо ઁҕغח ۈ׮ੋ [measure] ੋ੗ܳ ా೧ intermediate layout ܳ morph ೡ ࣻ ੓णפ׮.


    *


    * morph: അ੤ ݽনਸ ׮ܲ ݽনਵ۽ ߄Բח Ѫ


    *


    * [measure ۈ׮ ੋ੗]


    *


    * @param measurable intermediate layout ੄ measurable


    * @param constraints intermediate layout ੄ constraints


    * @param lookaheadSize intermediate layout ੄ ௼ӝ


    */


    fun Modifier.intermediateLayout(


    measure: MeasureScope.(


    measurable: Measurable,


    constraints: Constraints,


    lookaheadSize: IntSize


    ) -> MeasureResult


    ): Modifier


    }

    View Slide

  19. start
    fi
    nish
    Lookahead
    1st intermediate layout
    2nd intermediate layout
    will be 3rd intermediate layout
    OEJOUFSNFEJBUFMBZPVUীࢲप೯غח࢚ട
    Modifier.intermediateLayoutਵ۽ߓ஖ೡҔ


    LookaheadLayoutCoordinates.localLookaheadPositionOf੄য়೐ࣇ
    LookaheadLayoutCoordinates.localPositionOf੄য়೐ࣇ

    View Slide

  20. start
    fi
    nish
    Lookahead
    ௼ӝয়೐ࣇ

    View Slide

  21. ௼ӝઑ੿.PEJGJFSJNUFSNFEJBUF-BZPVU
    /**


    * lookahead ױ҅ীࢲ ҅࢑ػ ੿ࠁܳ ӝ߈ਵ۽ intermediate layout ܳ ߓ஖೤פ׮.


    * intermediate layout ੄ ௼ӝо ઁҕغח ۈ׮ੋ [measure] ੋ੗ܳ ా೧ intermediate layout ܳ morph ೡ ࣻ ੓णפ׮.


    */


    fun Modifier.intermediateLayout(


    measure: MeasureScope.(


    measurable: Measurable,


    constraints: Constraints,


    lookaheadSize: IntSize


    ) -> MeasureResult


    ): Modifier

    View Slide

  22. য়೐ࣇઑ੿.PEJGJFSPO1MBDFE
    /**


    * [LookaheadLayoutCoordinates] о ઱য૑ݶ ҅࢑ػ intermediate layout ੄ য়೐ࣇҗ അ੤ ߓ஖ظ ੓ח ஹನ੷࠶੄ য়೐ࣇਸ


    * [LookaheadLayoutCoordinates.localLookaheadPositionOf] ৬ [LookaheadLayoutCoordinates.localPositionOf] ܳ ࢎਊೞৈ ঳ਸ ࣻ ੓णפ׮.


    * ੉ܳ ా೧ ҅࢑ػ intermediate layout ੄ য়೐ࣇਸ ӝ߈ਵ۽ ஹನ੷࠶੄ ߓ஖ܳ ઑ੿ೡ ࣻ ੓णפ׮.


    */


    fun Modifier.onPlaced(


    onPlaced: (


    lookaheadScopeCoordinates: LookaheadLayoutCoordinates,


    layoutCoordinates: LookaheadLayoutCoordinates


    ) -> Unit


    ): Modifier


    View Slide

  23. fun Modifier.movement(lookaheadScope: LookaheadLayoutScope) = composed {


    var targetOffset: IntOffset? by remember { mutableStateOf(null) } // ߓ஖ೡ য়೐ࣇ


    var placementOffset by remember { mutableStateOf(IntOffset.Zero) } // അ੤ য়೐ࣇ


    with(lookaheadScope) {


    this@composed


    .onPlaced { lookaheadScopeCoordinates, layoutCoordinates ->


    // LookaheadLayout ੄ ۽ஸ coordinates ীࢲ ੉ modifier ੄ lookahead ਤ஖ܳ ߈ജ


    targetOffset = lookaheadScopeCoordinates


    .localLookaheadPositionOf(sourceCoordinates = layoutCoordinates)


    .round() // о੢ оө਍ IntOffset чਵ۽ য়೐ࣇ ߈ৢܿ


    // LookaheadLayout ੄ ۽ஸ coordinates ীࢲ ੉ modifier ੄ അ੤ ਤ஖ܳ ߈ജ


    placementOffset = lookaheadScopeCoordinates


    .localPositionOf(


    sourceCoordinates = layoutCoordinates,


    relativeToSource = Offset.Zero


    )


    .round()


    }


    .intermediateLayout { measurable, constraints, _ ->


    val placeable = measurable.measure(constraints)


    layout(width = placeable.width, height = placeable.height) {


    // ੉زೠ য়೐ࣇী ߓ஖





    View Slide

  24. targetOffset = lookaheadScopeCoordinates


    .localLookaheadPositionOf(sourceCoordinates = layoutCoordinates)


    .round() // о੢ оө਍ IntOffset чਵ۽ য়೐ࣇ ߈ৢܿ


    // LookaheadLayout ੄ ۽ஸ coordinate ীࢲ ੉ modifier ੄ അ੤ ਤ஖ܳ ߈ജ


    placementOffset = lookaheadScopeCoordinates


    .localPositionOf(


    sourceCoordinates = layoutCoordinates,


    relativeToSource = Offset.Zero


    )


    .round()


    }


    .intermediateLayout { measurable, constraints, _ ->


    val placeable = measurable.measure(constraints)


    layout(width = placeable.width, height = placeable.height) {


    // ੉زೠ য়೐ࣇী ߓ஖


    val (x, y) = targetOffset!! - placementOffset


    placeable.place(x = x, y = y)


    }


    }


    }


    }


    View Slide

  25. fun Modifier.transformation(lookaheadScope: LookaheadLayoutScope) = with(lookaheadScope) {


    intermediateLayout { measurable, _, lookaheadSize ->


    val (width, height) = lookaheadSize // lookahead ௼ӝ۽ width, height Ѿ੿


    val animatedConstraints = Constraints.fixed(


    width = width.coerceAtLeast(0), // ୭ࣗ 0 ਵ۽ ࢸ੿


    height = height.coerceAtLeast(0)


    )


    val placeable = measurable.measure(animatedConstraints)


    layout(width = placeable.width, height = placeable.height) { // lookahead ௼ӝী ݏѱ ߓ஖


    placeable.place(x = 0, y = 0)


    }


    }


    }


    View Slide

  26. LookaheadLayout(


    modifier = modifier


    .fillMaxSize()


    .navigationBarsPadding()


    .padding(16.dp),


    content = {


    Fab(


    modifier = Modifier


    .size(


    size = FabDefaults.size(


    isExpanded = isExpanded,


    maxWidthDp = screenMaxWidth.dp


    )


    )


    .movement(lookaheadScope = this)


    .transformation(lookaheadScope = this)


    .noRippleClickable { isExpanded = !isExpanded },


    isExpanded = isExpanded


    )


    },


    measurePolicy = DefaultMeasurePolicy


    )


    View Slide

  27. start
    fi
    nish
    Lookahead
    ௼ӝয়೐ࣇগפݫ੉࣌9

    View Slide

  28. 2. Animation

    View Slide

  29. #FGPSF😣"GUFS🪄

    View Slide

  30. ఫझ౟࢚࢝গפݫ੉࣌
    ߓ҃ਤ஖੉زগפݫ੉࣌
    GBEFJOPVU
    TMJEFJOPVUGBEFJOPVU
    "GUFS🪄

    View Slide

  31. ఫझ౟࢚࢝গפݫ੉࣌➡️BOJNBUF$PMPS"T4UBUF ߣبزੌ

    ߓ҃ਤ஖੉زগפݫ੉࣌➡️ߓ࢚҃࢝গפݫ੉࣌ਵ۽੐द؀୓
    GBEFJOPVU➡️"OJNBUFE$POUFOU
    TMJEFJOPVUGBEFJOPVU➡️"OJNBUFE$POUFOU
    "GUFS🪄

    View Slide

  32. Column(


    modifier = Modifier.fillMaxSize().background(color = Color.BackgroundWhite),


    verticalArrangement = Arrangement.SpaceBetween


    ) {


    TabContainer {


    TabDefaults.Items.forEach { tab ->


    TabItem(


    title = tab.shortname,


    backgroundColor = tabBackgroundColor(selectedTab = selectedTabState, nowTab = tab),


    textColor = tabTextColor(selectedTab = selectedTabState, nowTab = tab),


    onTabClick = { selectedTabState = tab }


    )


    }


    }


    MovieContainer {


    MovieName(fullname = selectedTabState.fullname)


    MoviePoster(posterDrawable = selectedTabState.poster)


    }


    }


    View Slide

  33. @Stable


    private fun tabBackgroundColor(


    selectedTab: Int,


    nowTab: Int


    ): Color = when (selectedTab == nowTab) {


    true -> TabDefaults.Color.selectedBackground


    false -> TabDefaults.Color.defaultBackground


    }


    @Stable


    private fun tabTextColor(


    selectedTab: Int,


    nowTab: Int


    ): Color = when (selectedTab == nowTab) {


    true -> TabDefaults.Color.selectedText


    false -> TabDefaults.Color.defaultText


    }


    @Composable


    private fun tabBackgroundColorWithAnimation(


    selectedTab: Tab,


    nowTab: Tab


    ): Color = animateColorAsState(


    targetValue = when (selectedTab == nowTab) {


    true -> TabDefaults.Color.selectedBackground


    false -> TabDefaults.Color.defaultBackground


    }


    ).value


    @Composable


    private fun tabTextColorWithAnimation(


    selectedTab: Tab,


    nowTab: Tab


    ): Color = animateColorAsState(


    targetValue = when (selectedTab == nowTab) {


    true -> TabDefaults.Color.selectedText


    false -> TabDefaults.Color.defaultText


    }


    ).value


    View Slide

  34. /**


    * [Color] ী ؀ೠ Fire-and-Forget গפݫ੉࣌ ӝמਸ ઁҕ೤פ׮.


    * animate*AsState ח [Float], [Color], [Dp], [Size], [Offset], [Rect], [Int], [IntOffset] ӒܻҊ [IntSize] ী ࢎ੹ ੿੄ظ ੓णפ׮.


    * ઁҕػ [targetValue] о ߸҃غݶ গפݫ੉࣌੉ ੗زਵ۽ प೯ؾפ׮.


    * [targetValue] о ߸҃ؼ ٸ ૓೯ ઺ੋ গפݫ੉࣌੉ ੉޷ ੓ח ҃਋ ૓೯ ઺ੋ গפݫ੉࣌਷ ࢜۽਍ [targetValue] ܳ ೱ೧ গפݫ੉࣌غب۾ ௏झܳ ઑ੿೤פ׮.


    *


    * animate*AsState ח গפݫ੉࣌੉ ੸ਊغҊ ੓ח [State] ܳ ߈ജ೤פ׮.


    *


    * @param targetValue গפݫ੉࣌੄ ఋѶ


    * @param animationSpec दрী ٮۄ чਸ ߸҃ೞח ؘ ࢎਊೡ গפݫ੉࣌


    * @param finishedListener গפݫ੉࣌੉ ৮ܐؼ ٸ ঌܿਸ ߉ח ࢶఖ੸ ܻझց


    */


    @Composable


    fun animateColorAsState(


    targetValue: Color,


    animationSpec: AnimationSpec = colorDefaultSpring,


    finishedListener: ((Color) -> Unit)? = null


    ): State

    View Slide

  35. গפݫ੉࣌ࢎনਸݏ୺ࢸ੿ೞӝਤೠੋఠಕ੉झ
    TQSJOH
    UXFFO
    LFZGSBNFT
    SFQFBUBCMFJOGJOJUF3FQFBUBCMF
    TOBQ
    "OJNBUJPO4QFD

    View Slide

  36. /**


    * द੘ чҗ ՘ ч ࢎ੉ী ޛܻ೟ ӝ߈ গפݫ੉࣌ਸ ੸ਊ೤פ׮.


    *


    * @param DampingRatio х࣭࠺ (఍ࢿ)


    * @param stiffness ъࢿ (ઙܐ чਵ۽ ੉زೞח ࣘب)


    * @param visibleThreshold оदࢿ ੐҅ч


    * গפݫ੉࣌੉ ؀࢚ী ߈ৢܿೞӝী ୽࠙൤ दп੸ਵ۽ оө਍ Ѫਵ۽ р઱غযঠ ೞח दӝܳ ੿੄೤פ׮.


    *


    * @return ઱য૓ ২࣌ਸ ࢎਊೞח [SpringSpec]


    */


    @Stable


    fun spring(


    dampingRatio: Float = Spring.DampingRatioNoBouncy,


    stiffness: Float = Spring.StiffnessMedium,


    visibilityThreshold: T? = null


    ): SpringSpec = SpringSpec(


    dampingRatio = dampingRatio,


    stiffness = stiffness,


    visibilityThreshold = visibilityThreshold


    )


    View Slide

  37. /**


    * ੉૚ ҋࢶਸ ࢎਊೞৈ ૑੿ػ [durationMillis] زউ द੘ чҗ ՘ ч рী গפݫ੉࣌ਸ ੸ਊ೤פ׮.


    *


    * @param durationMillis গפݫ੉࣌ ૑ࣘ दр (޻ܻୡ)


    * @param delayMillis গפݫ੉࣌੉ द੘غӝ ੹ী ؀ӝೞח दр (޻ܻୡ)


    * @param easing द੘җ ՘ ࢎ੉ܳ ࠁрೞח ؘ ࢎਊغח ੉૚ ҋࢶ


    *


    * @return ઱য૓ ২࣌ਸ ࢎਊೞח [TweenSpec]


    */


    @Stable


    fun tween(


    durationMillis: Int = AnimationConstants.DefaultDurationMillis,


    delayMillis: Int = 0,


    easing: Easing = FastOutSlowInEasing


    ): TweenSpec = TweenSpec(


    durationMillis = durationMillis,


    delay = delayMillis,


    easing = easing


    )

    View Slide

  38. /**


    * গפݫ੉࣌੄ ࠙ࣻ(fraction)ܳ ઑ੿ೞח ߑߨੑפ׮.


    * ੉૚ਸ ࢎਊೞݶ গפݫ੉࣌੉ ੌ੿ೠ ࣘب۽ ૓೯غח ؀न ࣘبܳ ֫੉Ѣա ծ୹ ࣻ ੓णפ׮.


    *


    * ࠙ࣻח গפݫ੉࣌੄ അ੤ ૑੼ਸ աఋղח 0 ীࢲ 1.0 ࢎ੉੄ чੑפ׮. ৈӝࢲ 0 ਷ द੘ਸ աఋղҊ 1.0 ਷ ՘ਸ աఋշפ׮.


    */


    @Stable


    fun interface Easing {


    fun transform(fraction: Float): Float


    }


    View Slide

  39. // ࡅܰѱ ࣘبܳ ֫੉Ҋ ੼ର੸ਵ۽ ו۰૘פ׮.


    val FastOutSlowInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 0.2f, 1.0f)


    // ٜযয়ח ਃࣗח ઁੌ ࡅܲ ࣘبীࢲ ੼੼ ו۰૘פ׮.


    val LinearOutSlowInEasing: Easing = CubicBezierEasing(0.0f, 0.0f, 0.2f, 1.0f)


    // աоח ਃࣗח ઁੌ וܽ ࣘبীࢲ ੼੼ ࡈۄ૘פ׮.


    val FastOutLinearInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 1.0f, 1.0f)


    // ࣻ੿غ૑ ঋ਷ ࠙ࣻܳ ߈ജ೤פ׮. ੉૚੉ ೙ਃೞ૑݅ ࣻ੿ػ ੉૚੉ ೙ਃೞ૑ ঋ਷ ҃਋ ӝࠄчਵ۽ ਬਊ೤פ׮.


    val LinearEasing: Easing = Easing { fraction -> fraction }


    // 3ର ߬૑য ҋࢶਸ ҳഅ೤פ׮.


    @Immutable


    class CubicBezierEasing(


    private val a: Float,


    private val b: Float,


    private val c: Float,


    private val d: Float


    ) : Easing


    View Slide

  40. IUUQTFBTJOHTOFU

    View Slide

  41. /**


    * গפݫ੉࣌ ӝрী ৈ۞ ఋ੐झఙ೐ীࢲ ૑੿ػ झշࢫ чਸ ӝ߈ਵ۽ গפݫ੉࣌ਸ ୊ܻ೤פ׮.


    * ঱ઁա গפݫ੉࣌ ч਷ ف ః೐ۨ੐ ч ࢎ੉ী ࠁрؾפ׮.


    * ః೐ۨ੐݃׮ ࢎਊೡ [Easing] ਸ ૑੿ೡ ࣻ ੓णפ׮.


    */


    @Stable


    fun keyframes(


    init: KeyframesSpec.KeyframesSpecConfig.() -> Unit


    ): KeyframesSpec = KeyframesSpec(


    config = KeyframesSpec.KeyframesSpecConfig().apply(init)


    )


    @Composable


    fun KeyframesExample(target: Int) {


    val value by animateIntAsState(


    targetValue = target, // 0 ~ 100


    animationSpec = keyframes {


    durationMillis = 1000 // 1000 ms زউ গפݫ੉࣌ ૓೯


    100 at 50 with LinearEasing // LinearEasing ਸ ࢎਊೞݴ 100 ms উী 50 ө૑ গפݫ੉࣌


    500 at 80 with FastOutSlowInEasing // FastOutSlowInEasing ਸ ࢎਊೞݴ 500 ms উী 80 ө૑ গפݫ੉࣌


    // ੉റ 501 ~ 1000 ms زউ ӝઓী ࢸ੿ೠ ੉૚ੋ FastOutSlowInEasing ܳ ҅ࣘ ࢎਊೞৈ 100 ө૑ গפݫ੉࣌


    }


    )


    }

    View Slide

  42. /**


    * ૑੿ػ ߈ࠂ പࣻী ب׳ೡ ٸө૑ ӝр ӝ߈ গפݫ੉࣌(৘: [tween] ژח [keyframes])ਸ ߈ࠂ੸ਵ۽ प೯೤פ׮.


    *


    * @param iterations ߈ࠂೡ പࣻ


    * @param animation ߈ࠂೡ গפݫ੉࣌


    * @param repeatMode ߈ࠂೡ ݽ٘ - [RepeatMode.Restart] ژח [RepeatMode.Reverse]


    * @param initialStartOffset গפݫ੉࣌ਸ द੘ೡ য়೐ࣇ


    */


    @Stable


    fun repeatable(


    iterations: Int,


    animation: DurationBasedAnimationSpec,


    repeatMode: RepeatMode = RepeatMode.Restart,


    initialStartOffset: StartOffset = StartOffset(0)


    ): RepeatableSpec = RepeatableSpec(


    iterations = iterations,


    animation = animation,


    repeatMode = repeatMode,


    initialStartOffset = initialStartOffset


    )

    View Slide

  43. /**


    * [repeatable] җ زੌೞҊ, ਬੌೠ ର੉੼਷ ޖೠ ߈ࠂ੉ۄ iterations ੋ੗о হणפ׮.


    */


    @Stable


    fun infiniteRepeatable(


    animation: DurationBasedAnimationSpec,


    repeatMode: RepeatMode = RepeatMode.Restart,


    initialStartOffset: StartOffset = StartOffset(0)


    ): InfiniteRepeatableSpec = InfiniteRepeatableSpec(


    animation = animation,


    repeatMode = repeatMode,


    initialStartOffset = initialStartOffset


    )

    View Slide

  44. /**


    * snap ਷ чਸ ૊द ઙܐ чਵ۽ ߸ജೞח ౠࣻ ݾ੸੄ [AnimationSpec] ੑפ׮.


    *


    * @param delayMillis গפݫ੉࣌ द੘ਸ ૑োೡ दр (޻ܻୡ)


    */


    @Stable


    fun snap(delayMillis: Int = 0): SnapSpec = SnapSpec(


    delay = delayMillis


    )


    View Slide

  45. @Composable


    fun animateColorAsState(


    targetValue: Color,


    animationSpec: AnimationSpec = colorDefaultSpring,


    finishedListener: ((Color) -> Unit)? = null


    ): State

    View Slide

  46. MovieContainer {


    MovieName(fullname = selectedTabState.fullname)


    MoviePoster(posterDrawable = selectedTabState.poster)


    }


    MovieContainer {


    AnimatedContent(


    modifier = Modifier


    .align(Alignment.CenterHorizontally)


    .padding(horizontal = 30.dp),


    targetState = selectedTabState


    ) { tab ->


    MovieName(fullname = tab.fullname)


    }


    AnimatedContent(


    modifier = Modifier.wrapContentSize(),


    targetState = selectedTabState


    ) { tab ->


    MoviePoster(posterDrawable = tab.poster)


    }


    }

    View Slide

  47. /**


    * [targetState] о ߸҃ؼ ٸ [content] ী ੗زਵ۽ গפݫ੉࣌ਸ ੸ਊೞח ஶప੉ցੑפ׮.


    *


    * @param targetState ߸҃ؼ ࢚క


    * @param modifier ੸ਊೡ [Modifier]


    * @param transitionSpec ੸ਊೡ গפݫ੉࣌


    * @param contentAlignment [content] о ߓ஖ؼ [Alignment]


    * @param content ߓ஖ೡ ஹನ੷࠶


    */


    @ExperimentalAnimationApi


    @Composable


    fun AnimatedContent(


    targetState: S,


    modifier: Modifier = Modifier,


    transitionSpec: AnimatedContentScope.() -> ContentTransform = { /* … */ },


    contentAlignment: Alignment = Alignment.TopStart,


    content: @Composable AnimatedVisibilityScope.(targetState: S) -> Unit


    )


    View Slide

  48. @ExperimentalAnimationApi


    @Composable


    fun AnimatedContent(


    targetState: S,


    modifier: Modifier = Modifier,


    transitionSpec: AnimatedContentScope.() -> ContentTransform = {


    fadeIn(


    animationSpec = tween(


    durationMillis = 220,


    delayMillis = 90


    )


    ) + scaleIn(


    initialScale = 0.92f,


    animationSpec = tween(


    durationMillis = 220,


    delayMillis = 90


    )


    ) with fadeOut(


    animationSpec = tween(durationMillis = 90)


    )


    },


    contentAlignment: Alignment = Alignment.TopStart,


    content: @Composable AnimatedVisibilityScope.(targetState: S) -> Unit


    )


    View Slide

  49. /**


    * ஹನ੷࠶੉ [AnimatedContent] ী ٜযоҊ աоח ߑߨਸ ੿੄೤פ׮.


    *


    * @param targetContentEnter ࢜۽਍ ஹನ੷࠶੉ ٜযয়ח গפݫ੉࣌


    * @param initialContentExit ӝઓ ஹನ੷࠶੉ աоח গפݫ੉࣌


    * @param targetContentZIndex ஹನ੷࠶੉ ٜযয়Ҋ աт ٸ ࢎਊೡ zIndex


    * @param sizeTransform ஹನ੷࠶੉ ٜযয়Ҋ աт ٸ ࢎ੉ૉо ߸ೡ ҃਋ ੉ܳ ҙܻೞӝ ਤೠ ২࣌


    */


    @ExperimentalAnimationApi


    class ContentTransform(


    val targetContentEnter: EnterTransition,


    val initialContentExit: ExitTransition,


    targetContentZIndex: Float = 0f,


    sizeTransform: SizeTransform? = SizeTransform()


    ) {


    var targetContentZIndex by mutableStateOf(targetContentZIndex)




    var sizeTransform: SizeTransform? = sizeTransform


    internal set


    }

    View Slide

  50. @ExperimentalAnimationApi


    class ContentTransform(


    val targetContentEnter: EnterTransition,


    val initialContentExit: ExitTransition,


    targetContentZIndex: Float = 0f,


    sizeTransform: SizeTransform? = SizeTransform()


    ) {


    /**


    * ஶప੉ցী ٜযт ٸ ࢜۽਍ ؀࢚ ஹನ੷࠶੄ zIndex ܳ ੄޷೤פ׮.


    * ӝࠄч਷ 0f ੑפ׮. zIndex о ֫਷ ஹನ੷࠶਷ zIndex о ծ਷ ஹನ੷࠶ ਤী Ӓ۰૘פ׮.


    * ੋؙझо э਷ ஹನ੷࠶਷ ఋѶ ஹನ੷࠶੉ ݔ ਤী ߓ஖ؾפ׮.


    */


    var targetContentZIndex by mutableStateOf(targetContentZIndex)


    /**


    * ࢜ ஹನ੷࠶੉ AnimatedContent ী ٜযоҊ ੉੹ ஹನ੷࠶੉ աт ٸ ௼ӝо ߸҃غח ҃਋ ஶప੉ց੄ ഛ੢ ߂ ୷ࣗܳ ҙܻ೤פ׮.


    * ӝࠄ੸ਵ۽ [spring][SpringSpec] ਷ ݽٚ ௼ӝ ߸҃ਸ গפݫ੉࣌ೞח ؘ ࢎਊغݴ [AnimatedContent] ח গפݫ੉࣌ػ ௼ӝ۽ ੜ݀פ׮.


    * ࢎਊؼ [SizeTransform] ܳ ૒੽ ࢸ੿ೡ ࣻ ੓णפ׮. ௼ӝ গפݫ੉࣌੉ ೙ਃೞ૑ ঋਵݶ [sizeTransform] ਸ null ۽ ࢸ੿ೞࣁਃ.


    */


    var sizeTransform: SizeTransform? = sizeTransform


    internal set


    }

    View Slide

  51. /**


    * ஹನ੷࠶੉ ಴दؼ ٸ ૓೯غח গפݫ੉࣌ਸ ੿੄೤פ׮.


    * ࢎਊೡ ࣻ ੓ח [EnterTransition] ੄ 4о૑ ஠పҊܻח ׮਺җ эणפ׮.


    *


    * 1. fade: [fadeIn]


    * 2. scale: [scaleIn]


    * 3. slide: [slideIn], [slideInHorizontally], [slideInVertically]


    * 4. expand: [expandIn], [expandHorizontally], [expandVertically]


    */


    @Immutable


    sealed class EnterTransition {


    internal abstract val data: TransitionData


    @Stable


    operator fun plus(enter: EnterTransition): EnterTransition {


    // …


    }


    }


    View Slide

  52. @Immutable


    sealed class EnterTransition {


    internal abstract val data: TransitionData // ղࠗীࢲ ࢎਊೞח ੿ࠁܳ ࠁҙೣ


    /**


    * ׮ܲ [EnterTransition] ਸ Ѿ೤೤פ׮. [EnterTransition] ח زदী द੘غ޲۽ Ѿ೤غח ࣽࢲח ઺ਃೞ૑ ঋणפ׮.


    * গפݫ੉࣌ਸ ੸ਊೞח ࣽࢲח ঌ౵ ߂ ߓਯਸ ݢ੷ ઑ੿ೞҊ ୷ࣗ ژח ഛ؀ೠ ׮਺ ठۄ੉٘೤פ׮.


    *


    * @param enter Ѿ೤ೡ ׮ܲ [EnterTransition]


    *


    * @return 2ѐ੄ [EnterTransition] ੉ Ѿ೤ػ ࢜۽਍ [EnterTransition]


    */


    @Stable


    operator fun plus(enter: EnterTransition): EnterTransition {


    return EnterTransitionImpl(


    TransitionData(


    fade = data.fade ?: enter.data.fade,


    slide = data.slide ?: enter.data.slide,


    changeSize = data.changeSize ?: enter.data.changeSize,


    scale = data.scale ?: enter.data.scale


    )


    )


    }


    }


    View Slide

  53. /**


    * ஹನ੷࠶੉ ࢎۄ૕ ٸ ૓೯غח গפݫ੉࣌ਸ ੿੄೤פ׮.


    * ࢎਊೡ ࣻ ੓ח [ExitTransition] ੄ 4о૑ ஠పҊܻח ׮਺җ эणפ׮.


    *


    * 1. fade: [fadeOut]


    * 2. scale: [scaleOut]


    * 3. slide: [slideOut], [slideOutHorizontally], [slideOutVertically]


    * 4. shrink: [shrinkOut], [shrinkHorizontally], [shrinkVertically]


    */


    @Immutable


    sealed class ExitTransition {


    internal abstract val data: TransitionData


    @Stable


    operator fun plus(exit: ExitTransition): ExitTransition {


    // …


    }


    }


    View Slide

  54. @Immutable


    sealed class ExitTransition {


    internal abstract val data: TransitionData // ղࠗীࢲ ࢎਊೞח ੿ࠁܳ ࠁҙೣ


    /**


    * ׮ܲ [ExitTransition] ਸ Ѿ೤೤פ׮. [ExitTransition] ח زदী द੘غ޲۽ Ѿ೤غח ࣽࢲח ઺ਃೞ૑ ঋणפ׮.


    * গפݫ੉࣌ਸ ੸ਊೞח ࣽࢲח ঌ౵ ߂ ߓਯਸ ݢ੷ ઑ੿ೞҊ ୷ࣗ ژח ഛ؀ೠ ׮਺ ठۄ੉٘೤פ׮.


    *


    * @param exit Ѿ೤ೡ ׮ܲ [ExitTransition]


    *


    * @return 2ѐ੄ [ExitTransition] ੉ Ѿ೤ػ ࢜۽਍ [ExitTransition]


    */


    @Stable


    operator fun plus(exit: ExitTransition): ExitTransition {


    return ExitTransitionImpl(


    TransitionData(


    fade = data.fade ?: exit.data.fade,


    slide = data.slide ?: exit.data.slide,


    changeSize = data.changeSize ?: exit.data.changeSize,


    scale = data.scale ?: exit.data.scale


    )


    )


    }


    }


    View Slide

  55. @ExperimentalAnimationApi


    class ContentTransform(


    val targetContentEnter: EnterTransition,


    val initialContentExit: ExitTransition,


    targetContentZIndex: Float = 0f,


    sizeTransform: SizeTransform? = SizeTransform()


    ) {


    var targetContentZIndex by mutableStateOf(targetContentZIndex)




    var sizeTransform: SizeTransform? = sizeTransform


    internal set


    }

    View Slide

  56. /**


    * ஹನ੷࠶੄ ௼ӝо ߸҃ؼ ٸ ೠ ௼ӝীࢲ ׮ܲ ௼ӝ۽ ߸ജೞח ߑߨਸ ੿੄೤פ׮.


    */


    @ExperimentalAnimationApi


    interface SizeTransform {


    /**


    * ࢎ੉ૉ ઑ੺ গפݫ੉࣌ীࢲ ஹನ੷࠶੄ ҃҅ী ݏѱ clip ೧ঠ ೞח૑ ৈࠗੑפ׮.


    */


    val clip: Boolean


    /**


    * গפݫ੉࣌ ੸ਊ ੹ ࢎ੉ૉੋ [initialSize] ৬ গפݫ੉࣌ ੸ਊ റ ࢎ੉ૉੋ [targetSize] ܳ ӝ߈ਵ۽ [AnimationSpec] ਸ ٜ݅ ࣻ ੓णפ׮.


    */


    fun createAnimationSpec(initialSize: IntSize, targetSize: IntSize): FiniteAnimationSpec


    }


    View Slide

  57. @ExperimentalAnimationApi


    @Composable


    fun AnimatedContent(


    targetState: S,


    modifier: Modifier = Modifier,


    transitionSpec: AnimatedContentScope.() -> ContentTransform = {


    fadeIn(


    animationSpec = tween(


    durationMillis = 220,


    delayMillis = 90


    )


    ) + scaleIn(


    initialScale = 0.92f,


    animationSpec = tween(


    durationMillis = 220,


    delayMillis = 90


    )


    ) with fadeOut(


    animationSpec = tween(durationMillis = 90)


    )


    },


    contentAlignment: Alignment = Alignment.TopStart,


    content: @Composable AnimatedVisibilityScope.(targetState: S) -> Unit


    )


    View Slide

  58. /**


    * ઁҕػ [this][EnterTransition] ߂ [exit] ܳ ࢎਊೞৈ [ContentTransform] ਸ ࢤࢿೞݴ,


    * [EnterTransition] ߂ [ExitTransition] গפݫ੉࣌੉ زदী प೯ؾפ׮.


    *


    * @param exit [this][EnterTransition] ৬ ೤ச [ExitTransition]


    *


    * @return [this][EnterTransition] ৬ [ExitTransition] ੉ ೤୛૓ [ContentTransform]


    */


    @ExperimentalAnimationApi


    infix fun EnterTransition.with(exit: ExitTransition) = ContentTransform(


    targetContentEnter = this,


    initialContentExit = exit


    )


    View Slide

  59. @ExperimentalAnimationApi


    @Composable


    fun AnimatedContent(


    targetState: S,


    modifier: Modifier = Modifier,


    transitionSpec: AnimatedContentScope.() -> ContentTransform = { /* … */ },


    contentAlignment: Alignment = Alignment.TopStart,


    content: @Composable AnimatedVisibilityScope.(targetState: S) -> Unit


    )


    View Slide

  60. /**


    * [AnimatedContent] ীࢲ݅ ಞܻೞѱ ੸ਊೡ ࣻ ੓ח ӝמਸ ઁҕ೤פ׮.


    */


    @ExperimentalAnimationApi


    class AnimatedContentScope internal constructor(


    internal val transition: Transition,


    internal var contentAlignment: Alignment,


    internal var layoutDirection: LayoutDirection


    ) : Transition.Segment {


    override val initialState: S get() = transition.segment.initialState // গפݫ੉࣌੉ द੘غӝ ੹ ୡӝ ч


    override val targetState: S get() = transition.segment.targetState // গפݫ੉࣌੉ ੸ਊؼ ч, ૊ গפݫ੉࣌੄ ઙܐ ч


    /**


    * അ੤ [this][ContentTransform] ੄ [sizeTransform] ܳ ੋ੗۽ ߉਷ [sizeTransform] ۽ ࢸ੿೤פ׮.


    *


    * @param sizeTransform ࢜۽ ੸ਊೡ [SizeTransform]


    *


    * @return [sizeTransform] ਸ ࢜۽ ੸ਊೠ [ContentTransform]


    */


    @ExperimentalAnimationApi


    infix fun ContentTransform.using(sizeTransform: SizeTransform?): ContentTransform = apply {


    this.sizeTransform = sizeTransform


    }


    // …

    View Slide



  61. /**


    * ஶప੉ց੄ о੢੗ܻীࢲ [AnimatedContent] ী ౠ੿ೠ ࣻಣ/ࣻ૒ slide-in ਸ ੿੄೤פ׮.


    * [slideInHorizontally] ߂ [slideInVertically] ৬ ׳ܻ द੘ য়೐ࣇ੉ [AnimatedContent] ੄


    * അ੤ ௼ӝ৬ ੿۳ ২࣌ਸ ӝ߈ਵ۽ ੗ز ҅࢑ؾפ׮.


    *


    * @param towards ठۄ੉٘ ߑೱ


    * ஹನ੷࠶਷ח [SlideDirection.Left], [SlideDirection.Right], [SlideDirection.Up] ߂ [SlideDirection.Down]


    * ߑೱਵ۽ ஶప੉ցܳ ೱ೧ slide ೡ ࣻ ੓णפ׮.


    * @param animationSpec ࢎਊೡ গפݫ੉࣌


    * @param initialOffset द੘ য়೐ࣇ. ੗زਵ۽ ҅࢑غ૑݅ ৉द ࣻزਵ۽ ૑੿ೡ ࣻب ੓णפ׮.


    */


    fun slideIntoContainer(


    towards: AnimatedContentScope.SlideDirection,


    animationSpec: FiniteAnimationSpec = spring(visibilityThreshold = IntOffset.VisibilityThreshold),


    initialOffset: (offsetForFullSlide: Int) -> Int = { it }


    ): EnterTransition


    // slideIntoContainer ৬ زੌ, ױ slide-in ؀न slide-out


    fun slideOutOfContainer(


    towards: AnimatedContentScope.SlideDirection,


    animationSpec: FiniteAnimationSpec = spring(visibilityThreshold = IntOffset.VisibilityThreshold),


    targetOffset: (offsetForFullSlide: Int) -> Int = { it }


    ): ExitTransition


    }


    View Slide

  62. @ExperimentalAnimationApi


    @Composable


    fun AnimatedContent(


    targetState: S,


    modifier: Modifier = Modifier,


    transitionSpec: AnimatedContentScope.() -> ContentTransform = { /* … */ },


    contentAlignment: Alignment = Alignment.TopStart,


    content: @Composable AnimatedVisibilityScope.(targetState: S) -> Unit


    )


    View Slide

  63. @Composable


    private fun tabBackgroundColorWithAnimation(


    selectedTab: Tab,


    nowTab: Tab


    ): Color = animateColorAsState(


    targetValue = when (selectedTab == nowTab) {


    true -> TabDefaults.Color.selectedBackground


    false -> TabDefaults.Color.defaultBackground


    }


    ).value


    @Composable


    private fun tabTextColorWithAnimation(


    selectedTab: Tab,


    nowTab: Tab


    ): Color = animateColorAsState(


    targetValue = when (selectedTab == nowTab) {


    true -> TabDefaults.Color.selectedText


    false -> TabDefaults.Color.defaultText


    }


    ).value


    MovieContainer {


    AnimatedContent(


    modifier = Modifier


    .align(Alignment.CenterHorizontally)


    .padding(horizontal = 30.dp),


    targetState = selectedTabState


    ) { tab ->


    MovieName(fullname = tab.fullname)


    }


    AnimatedContent(


    modifier = Modifier.wrapContentSize(),


    targetState = selectedTabState,


    contentAlignment = Alignment.Center


    ) { tab ->


    MoviePoster(posterDrawable = tab.poster)


    }


    }

    View Slide

  64. View Slide

  65. @ExperimentalAnimationApi


    @Composable


    fun AnimatedContent(


    targetState: S,


    modifier: Modifier = Modifier,


    transitionSpec: AnimatedContentScope.() -> ContentTransform = {


    fadeIn(


    animationSpec = tween(


    durationMillis = 220,


    delayMillis = 90


    )


    ) + scaleIn(


    initialScale = 0.92f,


    animationSpec = tween(


    durationMillis = 220,


    delayMillis = 90


    )


    ) with fadeOut(


    animationSpec = tween(durationMillis = 90)


    )


    },


    contentAlignment: Alignment = Alignment.TopStart,


    content: @Composable AnimatedVisibilityScope.(targetState: S) -> Unit


    )


    View Slide

  66. TMJEFJOPVUGBEFJOPVU
    DMJQ5P1BEEJOHGBMTF
    [*OEFY
    ೙ਃೠࣁࠗগפݫ੉࣌🖌️

    View Slide

  67. MovieContainer {


    AnimatedContent(


    modifier = Modifier


    .align(Alignment.CenterHorizontally)


    .padding(horizontal = 30.dp),


    targetState = selectedTabState


    ) { tab ->


    MovieName(fullname = tab.fullname)


    }


    AnimatedContent(


    modifier = Modifier.wrapContentSize(),


    targetState = selectedTabState,


    contentAlignment = Alignment.Center


    ) { tab ->


    MoviePoster(posterDrawable = tab.poster)


    }


    }


    MovieContainer {


    AnimatedContent(


    modifier = Modifier


    .align(Alignment.CenterHorizontally)


    .padding(horizontal = 30.dp),


    targetState = selectedTabState,


    transitionSpec = {


    fadeIn() with fadeOut() using SizeTransform(clip = false)


    }


    ) { tab ->


    MovieName(fullname = tab.fullname)


    }


    // …


    }

    View Slide

  68. MovieContainer {


    // …


    AnimatedContent(


    modifier = Modifier.wrapContentSize(),


    targetState = selectedTabState,


    contentAlignment = Alignment.Center,


    transitionSpec = {


    val targetIndex = targetState.index // targetState == Tab


    val initialIndex = initialState.index // initialState == Tab


    if (targetIndex > initialIndex) { // ׮਺ చ


    slideIntoContainer(


    towards = AnimatedContentScope.SlideDirection.Start


    ) with fadeOut() using SizeTransform(clip = false)


    } else { // ੉੹ చ


    fadeIn() with slideOutOfContainer(


    towards = AnimatedContentScope.SlideDirection.End


    ) using SizeTransform(clip = false)


    }.apply {


    targetContentZIndex = targetIndex.toFloat()


    }


    }


    ) { tab ->


    MoviePoster(posterDrawable = tab.poster)


    }


    }
    MovieContainer {


    AnimatedContent(


    modifier = Modifier


    .align(Alignment.CenterHorizontally)


    .padding(horizontal = 30.dp),


    targetState = selectedTabState


    ) { tab ->


    MovieName(fullname = tab.fullname)


    }


    AnimatedContent(


    modifier = Modifier.wrapContentSize(),


    targetState = selectedTabState,


    contentAlignment = Alignment.Center


    ) { tab ->


    MoviePoster(posterDrawable = tab.poster)


    }


    }


    View Slide

  69. View Slide

  70. View Slide

  71. View Slide

  72. 🤔

    View Slide

  73. View Slide

  74. /**


    * ࢚క ࣻળীࢲ ݽٚ গפݫ੉࣌ਸ ҙܻ೤פ׮.


    * গפݫ੉࣌਷ [Transition.animateFloat], [Transition.animateColor], [Transition.animateValue] ١ਸ


    * ࢎਊೞৈ ࢶ঱੸ੋ ߑधਵ۽ ٜ݅ ࣻ ੓णפ׮.


    *


    * @param initialState গפݫ੉࣌ ୡӝ ч


    * @param label Android Studio Animation Inspector ীࢲ [Transition] ਸ ҳ߹ೞח ؘ ࢎਊೡ కӒ


    */


    @Stable


    class Transition @PublishedApi internal constructor(


    initialState: S,


    label: String?


    )

    View Slide

  75. /**


    * [Transition] ਸ ࢤࢿೞҊ [targetState] чਵ۽ ୡӝ ࢚కܳ ૑੿೤פ׮.


    * [targetState] о ߸҃غݶ [Transition] ਷ ࢜ [targetState] чਵ۽ ٜ݅য૓ ݽٚ গפݫ੉࣌ਸ ઑ੿೤פ׮.


    *


    * @param targetState ߸҃ਸ х૑ೡ ؀࢚


    * @param label Android Studio Animation Inspector ীࢲ [Transition] ਸ ҳ߹ೞח ؘ ࢎਊೡ కӒ


    *


    * @return [targetState] чਵ۽ ୡӝ ࢚కо ૑੿ػ [Transition]


    */


    @Composable


    fun updateTransition(


    targetState: T,


    label: String? = null


    ): Transition

    View Slide

  76. /**


    * ૑੿ػ [Transition] ী ࢝ӭ গפݫ੉࣌ਸ ୶о೤פ׮.


    * ੉ח ੉ গפݫ੉࣌੄ ࢤݺ ઱ӝо [Transition] ী ੄೧ ҙܻؽਸ ੄޷೤פ׮.


    *


    * @param transitionSpec ੸ਊೡ গפݫ੉࣌


    * @param label Android Studio Animation Inspector ীࢲ [Transition] ਸ ҳ߹ೞח ؘ ࢎਊೡ కӒ


    * @param targetValueByState ૑੿ػ [Transition] ੄ গפݫ੉࣌੉ द੘عਸ ٸ ઁҕೡ ч


    *


    * @return গפݫ੉࣌੉ ੸ਊػ [Color] ੄ [State]


    */


    @Composable


    inline fun Transition.animateColor(


    noinline transitionSpec: @Composable Transition.Segment.() -> FiniteAnimationSpec = { spring() },


    label: String = "ColorAnimation",


    targetValueByState: @Composable (state: S) -> Color


    ): State

    View Slide

  77. /**


    * ࢚ਤ [Transition] ੄ ࢚క৬ ೞਤ ࢚క р੄ ݒೝਸ ӝ߈ਵ۽ ೞਤ [Transition] ਸ ٟ݅פ׮.


    * ੉ח ׮਺җ э਷ ਊب۽ ࢎਊؾפ׮:


    *


    * 1. ೞਤ [Transition] ࢚కܳ ࢚ਤ [Transition] ਵ۽ ഐ੉झ౴


    * ࢚ਤ [Transition] ਷ زੌೠ ؀࢚੄ ࢚క ߸҃ਵ۽ ੋ೧ ૓೯ ઺ੋ গפݫ੉࣌੉ ੓ח૑ ৈࠗܳ ੋध೤פ׮.


    * ੉ۧѱ ೞݶ ࢎ੹ী ૓೯઺੉؍ গפݫ੉࣌੉ ৮ܐغ঻ਸ ٸ ࣽର੸ਵ۽ ׮ܲ গפݫ੉࣌ਸ ࢸ੿ೡ ࣻ ੓णפ׮.


    *


    * 2. ҙबࢎ ܻ࠙


    * ࢚ਤ [Transition] ীࢲ ߉਷ ࢚కীࢲ ࢎਊೞח ੿ࠁ݅ ೙ఠ݂ೞৈ ೞਤ۽ ੹׳ೞҊ रਸ ٸ ਬਊೞѱ ॳੌ ࣻ ੓णפ׮.


    *


    * @param label Android Studio Animation Inspector ীࢲ [Transition] ਸ ҳ߹ೞח ؘ ࢎਊೡ కӒ


    * @param transformToChildState ࢚ਤ [Transition] ীࢲ чਸ ߉Ҋ ࢜۽ ݅ٚ ࢚క ч


    *


    * @return ࢜۽਍ ࢚కܳ о૑ח ೞਤ [Transition]


    */


    @ExperimentalTransitionApi


    @Composable


    inline fun Transition.createChildTransition(


    label: String = "ChildTransition",


    transformToChildState: @Composable (parentState: S) -> T


    ): Transition


    View Slide

  78. var selectedTabState by remember { mutableStateOf(TabDefaults.Items.first()) }


    val selectedTabTypeTransition = updateTransition(


    targetState = selectedTabState.type,


    label = "selected tab"


    )


    // …


    TabContainer {


    TabDefaults.Items.forEach { tab ->


    val backgroundColor by selectedTabTypeTransition.animateColor(


    transitionSpec = { defaultTween() },


    label = "background color"


    ) { movie ->


    when (movie == tab.type) {


    true -> TabDefaults.Color.selectedBackground


    false -> TabDefaults.Color.defaultBackground


    }


    }


    val textColor by selectedTabTypeTransition.animateColor(


    transitionSpec = { defaultTween() },


    label = "text color"


    ) { movie ->


    when (movie == tab.type) {


    true -> TabDefaults.Color.selectedText




    View Slide



  79. ) { movie ->


    when (movie == tab.type) {


    true -> TabDefaults.Color.selectedBackground


    false -> TabDefaults.Color.defaultBackground


    }


    }


    val textColor by selectedTabTypeTransition.animateColor(


    transitionSpec = { defaultTween() },


    label = "text color"


    ) { movie ->


    when (movie == tab.type) {


    true -> TabDefaults.Color.selectedText


    false -> TabDefaults.Color.defaultText


    }


    }


    TabItem(


    title = tab.shortname,


    backgroundColor = backgroundColor,


    textColor = textColor,


    onTabClick = {


    selectedTabState = tab


    }


    )


    }


    }

    View Slide

  80. View Slide

  81. MovieContainer {


    AnimatedContent(


    modifier = Modifier


    .align(Alignment.CenterHorizontally)


    .padding(horizontal = 30.dp),


    targetState = selectedTabState,


    // … ӝઓҗ زੌ


    ) { tab ->


    MovieName(fullname = tab.fullname)


    }


    AnimatedContent(


    modifier = Modifier.wrapContentSize(),


    targetState = selectedTabState,


    // … ӝઓҗ زੌ


    ) { tab ->


    MoviePoster(posterDrawable = tab.poster)


    }


    }

    View Slide

  82. /**


    * [this][Transition] ੄ targetState о ߸҃ؼ ٸ ੗زਵ۽ [content] ী গפݫ੉࣌ਸ ੸ਊೞח ஶప੉ցੑפ׮.


    *


    * @param modifier ੸ਊೡ [Modifier]


    * @param transitionSpec ੸ਊೡ গפݫ੉࣌


    * @param contentAlignment [content] о ߓ஖ؼ [Alignment]


    * @param contentKey [Transition] ੄ targetState ী ؀ೠ ః. زੌೠ ఃܳ ҕਬೞח ߸҃ੌ ҃਋ গפݫ੉࣌੉ ૓೯غ૑ ঋणפ׮.


    * @param content ߓ஖ೡ ஹನ੷࠶


    */


    @ExperimentalAnimationApi


    @Composable


    fun Transition.AnimatedContent(


    modifier: Modifier = Modifier,


    transitionSpec: AnimatedContentScope.() -> ContentTransform = {


    // ӝઓ AnimatedContent ৬ زੌ


    },


    contentAlignment: Alignment = Alignment.TopStart,


    contentKey: (targetState: S) -> Any? = { it },


    content: @Composable AnimatedVisibilityScope.(targetState: S) -> Unit


    )


    View Slide

  83. MovieContainer {


    selectedTabTypeTransition


    .createChildTransition(label = "selected tab fullname") { movie ->


    movie.fullname


    }


    .AnimatedContent(/* ӝઓҗ زੌ */) { tabFullname ->


    MovieName(fullname = tabFullname)


    }


    selectedTabTypeTransition


    .AnimatedContent(/* ӝઓҗ زੌ */) { movie ->


    MoviePoster(posterDrawable = movie.poster)


    }


    }


    View Slide

  84. View Slide

  85. View Slide

  86. ࢶఖߓ҃য়೐ࣇ੉ز
    ࢶఖߓ҃ݽন ۄ਍٬

    ೙ਃೠࣁࠗগפݫ੉࣌🖌️

    View Slide

  87. @Composable


    private fun MovieTab(


    selectedTabTypeTransition: Transition,


    updateSelectedTab: (tab: Tab) -> Unit


    ) {


    BoxWithConstraints(


    modifier = Modifier


    .fillMaxWidth()


    .wrapContentHeight()


    .clip(


    RoundedCornerShape(


    bottomStartPercent = DefaultCornerUnit,


    bottomEndPercent = DefaultCornerUnit


    )


    )


    ) {


    val backgroundBoxWidth = remember { maxWidth / TabDefaults.Items.count() }


    val backgroundOffsetTransition by selectedTabTypeTransition.animateIntOffset(


    transitionSpec = { defaultTween() },


    label = "background offset"


    ) { movie ->


    IntOffset(


    x = with(LocalDensity.current) {


    (eachItemWidth * movie.index).toPx()


    }.toInt(),




    View Slide

  88. )


    ) {


    val eachItemWidth = remember { maxWidth / TabDefaults.Items.count() }


    val backgroundOffsetTransition by selectedTabTypeTransition.animateIntOffset(


    transitionSpec = { defaultTween() },


    label = "background offset"


    ) { movie ->


    IntOffset(


    x = with(LocalDensity.current) {


    (backgroundBoxWidth * movie.index).toPx() // dp -> float


    }.toInt(), // float -> int


    y = 0


    )


    }


    val backgroundShapeTransition by selectedTabTypeTransition.animateValue(


    transitionSpec = { defaultTween() },


    label = "background shape",


    typeConverter = TwoWayConverter(


    convertToVector = { corner ->


    AnimationVector4D(


    v1 = corner.topStart.toPercent(),


    v2 = corner.topEnd.toPercent(),


    v3 = corner.bottomStart.toPercent(),


    v4 = corner.bottomEnd.toPercent()


    )


    },


    convertFromVector = { vector ->




    View Slide

  89. /**


    * গפݫ੉࣌ী ࢎਊغח ݽٚ ؘ੉ఠ ਬഋ਷ ରਗী ٮۄ


    * [AnimationVector1D], [AnimationVector2D], [AnimationVector3D] ژח [AnimationVector4D] ۽ ߸ജؾפ׮.


    * ٮۄࢲ ё୓੄ ৈ۞ ҳࢿਃࣗܳ пп ੗୓ ࣘب ୶੸ ӝמਸ ࢎਊೞৈ ة݀੸ਵ۽ গפݫ੉࣌ ୊ܻೡ ࣻ ੓णפ׮.


    *


    * ৘ܳ ٜয [Color] ח A, R, G, B 4ѐ੄ ҳࢿ ਃࣗ۽ ੉ܖযઉ ੓णפ׮.


    * ٮۄࢲ [AnimationVector4D] ܳ ࢎਊ೤פ׮.


    */


    sealed class AnimationVector {


    internal abstract fun reset()


    internal abstract fun newVector(): AnimationVector


    internal abstract operator fun get(index: Int): Float


    internal abstract operator fun set(index: Int, value: Float)


    internal abstract val size: Int


    }


    View Slide

  90. /**


    * ੐੄੄ ఋੑ [T] ীࢲ [AnimationVector] ۽ ߸ജೞҊ [AnimationVector] ܳ ׮द ఋੑ [T] ۽ ߸ജೞח ߑߨী ؀ೠ ੿੄о ನೣغয ੓णפ׮.


    * ੉ܳ ా೧ গפݫ੉࣌ਸ ݽٚ ఋੑ੄ ѐ୓ীࢲ ҳഅೡ ࣻ ੓णפ׮.


    */


    interface TwoWayConverter {


    /**


    * ఋੑ [T] ਸ [AnimationVector] ఋੑਵ۽ ߸ജೞח ߑߨਸ ੿੄೤פ׮.


    */


    val convertToVector: (T) -> V


    /**


    * [AnimationVector] ఋੑਸ ׮द ఋੑ [T] ۽ ߸ജೞח ߑߨਸ ੿੄೤פ׮.


    */


    val convertFromVector: (V) -> T


    }


    View Slide

  91. )


    }


    val backgroundShapeTransition by selectedTabTypeTransition.animateValue(


    transitionSpec = { defaultTween() },


    label = "background shape",


    typeConverter = TwoWayConverter(


    convertToVector = { corner ->


    AnimationVector4D(


    v1 = corner.topStart.getPercent(),


    v2 = corner.topEnd.getPercent(),


    v3 = corner.bottomStart.getPercent(),


    v4 = corner.bottomEnd.getPercent()


    )


    },


    convertFromVector = { vector ->


    RoundedCornerShape(


    topStartPercent = vector.v1.toInt(),


    topEndPercent = vector.v2.toInt(),


    bottomStartPercent = vector.v3.toInt(),


    bottomEndPercent = vector.v4.toInt()


    )


    }


    )


    ) { movie ->


    when (movie) {


    Movie.Thor -> RoundedCornerShape(bottomStartPercent = 30)


    Movie.Spider -> RoundedCornerShape(percent = 0)



    View Slide



  92. )


    ) { movie ->


    when (movie) {


    Movie.Thor -> RoundedCornerShape(bottomStartPercent = 30) // ৽ଃ -> ৽ଃ ೞױ݅ ۄ਍٬


    Movie.Spider -> RoundedCornerShape(percent = 0) // о਍ؘ -> ۄ਍٬ X


    Movie.Doctor -> RoundedCornerShape(bottomEndPercent = 30) // য়ܲଃ -> য়ܲଃ ೞױ݅ ۄ਍٬


    }


    }


    TabContainer {


    TabDefaults.Items.forEach { tab ->


    val textColor by selectedTabTypeTransition.animateColor(


    transitionSpec = { defaultTween() },


    label = "text color"


    ) { movie ->


    when (movie == tab.type) {


    true -> TabDefaults.Color.selectedText


    false -> TabDefaults.Color.defaultText


    }


    }


    TabItem(


    title = tab.shortname,


    backgroundColor = TabDefaults.Color.defaultBackground,


    textColor = textColor,


    onTabClick = {



    View Slide

  93. false -> TabDefaults.Color.defaultText


    }


    }


    TabItem(


    title = tab.shortname,


    backgroundColor = TabDefaults.Color.defaultBackground,


    textColor = textColor,


    onTabClick = {


    updateSelectedTab(tab)


    }


    )


    }


    }


    Box(


    modifier = Modifier


    .width(backgroundBoxWidth)


    .height(TabDefaults.Height)


    .offset { backgroundOffsetTransition }


    .clip(backgroundShapeTransition)


    .background(color = TabDefaults.Color.selectedBackground)


    )


    }


    }

    View Slide

  94. 🤩🥳

    View Slide

  95. \
    start
    fi
    nish
    Lookahead
    ௼ӝয়೐ࣇগפݫ੉࣌9

    View Slide

  96. fun Modifier.movement(lookaheadScope: LookaheadLayoutScope) = composed {


    var targetOffset: IntOffset? by remember { mutableStateOf(null) }


    var placementOffset by remember { mutableStateOf(IntOffset.Zero) }


    // 1. ӝࠄч੉ হ׮: animateAsState X


    // 2. ׮਺ গפݫ੉࣌ чਵ۽ ੿೧૓ ч੉ হ׮: Transition X


    with(lookaheadScope) {


    this@composed


    .onPlaced { lookaheadScopeCoordinates, layoutCoordinates ->


    targetOffset = lookaheadScopeCoordinates


    .localLookaheadPositionOf(sourceCoordinates = layoutCoordinates)


    .round()


    placementOffset = lookaheadScopeCoordinates


    .localPositionOf(


    sourceCoordinates = layoutCoordinates,


    relativeToSource = Offset.Zero


    )


    .round()


    }


    .intermediateLayout { measurable, constraints, _ ->


    val placeable = measurable.measure(constraints)


    layout(width = placeable.width, height = placeable.height) {


    val (x, y) = targetOffset!! - placementOffset






    View Slide

  97. fun Modifier.transformation(lookaheadScope: LookaheadLayoutScope) = with(lookaheadScope) {


    intermediateLayout { measurable, _, lookaheadSize ->


    val (width, height) = lookaheadSize


    // 1. ӝࠄч੉ হ׮: animateAsState X


    // 2. ׮਺ গפݫ੉࣌ чਵ۽ ੿೧૓ ч੉ হ׮: Transition X


    val animatedConstraints = Constraints.fixed(


    width = width.coerceAtLeast(0),


    height = height.coerceAtLeast(0)


    )


    val placeable = measurable.measure(animatedConstraints)


    layout(width = placeable.width, height = placeable.height) {


    placeable.place(x = 0, y = 0)


    }


    }


    }


    View Slide

  98. /**


    * [animateTo] ܳ ా೧ ч੉ ߸҃ؼ ٸ ੗زਵ۽ чী গפݫ੉࣌ਸ ੸ਊೞח ч ഓ؊ੑפ׮.


    */


    class Animatable(


    initialValue: T, // ୡӝ ч


    val typeConverter: TwoWayConverter, // গפݫ੉࣌ ё୓ী ࢎਊೡ AnimationVector


    private val visibilityThreshold: T? = null // оदࢿ ੐҅ч


    ) {


    // গפݫ੉࣌੄ അ੤ ч


    val value: T get() = internalState.value


    // അ੤ গפݫ੉࣌੄ ؀࢚. গפݫ੉࣌੉ ઺ױ হ੉ ՘աݶ ੉ ݾ಴ чী ب׳೤פ׮.


    var taretValue: T by mutableStateOf(initialValue)


    private set


    // targetValue чਸ ೱ೧ গפݫ੉࣌ਸ द੘೤פ׮. block ੋ੗ח ݒ গפݫ੉࣌ ೐ۨ੐ ݃׮ ഐ୹ؾפ׮.


    suspend fun animateTo(


    targetValue: T,


    animationSpec: AnimationSpec = defaultSpringSpec,


    initialVelocity: T = velocity,


    block: (Animatable.() -> Unit)? = null


    ): AnimationResult


    }

    View Slide

  99. fun Modifier.animateMovement(


    lookaheadScope: LookaheadLayoutScope,


    animationSpec: AnimationSpec = defaultSpring()


    ) = composed {


    var placementOffset by remember { mutableStateOf(IntOffset.Zero) }


    var targetOffset: IntOffset? by remember { mutableStateOf(null) }


    var targetOffsetAnimation: Animatable? by remember {


    mutableStateOf(null)


    }


    LaunchedEffect(Unit) {


    snapshotFlow { targetOffset }.collect { target ->


    if (target != null && target != targetOffsetAnimation?.targetValue) {


    targetOffsetAnimation?.run {


    launch {


    animateTo(


    targetValue = target,


    animationSpec = animationSpec


    )


    }


    } ?: Animatable(


    initialValue = target,


    typeConverter = IntOffset.VectorConverter


    ).let { offsetAnimatable ->



    View Slide

  100. animationSpec = animationSpec


    )


    }


    } ?: Animatable(


    initialValue = target,


    typeConverter = IntOffset.VectorConverter


    ).let { offsetAnimatable ->


    targetOffsetAnimation = offsetAnimatable


    }


    }


    }


    }


    with(lookaheadScope) {


    this@composed


    .onPlaced { /* ӝઓҗ زੌ */ }


    .intermediateLayout { measurable, constraints, _ ->


    val placeable = measurable.measure(constraints)


    layout(width = placeable.width, height = placeable.height) {


    val (x, y) = (targetOffsetAnimation?.value ?: targetOffset!!) - placementOffset


    placeable.place(x = x, y = y)


    }


    }


    }


    }

    View Slide

  101. fun Modifier.animateTransformation(


    lookaheadScope: LookaheadLayoutScope,


    animationSpec: AnimationSpec = defaultSpring()


    ) = composed {


    var targetSize: IntSize? by remember { mutableStateOf(null) }


    var targetSizeAnimation: Animatable? by remember {


    mutableStateOf(null)


    }


    LaunchedEffect(Unit) {


    snapshotFlow { targetSize }.collect { target ->


    if (target != null && target != targetSizeAnimation?.targetValue) {


    targetSizeAnimation?.run {


    launch {


    animateTo(


    targetValue = target,


    animationSpec = animationSpec


    )


    }


    } ?: Animatable(


    initialValue = target,


    typeConverter = IntSize.VectorConverter


    ).let { sizeAnimatable ->


    targetSizeAnimation = sizeAnimatable


    }




    View Slide



  102. } ?: Animatable(


    initialValue = target,


    typeConverter = IntSize.VectorConverter


    ).let { sizeAnimatable ->


    targetSizeAnimation = sizeAnimatable


    }


    }


    }


    }


    with(lookaheadScope) {


    [email protected] { measurable, _, lookaheadSize ->


    targetSize = lookaheadSize


    val (width, height) = targetSizeAnimation?.value ?: lookaheadSize


    val animatedConstraints = Constraints.fixed(


    width = width.coerceAtLeast(0),


    height = height.coerceAtLeast(0)


    )


    val placeable = measurable.measure(animatedConstraints)


    layout(width = placeable.width, height = placeable.height) {


    placeable.place(x = 0, y = 0)


    }


    }


    }




    View Slide

  103. View Slide

  104. ೞա੄"OJNBUJPO4QFD݅оמ
    ೞա੄গפݫ੉࣌݅оמ
    ೞա੄ఋੑ݅оמ
    "OJNBUF
    <5>
    4UBUF
    4UBUF
    <5>
    "OJNBUF"T4UBUF
    "OJNBUJPO
    4QFD
    PCKFDU PCKFDU

    View Slide

  105. ৈ۞ѐ੄"OJNBUJPO4QFDоמ
    ৈ۞ѐ੄গפݫ੉࣌оמ
    ೞա੄ఋੑ݅оמ
    "OJNBUBCMF
    <5>
    4UBUF
    <5>
    PCKFDU PCKFDU
    BOJNBUF5P

    BOJNBUF5P

    "OJNBUJPO
    4QFD
    "OJNBUJPO
    4QFD
    "OJNBUBCMF

    View Slide

  106. ৈ۞ѐ੄"OJNBUJPO4QFDоמ
    ৈ۞ѐ੄গפݫ੉࣌оמ
    ৈ۞ѐ੄ఋੑоמ
    উ٘۽੉٘झౚ٣য়JOTQFDUоמ
    5SBOTJUJPO

    <5>
    4UBUF
    <5>
    5SBOTJUJPO
    PCKFDU PCKFDU
    BOJNBUF
    <5>
    BOJNBUF
    <5>
    "OJNBUJPO
    4QFD
    "OJNBUJPO
    4QFD

    View Slide

  107. 3. Internal System
    ↟-JWF-JUFSBM
    ↟4UBCJMJUZ

    View Slide

  108. View Slide

  109. fun earth() {


    print("Bye World")


    }


    View Slide

  110. fun earth() {


    print(LiveLiterals$EarthKt.`getString$arg-0$call-print$fun-earth`())


    }


    object LiveLiterals$EarthKt {


    var `String$arg-0$call-print$fun-earth` = "Bye World"


    var `State$String$arg-0$call-print$fun-earth`: MutableState? = null




    fun `getString$arg-0$call-print$fun-earth`(): String {


    val field = this.`String$arg-0$call-print$fun-earth`


    val state = if (field == null) {


    val tmp = liveLiteral(


    "String$arg-0$call-print$fun-earth",


    this.`String$arg-0$call-print$fun-earth`


    )


    this.`String$arg-0$call-print$fun-earth` = tmp


    tmp


    } else field


    return field.value


    }


    }


    View Slide

  111. fun earth() {


    print(LiveLiterals$EarthKt.`getString$arg-0$call-print$fun-earth`())


    }


    object LiveLiterals$EarthKt {


    var `String$arg-0$call-print$fun-earth` = "Bye World"


    var `State$String$arg-0$call-print$fun-earth`: MutableState? = null




    fun `getString$arg-0$call-print$fun-earth`(): String {


    val field = this.`String$arg-0$call-print$fun-earth`


    val state = if (field == null) {


    val tmp = liveLiteral(


    "String$arg-0$call-print$fun-earth",


    this.`String$arg-0$call-print$fun-earth`


    )


    this.`String$arg-0$call-print$fun-earth` = tmp


    tmp


    } else field


    return field.value


    }


    }


    View Slide

  112. fun earth() {


    print(LiveLiterals$EarthKt.`getString$arg-0$call-print$fun-earth`())


    }


    object LiveLiterals$EarthKt {


    var `String$arg-0$call-print$fun-earth` = "Bye World"


    var `State$String$arg-0$call-print$fun-earth`: MutableState? = null




    fun `getString$arg-0$call-print$fun-earth`(): String {


    val field = this.`String$arg-0$call-print$fun-earth`


    val state = if (field == null) {


    val tmp = liveLiteral(


    "String$arg-0$call-print$fun-earth",


    this.`String$arg-0$call-print$fun-earth`


    )


    this.`String$arg-0$call-print$fun-earth` = tmp


    tmp


    } else field


    return field.value


    }


    }


    View Slide

  113. fun earth() {


    print(LiveLiterals$EarthKt.`getString$arg-0$call-print$fun-earth`())


    }


    object LiveLiterals$EarthKt {


    var `String$arg-0$call-print$fun-earth` = "Bye World"


    var `State$String$arg-0$call-print$fun-earth`: MutableState? = null




    fun `getString$arg-0$call-print$fun-earth`(): String {


    val field = this.`String$arg-0$call-print$fun-earth`


    val state = if (field == null) {


    val tmp = liveLiteral(


    "String$arg-0$call-print$fun-earth",


    this.`String$arg-0$call-print$fun-earth`


    )


    this.`String$arg-0$call-print$fun-earth` = tmp


    tmp


    } else field


    return field.value


    }


    }


    View Slide

  114. fun earth() {


    print(LiveLiterals$EarthKt.`getString$arg-0$call-print$fun-earth`())


    }


    object LiveLiterals$EarthKt {


    var `String$arg-0$call-print$fun-earth` = "Bye World"


    var `State$String$arg-0$call-print$fun-earth`: MutableState? = null




    fun `getString$arg-0$call-print$fun-earth`(): String {


    val field = this.`String$arg-0$call-print$fun-earth`


    val state = if (field == null) {


    val tmp = liveLiteral(


    "String$arg-0$call-print$fun-earth",


    this.`String$arg-0$call-print$fun-earth`


    )


    this.`String$arg-0$call-print$fun-earth` = tmp


    tmp


    } else field


    return field.value


    }


    }


    View Slide

  115. fun earth() {


    print(LiveLiterals$EarthKt.`getString$arg-0$call-print$fun-earth`())


    }


    object LiveLiterals$EarthKt {


    var `String$arg-0$call-print$fun-earth` = "Bye World"


    var `State$String$arg-0$call-print$fun-earth`: MutableState? = null




    fun `getString$arg-0$call-print$fun-earth`(): String {


    val field = this.`String$arg-0$call-print$fun-earth`


    val state = if (field == null) {


    val tmp = liveLiteral(


    "String$arg-0$call-print$fun-earth",


    this.`String$arg-0$call-print$fun-earth`


    )


    this.`String$arg-0$call-print$fun-earth` = tmp


    tmp


    } else field


    return field.value


    }


    }


    View Slide

  116. fun earth() {


    print(LiveLiterals$EarthKt.`getString$arg-0$call-print$fun-earth`())


    }


    object LiveLiterals$EarthKt {


    var `String$arg-0$call-print$fun-earth` = "Bye World"


    var `State$String$arg-0$call-print$fun-earth`: MutableState? = null




    fun `getString$arg-0$call-print$fun-earth`(): String {


    val field = this.`String$arg-0$call-print$fun-earth`


    val state = if (field == null) {


    val tmp = liveLiteral(


    "String$arg-0$call-print$fun-earth",


    this.`String$arg-0$call-print$fun-earth`


    )


    this.`String$arg-0$call-print$fun-earth` = tmp


    tmp


    } else field


    return field.value


    }


    }


    View Slide

  117. private val liveLiteralCache = HashMap>()


    @InternalComposeApi


    @ComposeCompilerApi


    fun liveLiteral(key: String, value: T): State {


    return liveLiteralCache.getOrPut(key) {


    mutableStateOf(value)


    } as State


    }


    View Slide

  118. @InternalComposeApi


    fun updateLiveLiteralValue(key: String, value: Any?) {


    var needToUpdate = true


    val stateObj = liveLiteralCache.getOrPut(key) {


    needToUpdate = false


    mutableStateOf(value)


    }


    if (needToUpdate) {


    stateObj.value = value


    }


    }


    View Slide

  119. private fun findEffectiveRecomposeScope(group: Int): RecomposeScopeImpl? {


    var current = group


    while (current > 0) {


    for (data in DataIterator(this, current)) {


    if (data is RecomposeScopeImpl) {


    return data


    }


    }


    current = groups.parentAnchor(current)


    }


    return null


    }


    View Slide

  120. // @Composable XXX


    fun earth() {


    print(LiveLiterals$EarthKt.`getString$arg-0$call-print$fun-earth`())


    }


    object LiveLiterals$EarthKt {


    var `String$arg-0$call-print$fun-earth` = "Bye World"


    var `State$String$arg-0$call-print$fun-earth`: MutableState? = null




    fun `getString$arg-0$call-print$fun-earth`(): String {


    val field = this.`String$arg-0$call-print$fun-earth`


    val state = if (field == null) {


    val tmp = liveLiteral(


    "String$arg-0$call-print$fun-earth",


    this.`String$arg-0$call-print$fun-earth`


    )


    this.`String$arg-0$call-print$fun-earth` = tmp


    tmp


    } else field


    return field.value


    }


    }


    View Slide

  121. /**


    * LiveLiteral ੉ ெઉ ੓ח ҃਋ীب ੸ਊغח ߧਤ ղীࢲ LiveLiteral ਸ ࢤࢿೞ૑ ঋب۾


    * ஹನૉ ஹ౵ੌ۞ী ಴दೞח ؘ ࢎਊؾפ׮.


    */


    @Target(


    AnnotationTarget.PROPERTY,


    AnnotationTarget.FUNCTION,


    AnnotationTarget.CLASS,


    AnnotationTarget.FILE


    )


    @Retention(AnnotationRetention.SOURCE)


    annotation class NoLiveLiterals


    @NoLiveLiterals


    fun earth() {


    print("Bye World")


    }


    View Slide

  122. 3. Internal System
    ↟-JWF-JUFSBM
    ↟4UBCJMJUZ

    View Slide

  123. /**


    * উ੿ ࢚కח ௼ѱ 3о૑੄ ઑѤਸ ٮܵפ׮.


    *


    * - ч੉ ߸҃عਸ ҃਋ ஹನ੷࠶ীѱ ঌ۰ઉঠ ೤פ׮. (૊, [State] ۽ ੘زظঠ ೣ)


    * - ੉੹ ੋझఢझ৬ അ੤ ੋझఢझо زੌ೧ঠ ೤פ׮.


    * - ݽٚ ҕѐ ೙ٜ٘ب ׮ উ੿ ࢚కৈঠ ೤פ׮.


    *


    * ੉ 3о૑ ઑѤਸ ٮܲ׮ݶ ஹನૉח ೧׼ ೙٘о উ੿੸ੋ ࢚కۄҊ ౵ঈೞҊ,


    * ೧׼ ೙٘੄ ч੉ ߄Շ૑ ঋও׮ݶ ࢎਊػ ஹನ੷࠶੄ ܻஹನ૑࣌ਸ ࢤۚ೤פ׮.


    *


    * @see Immutable


    * @see Stable


    */


    @Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)


    @Retention(AnnotationRetention.BINARY)


    annotation class StableMarker


    View Slide

  124. /**


    * ࢤࢿػ ੉റ۽ ݽٚ ҕѐ੸ੋ ೙٘о ੺؀ ߸ೞ૑ ঋח׮ח Ѫਸ աఋղݴ ௿ېझী ੸ਊؼ ࣻ ੓णפ׮.


    *


    * ࢤࢿ ੉റ ч੉ ߸҃غ૑ ঋਵ޲۽ উ੿੄ ୐ ߣ૩ ӏ஗ੋ


    * "ч੉ ߸҃عਸ ҃਋ ஹನ੷࠶ীѱ ঌ۰ઉঠ ೤פ׮." о ޖदؾפ׮.


    */


    @Target(AnnotationTarget.CLASS)


    @Retention(AnnotationRetention.BINARY)


    @StableMarker


    annotation class Immutable


    fun main() {


    val list = mutableListOf(1)


    list.add(2) // ੋझఢझח زੌೞ૑݅ ч੉ ߄Չ ࣻ ੓णפ׮.


    // @Immutable ਷ ੉۞ೠ ߸҃ب ೲਊೞ૑ ঋणפ׮.


    }


    View Slide

  125. /**


    * ч੉ ߸҃ؼ ࣻ ੓ח ࢚క੉ݴ, ੸ਊ ؀࢚ী ٮۄ ডрঀ ৉ೡ੉ ׳ۄ૘פ׮.


    *


    * ఋੑী ੸ਊػ׮ݶ ୶о ৉ೡ হ੉ [StableMarker] ੄ ৉ೡਸ Ӓ؀۽ оઉцפ׮.


    *


    * ೣࣻա ೐۽ಌ౭ী ੸ਊػ׮ݶ [StableMarker] ੄ ৉ೡী ୶оغח ৉ೡ੉ ࢤӤפ׮.


    * э਷ input ী ੓যࢲח ೦࢚ زੌೠ output ਸ ٜ݅যղݴ(ࣽࣻ ೣࣻ), ೣࣻ੄ ҃਋ ੋ੗ٜ ৉द ݽف উ੿੸ੋ ࢚కۄח Ѫਸ ডࣘ೤פ׮.


    * ݅ড input ੉ زੌೞ׮ݶ output ژೠ زੌೡ Ѫ੉ӝ ٸޙী ܻஹನ૑࣌ਸ झఈೞѱ ؾפ׮.


    */


    @Target(


    AnnotationTarget.CLASS,


    AnnotationTarget.FUNCTION,


    AnnotationTarget.PROPERTY_GETTER,


    AnnotationTarget.PROPERTY


    )


    @Retention(AnnotationRetention.BINARY)


    @StableMarker


    annotation class Stable


    View Slide

  126. /**


    * ஹ౵ੌ द੼ী ݽٚ ௿ېझٜ੄ উ੿ࢿਸ ੗زਵ۽ ୶ۿ೤פ׮.


    *


    * @param parameters উ੿ࢿਸ ୶ۿೞחؘ ب਑੉ ؼ ࣻ ੓ѱ ੋ੗ٜ੄ ࠺౟݃झ௼ܳ աఋշפ׮.


    */


    @ComposeCompilerApi


    @Target(AnnotationTarget.CLASS)


    @Retention(AnnotationRetention.BINARY)


    annotation class StabilityInferred(val parameters: Int)


    // ਬੌೠ ೙٘ੋ value о উ੿ ࢚క੉Ҋ ࠛ߸ೞӝ ٸޙী Name ௿ېझח উ੿ ࢚క۽ ౸ױؾפ׮.


    class Name(val value: String)


    // ਬੌೠ ೙٘ੋ value ੄ ఋੑ੉ ઁ֎ܼ੉ۄ ఋੑਸ ౸ױೡ ࣻ হӝী ࢎ੹ী ࠺౟݃झఊػ чਸ ӝળਵ۽ উ੿ ࢚కܳ ୶ۿ೤פ׮.


    // ೞ૑݅ ೙٘о о߸ ࢚క੉ӝ ٸޙী T ఋੑ੉ উ੿੉ৈب NameWithGeneric ௿ېझח ࠛউ੿ ࢚క۽ ౸ױؾפ׮.


    class NameWithGeneric(var value: T)


    View Slide

  127. @Composable


    fun TextWithImmutableList(texts: List) {


    Text(text = texts.joinToString())


    }


    View Slide

  128. public interface MutableList : List, MutableCollection


    @Composable


    fun TextWithImmutableList(texts: List = mutableListOf()) {


    Text(text = texts.joinToString())


    }


    View Slide

  129. @Immutable


    class ImmutableListWrapper(val values: List)


    @Composable


    fun TextWithImmutableList(texts: ImmutableListWrapper) {


    Text(text = texts.values.joinToString())


    }


    View Slide

  130. // https://github.com/Kotlin/kotlinx.collections.immutable


    @Composable


    fun TextWithImmutableList(texts: ImmutableList) {


    Text(text = texts.joinToString())


    }


    View Slide

  131. 4. Compiler Debugging
    ↟CVJMENFUSJDT
    ↟CVJMESFQPSUT

    View Slide

  132. CVJMENFUSJDT
    // ஹ౵ੌ۞о х૑ೠ ஹನ੷࠶ ೣٜࣻ੄ ੿ࠁܳ աఋշפ׮.


    kotlinOptions {


    freeCompilerArgs += [


    "-P",


    "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +


    "${rootProject.file(".").absolutePath}/report/compose-metrics"


    ]


    }

    View Slide

  133. <ݽٕݺ>@<࠽٘ఋੑ>NPEVMFKTPO
    {
    {


    "skippableComposables": 48, // skippable ࢚కੋ ஹನ੷࠶ ѐࣻ


    "restartableComposables": 67,


    "readonlyComposables": 0,


    "totalComposables": 67, // ੹୓ ஹನ੷࠶ ѐࣻ


    "restartGroups": 67,


    "totalGroups": 72,


    "staticArguments": 130,


    "certainArguments": 28,


    "knownStableArguments": 793, // উ੿ ࢚క۽ ঌ۰૓ ੋ੗ ѐࣻ


    "knownUnstableArguments": 30, // ࠛউ੿ ࢚క۽ ঌ۰૓ ੋ੗ ѐࣻ


    "unknownStableArguments": 8,


    "totalArguments": 831,


    "markedStableClasses": 0,


    "inferredStableClasses": 16, // উ੿ ࢚క۽ ୶ۿػ ௿ېझ ѐࣻ


    "inferredUnstableClasses": 2, // ࠛউ੿ ࢚క۽ ୶ۿػ ௿ېझ ѐࣻ


    "inferredUncertainClasses": 0,


    "effectivelyStableClasses": 16,


    "totalClasses": 18,


    "memoizedLambdas": 54,


    "singletonLambdas": 5,


    "singletonComposableLambdas": 8,


    "composableLambdas": 31,


    "totalLambdas": 89


    }


    View Slide

  134. CVJMESFQPSUT
    // ஹ౵ੌ۞о х૑ೠ ஹನ੷࠶ ೣٜࣻ੉ ࢎਊ઺ੋ ௿ېझ৬ ੋ੗ী ؀ೠ উ੿ࢿ ੿ࠁܳ աఋշפ׮.


    kotlinOptions {


    freeCompilerArgs += [


    "-P",


    "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +


    "${rootProject.file(".").absolutePath}/report/compose-reports"


    ]


    }

    View Slide

  135. <ݽٕݺ>@<࠽٘ఋੑ>DMBTTFTUYU
    /* ——— classes ——— */


    sealed class UiState {


    object Loading : UiState()


    object Done : UiState()


    data class Exception(val throwable: Throwable) : UiState()


    }


    /* ——— reports ——— */


    stable class Loading {


    = Stable


    }


    stable class Done {


    = Stable


    }


    unstable class Exception {


    unstable val throwable: Throwable


    = Unstable


    }

    View Slide

  136. <ݽٕݺ>@<࠽٘ఋੑ>DPNQPTBCMFTDTW
    /* ——— composables ——— */


    @Composable


    fun DisplayText(text: String = "Hi") {


    Text(text = text)


    }


    @Composable


    fun DisplayTexts(texts: List) {


    Text(text = texts.joinToString())


    }


    /* ——— reports ——— */


    View Slide

  137. <ݽٕݺ>@<࠽٘ఋੑ>DPNQPTBCMFTUYU
    /* ——— composables ——— */


    @Composable


    fun DisplayText(text: String = "Hi") {


    Text(text = text)


    }


    @Composable


    fun DisplayTexts(texts: List) {


    Text(text = texts.joinToString())


    }


    /* ——— reports ——— */


    restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun DisplayText(


    stable text: String? = @dynamic LiveLiterals$MainActivityKt.String$param-text$fun-DisplayText()


    )


    restartable /* skippable ੉ হ਺ */ scheme("[androidx.compose.ui.UiComposable]") fun DisplayTexts(


    unstable texts: List


    )


    View Slide

  138. <ݽٕݺ>@<࠽٘ఋੑ>DPNQPTBCMFTUYU
    /* ——— composables ——— */


    @Composable


    fun DisplayText(text: String = "Hi") {


    Text(text = text)


    }


    @Composable


    fun DisplayTexts(texts: List) {


    Text(text = texts.joinToString())


    }


    /* ——— reports ——— */


    restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun DisplayText(


    stable text: String? = @dynamic LiveLiterals$MainActivityKt.String$param-text$fun-DisplayText()


    )


    restartable scheme("[androidx.compose.ui.UiComposable]") fun DisplayTexts(


    unstable texts: List


    )


    View Slide

  139. <ݽٕݺ>@<࠽٘ఋੑ>DPNQPTBCMFTUYU
    /* ——— composables ——— */


    @NoLiveLiterals // new


    @Composable


    fun DisplayText(text: String = "Hi") {


    Text(text = text)


    }


    @Composable


    fun DisplayTexts(texts: List) {


    Text(text = texts.joinToString())


    }


    /* ——— reports ——— */


    restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun DisplayText(


    stable text: String? = @static "Hi"


    )


    restartable scheme("[androidx.compose.ui.UiComposable]") fun DisplayTexts(


    unstable texts: List


    )


    View Slide

  140. 5. Best Practice
    ↟ܻஹನ૑࣌ߧਤ઴੉ӝ
    ↟ܻஹನ૑࣌হগӝ
    ↟ࠛ೙ਃೠ࢚క୶੸ઁѢ
    ↟"05ஹ౵ੌഝࢿച

    View Slide

  141. EPOVUIPMFTLJQQJOH
    setContent {


    var number by remember { mutableStateOf(0) }


    println("setContent recomposition")


    Text(


    modifier = Modifier.clickable { number++ },


    text = number.toString()


    ).also { println("Text recomposition") }


    }


    /*


    ܻஹನ૑࣌੉ য٣ী ૓೯ؼөਃ?


    1, setContent content ৔৉


    2. Text


    3. setContent content ৔৉ + Text


    */

    View Slide

  142. EPOVUIPMFTLJQQJOH
    setContent {


    var number by remember { mutableStateOf(0) }


    println("setContent recomposition")


    Text(


    modifier = Modifier.clickable { number++ },


    text = number.toString()


    ).also { println("Text recomposition") }


    }


    /*


    [first-composition]


    setContent recomposition


    Text recomposition


    [re-composition]


    setContent recomposition


    Text recomposition


    ੉റ زੌ ۽Ӓ ߈ࠂ


    */

    View Slide

  143. EPOVUIPMFTLJQQJOH
    setContent {


    var number by remember { mutableStateOf(0) }


    println("setContent recomposition")


    Button(onClick = { number++ }) {


    Text(


    text = number.toString()


    ).also { println("Text recomposition") }


    }.also { println("Button recomposition") }


    }


    /*


    [first-composition]


    setContent recomposition


    Text recomposition


    Button recomposition


    [re-composition]


    Text recomposition


    ੉റ زੌ ۽Ӓ ߈ࠂ


    */

    View Slide

  144. EPOVUIPMFTLJQQJOH
    // ஹ౵ੌ ੹


    @Composable


    fun TextWrapper(text: String) {


    Text(text = text)


    }


    // ஹ౵ੌ റ (೨ब ࠗ࠙݅ ಴द)


    @Composable


    fun TextWrapper(composer: Composer, text: String) {


    composer.startRestartGroup(-199242123) // ղࠗীࢲ addRecomposeScope() ۽ ܻஹನ૑࣌ झ௏೐ܳ ୶оೞҊ ੓਺


    Text(text = text)


    composer.endRestartGroup()


    }

    View Slide

  145. EPOVUIPMFTLJQQJOH
    setContent {


    var number by remember { mutableStateOf(0) }


    println("setContent recomposition")


    Text(


    modifier = Modifier.clickable { number++ },


    text = number.toString()


    ).also { println("Text recomposition") }


    }


    setContent {


    var number by remember { mutableStateOf(0) }


    println("setContent recomposition")


    Button(onClick = { number++ }) {


    Text(


    text = number.toString()


    ).also { println("Text recomposition") }


    }.also { println("Button recomposition") }


    }


    Text RecomposeScope

    Text RecomposeScope

    View Slide

  146. EPOVUIPMFTLJQQJOH
    setContent {


    var number by remember { mutableStateOf(0) }


    println("setContent recomposition")


    Button(onClick = { number++ }) {


    Text(


    text = number.toString()


    ).also { println("Text recomposition") }


    }.also { println("Button recomposition") }


    }


    Text RecomposeScope

    Button RecomposeScope

    View Slide

  147. 4UBCJMJUZ4ZTUFN
    setContent {


    var number by remember { mutableStateOf(1) }


    TextWrapper(


    modifier = Modifier.clickable { number++ },


    text = number.toString()


    )


    }


    @Composable


    fun TextWrapper(


    modifier: Modifier = Modifier,


    text: String


    ) {


    Text(modifier = modifier, text = text)


    }


    View Slide

  148. 4UBCJMJUZ4ZTUFN
    setContent {


    var number by remember { mutableStateOf(1) }


    TextWithLambda(


    modifier = Modifier.clickable { number++ },


    text = { number.toString() }


    )


    }


    @Composable


    fun TextWithLambda(


    modifier: Modifier = Modifier,


    text: () -> String // Function0


    ) {


    Text(modifier = modifier, text = text())


    }


    View Slide

  149. 5. Best Practice
    ↟ܻஹನ૑࣌ߧਤ઴੉ӝ
    ↟ܻஹನ૑࣌হগӝ
    ↟ࠛ೙ਃೠ࢚క୶੸ઁѢ
    ↟"05ஹ౵ੌഝࢿച

    View Slide

  150. NPWBCMF$POUFOU0G
    val content = remember<@Composable () -> Unit> {


    {


    repeat(2) {


    Box(modifier = Modifier.size(100.dp).background(color = Color.Green))


    }


    }


    }


    Column {


    Button(onClick = { isRow = !isRow }) {


    Text(text = "Switch")


    }


    if (isRow) {


    Row { content() }


    } else {


    Column { content() }


    }


    }

    View Slide

  151. NPWBCMF$POUFOU0G
    val content = remember {


    movableContentOf { // movableContentWithReceiverOf


    repeat(2) {


    Box(modifier = Modifier.size(100.dp).background(color = Color.Green))


    }


    }


    }


    Column {


    Button(onClick = { isRow = !isRow }) {


    Text(text = "Switch")


    }


    if (isRow) {


    Row { content() }


    } else {


    Column { content() }


    }


    }

    View Slide

  152. !3FBE0OMZ$PNQPTBCMF
    object MaterialTheme {


    val colors: Colors


    @Composable


    @ReadOnlyComposable


    get() = LocalColors.current


    val typography: Typography


    @Composable


    @ReadOnlyComposable


    get() = LocalTypography.current


    // …


    }


    @Composable


    @ReadOnlyComposable


    fun stringResource(@StringRes id: Int): String {


    val resources = resources()


    return resources.getString(id)


    }

    View Slide

  153. !/PO3FTUBSUBCMF$PNQPTBCMF
    @Composable


    @NonRestartableComposable


    fun DisposableEffect(key1: Any?, effect: DisposableEffectScope.() -> DisposableEffectResult) {


    remember(key1) { DisposableEffectImpl(effect) }


    }


    @Composable


    @NonRestartableComposable


    fun Image(


    modifier: Modifier = Modifier, colorFilter: ColorFilter? = null,


    imageVector: ImageVector, contentDescription: String?, alpha: Float = DefaultAlpha,


    alignment: Alignment = Alignment.Center, contentScale: ContentScale = ContentScale.Fit


    ) {


    Image(


    modifier = modifier, alignment = alignment,


    contentScale = contentScale, alpha = alpha, colorFilter = colorFilter,


    painter = rememberVectorPainter(imageVector), contentDescription = contentDescription


    )


    }

    View Slide

  154. 5. Best Practice
    ↟ܻஹನ૑࣌ߧਤ઴੉ӝ
    ↟ܻஹನ૑࣌হগӝ
    ↟ࠛ೙ਃೠ࢚క୶੸ઁѢ
    ↟"05ஹ౵ੌഝࢿച

    View Slide

  155. TUBUJD$PNQPTJUJPO-PDBM0G
    val LocalContext = staticCompositionLocalOf {


    noLocalProvidedFor("LocalContext")


    }


    val LocalClipboardManager = staticCompositionLocalOf {


    noLocalProvidedFor("LocalClipboardManager")


    }


    val LocalDensity = staticCompositionLocalOf {


    noLocalProvidedFor("LocalDensity")


    }


    val LocalFocusManager = staticCompositionLocalOf {


    noLocalProvidedFor("LocalFocusManager")


    }

    View Slide

  156. !/P-JWF-JUFSBMT
    @file:NoLiveLiterals


    @NoLiveLiterals

    View Slide

  157. 5. Best Practice
    ↟ܻஹನ૑࣌ߧਤ઴੉ӝ
    ↟ܻஹನ૑࣌হগӝ
    ↟ࠛ೙ਃೠ࢚క୶੸ઁѢ
    ↟"05ஹ౵ੌഝࢿച

    View Slide

  158. #BTFMJOFQSPGJMFT
    #BTFMJOFQSPGJMFT਷ݠन௏٘ী؀ೠ઺ਃೠ҃۽ܳࢎ੹ஹ౵ੌೞӝਤ೧

    ࢸ஖઺ী"OESPJE۠ఋ੐ "35
    ীࢲࢎਊೞח"1,ীನೣػ௿ېझ߂ݫࢲ٘ݾ۾ੑפ׮
    IUUQTEFWFMPQFSBOESPJEDPNUPQJDQFSGPSNBODFCBTFMJOFQSPGJMFT

    View Slide

  159. #BTFMJOFQSPGJMFT
    .BDSPCFODINBSLݽٕࢸ੿

    View Slide

  160. #BTFMJOFQSPGJMFT
    .BDSPCFODINBSLݽٕࢸ੿
    ௏٘੘ࢿ CBTFMJOFQSPGJMFTࢤࢿ

    @ExperimentalBaselineProfilesApi


    @RunWith(AndroidJUnit4::class)


    class BaselineProfileGenerator {


    @get:Rule


    val baselineProfileRule = BaselineProfileRule()


    @Test


    fun startup() = baselineProfileRule.collectBaselineProfile(


    packageName = AppPackageName


    ) {


    pressHome()


    startActivityAndWait()


    }


    }


    View Slide

  161. #BTFMJOFQSPGJMFT
    .BDSPCFODINBSLݽٕࢸ੿
    ௏٘੘ࢿ CBTFMJOFQSPGJMFT੸ਊ੹റஏ੿

    @RunWith(AndroidJUnit4::class)


    class BaselineProfileBenchmark {


    @get:Rule


    val benchmarkRule = MacrobenchmarkRule()


    // …


    private fun startup(compilationMode: CompilationMode) {


    benchmarkRule.measureRepeated(


    packageName = AppPackageName,


    metrics = listOf(StartupTimingMetric()),


    iterations = 10,


    startupMode = StartupMode.COLD,


    compilationMode = compilationMode


    ) {


    pressHome()


    startActivityAndWait()


    }


    }


    }


    View Slide

  162. #BTFMJOFQSPGJMFT
    .BDSPCFODINBSLݽٕࢸ੿
    ௏٘੘ࢿ CBTFMJOFQSPGJMFT੸ਊ੹റஏ੿

    @RunWith(AndroidJUnit4::class)


    class BaselineProfileBenchmark {


    @get:Rule


    val benchmarkRule = MacrobenchmarkRule()


    @Test


    fun startupNoCompilation() {


    startup(compilationMode = CompilationMode.None()) // JIT ஹ౵ੌ


    }


    @Test


    fun startupBaselineProfile() {


    startup(compilationMode = CompilationMode.Partial(baselineProfileMode = BaselineProfileMode.Require)) // AOT ஹ౵ੌ


    }


    // …


    }


    View Slide

  163. #BTFMJOFQSPGJMFT
    .BDSPCFODINBSLݽٕࢸ੿
    ௏٘੘ࢿ
    #VJME7BSJBOUTࢸ੿

    View Slide

  164. #BTFMJOFQSPGJMFT
    ܖ౟ӂೠഝࢿച

    View Slide

  165. #BTFMJOFQSPGJMFT
    adb root

    View Slide

  166. #BTFMJOFQSPGJMFT
    ܖ౟ӂೠഝࢿച
    3VO

    View Slide

  167. #BTFMJOFQSPGJMFT
    ܖ౟ӂೠഝࢿച
    3VO
    adb pull

    View Slide

  168. #BTFMJOFQSPGJMFT
    ܖ౟ӂೠഝࢿച
    3VO
    adb pull


    CBTFMJOFQSPGUYUਵ۽౵ੌݺ߸҃റTSDNBJOਵ۽੉ز

    View Slide

  169. #BTFMJOFQSPGJMFT
    ܖ౟ӂೠഝࢿച
    3VO
    adb pull


    CBTFMJOFQSPGUYUਵ۽౵ੌݺ߸҃റTSDNBJOਵ۽੉ز
    ੸ਊ੹റ࠺Ү

    View Slide

  170. #BTFMJOFQSPGJMFT
    ܖ౟ӂೠഝࢿച
    3VO
    adb pull


    CBTFMJOFQSPGUYUਵ۽౵ੌݺ߸҃റTSDNBJOਵ۽੉ز
    ੸ਊ੹റ࠺Ү

    View Slide

  171. #BTFMJOFQSPGJMFT
    ܖ౟ӂೠഝࢿച
    3VO
    adb pull


    CBTFMJOFQSPGUYUਵ۽౵ੌݺ߸҃റTSDNBJOਵ۽੉ز
    ੸ਊ੹റ࠺Ү
    ੸ਊೡݽٕী੄ઓࢿ୶о
    dependencies {


    implementation("androidx.profileinstaller:profileinstaller:1.2.0-beta01")


    }


    View Slide

  172. 6. Wrap-up
    ↟ة݀ߡ੹ҙܻ୓҅द੘

    View Slide

  173. 6. Wrap-up
    ↟नӏ"1*DPMMFDU"T4UBUF8JUI-JGFDZDMF

    View Slide

  174. хࢎ೤פ׮
    ஹನૉ ղࠗ ௏ٜ٘਷ cs.android.com ীࢲ оઉ৳णפ׮.


    ੉ ೐ۨઃప੉࣌ীח షझ౱ীࢲ ઁҕೠ షझಕ੉झо ੸ਊغয ੓णפ׮.


    QnA🙋

    View Slide