$30 off During Our Annual Pro Sale. View Details »

Jetpack Compose, having and not.

Sungyong An
September 25, 2021

Jetpack Compose, having and not.

Jetpack Compose에 있는 것, 없는 것
드로이드나이츠 2021 행사에서 발표한 내용입니다.
https://sites.google.com/view/dk21/

Sungyong An

September 25, 2021
Tweet

More Decks by Sungyong An

Other Decks in Programming

Transcript

  1. Sungyong An
    Jetpack Composeী ੓ח Ѫ, হח Ѫ

    View Slide

  2. Timeline
    Stable!
    I/O '19
    Early Preview
    '19 05.
    11 Weeks


    of Android
    Alpha
    '20 08. '21 05.
    I/O '21
    '19 10.
    ADS '19
    Beta

    View Slide

  3. mordern UI toolkit
    ௏٘ хࣗ ૒ҙ੸
    declarative UI
    ъ۱ೠ ࢿמ
    ѐߊ ࣘب ೱ࢚
    Kotlin APIs

    View Slide

  4. mordern UI toolkit
    ௏٘ хࣗ ૒ҙ੸
    declarative UI
    ъ۱ೠ ࢿמ
    ѐߊ ࣘب ೱ࢚
    Kotlin APIs
    ҕध ѐߊ ޙࢲীࢲח ৈ۞ ੢੼ٜ੉ աৌغয ੓૑݅,


    ѐߊ੗݃׮ ׮ܲ ࢤпਸ ೞҊ ੓ਸ Ѫ э׮.

    View Slide

  5. ૑Әө૑ ੘ࢿ೧য়؍ XML ߑध੄ UI ҳഅীࢲ

    Jetpack Compose۽ ߄۽ ੹ജ೧ঠ ೞաਃ?
    🤔

    View Slide

  6. M V P M V VM M V I
    M V C
    ӝઓ੄ Jetpack ۄ੉࠳۞ܻ৬ ׮ܰѱ


    Jetpack Composeח জ .

    View Slide

  7. ೐۽ં౟݃׮ ࢚ട੉ ׮ܰ׮.


    ੿׹੉ হ׮.
    👀

    View Slide

  8. Compose ੸ਊਸ Ѩష೧ࠅ ࣻ ੓ѱ View ৬ ࠺Ү೧ࠁ੗
    ݾ಴

    View Slide

  9. 02
    01
    03
    ৵ Composeܳ ࢎਊ೧ঠ ೡө
    Composeী হח Ѫ
    Compose۽੄ ੹ജਸ ળ࠺ೞӝ
    ݾର

    View Slide

  10. ୶ୌ ৔࢚ (ҕध)
    Understanding Compose
    AndroidDevSummit '19
    I/O '19
    Declarative UI patterns Thinking in Compose
    11 Weeks of Android
    Compose੄ ӝࠄ ѐ֛ী ؀೧ࢲח ׮ܖ૑ ঋणפ׮.


    ҙ۲೧ࢲח ҕध ৔࢚ द୒ਸ ୶ୌ೤פ׮.

    View Slide

  11. ୶ୌ ৔࢚ (ೠҴয)
    Jetpack Compose: I/O 2021 Ӓ ੉റ
    I/O Extended Korea Android 2021
    I/O Extended with doubleS
    Compose द੘ೞӝ
    ৢ೧ ೠҴয ࣁ࣌ب ੓঻ਵפ, ҙब ੓ਵन ٜ࠙਷ द୒೧ࠁࣁਃ.

    View Slide

  12. View Compose
    ⚠ ৽ଃ਷ View, য়ܲଃ਷ Compose ղਊੑפ׮.

    View Slide

  13. ৵ Composeܳ ࢎਊ೧ঠ ೡө?
    01

    View Slide

  14. ✅ View ࠁ׮ Compose о ա਷ Ѫ?

    View Slide

  15. ✅ ೒ۖಬ ઙࣘࢿ ୭ࣗച
    Composeח Jetpack ۄ੉࠳۞ܻ۽ ߓನغয


    ೒ۖಬী ؏ ઙࣘ੸੉׮.
    ೒ۖಬ Viewীח APIܳ ࢎਊೡ ࣻ ੓ח ୭ࣗ ߡ੹ ઁড੉ ੓׮.


    ੉ܳ ೧ࣗೞ۰Ҋ, Compat APIܳ ઁҕೞ૑݅ ೠ҅о ੓׮.

    View Slide

  16. ✅ ࢚ࣘਵ۽ ੋೠ ޙઁ ೧ࣗ
    public class TextView extends View {}


    public class Button extends TextView {}
    @Composable


    fun Button(onClick: () -> Unit, ...) { ... }


    @Composable


    fun Text(text: String, ...) { ... }


    @Composable


    fun Sample() {


    Button(onClick = { ... }) {


    Text(text = "BUTTON")


    }


    }
    Viewח ߈٘द ࢚ࣘ೧ঠ݅ ೞҊ,


    Ӓېࢲ ਗ஖ ঋח ࠗݽ੄ ӝמب ыѱ ػ׮.
    Composeח composable ೣٜࣻ۽ ҳࢿغয,


    ೙ਃೠ ӝמ݅ ੘ѱ ਬ૑ೡ ࣻ ੓׮.

    View Slide

  17. ✅ ઺୏ ۨ੉ইਓ ࢿמ
















































































    @Composable


    fun Sample() {


    Column {


    Image()


    Image()


    Column {


    Text()


    Column {


    Text()


    Box {


    TextField()


    }


    }


    Column {


    Text()


    Box {


    TextField()




    ViewGroupী ٮۄ measureܳ ৈ۞ߣ ࣻ೯ೡ ࣻ ੓যࢲ


    ୭؀ೠ ۨ੉ইਓ੉ ઺୏غ૑ ঋب۾ न҃ॄঠ ೮׮.
    Composeח measureܳ ೠߣ݅ ࣻ೯ೞب۾ ъઁغয


    ઺୏ ۨ੉ইਓীࢲب ࢿמ੉ ௼ѱ ੷ೞغ૑ ঋח׮.

    View Slide

  18. ✅ UI ੤ࢎਊࢿ

    View Slide

  19. ✅ ૑ো ୡӝച





    android:id="@+id/failedStub"


    android:layout_width="match_parent"


    android:layout_height="match_parent"


    android:inflatedId="@+id/failed"


    android:layout="@layout/loading_failed"


    android:visibility="gone" />





    // show error


    View failedStub = findViewById(R.id.failedStub);


    failedStub.setOnInflateListener(new ViewStub.OnInflateListener() {


    @Override


    public void onInflate(ViewStub stub, final View inflated) { ... }


    });


    failedStub.inflate();
    @Composable


    fun Sample() {


    if (showError) {


    Column {


    Text()


    Button()


    }


    }


    }
    😱
    ViewStub਷ ࢎਊೞӝী


    ݒ਋ ࠛಞೞ׮.
    Composeח ઑѤޙ ݅ਵ۽ب


    рױ൤ ҳഅೡ ࣻ ੓׮.

    View Slide

  20. ✅ Single Source Of Truth
    public class TextView extends View {


    private @Nullable CharSequence mText;


    ...


    }


    public class EditText extends TextView {}


    viewModel.name.observe(this) {


    editText.setText(it)


    }


    editText.doOnTextChanged { text, _, _, _ ->


    viewModel.onNameChanged(text)


    )
    @Composable


    fun Sample() {


    val viewModel: SampleViewModel = viewModel()


    val name by viewModel.name.observeAsState()


    TextField(


    value = name,


    onValueChange = { viewModel.onNameChanged(it) }


    )


    }
    Viewח ۪؊݂ ݾ੸ਵ۽


    ؘ੉ఠ৬ ߹ѐ੄ ࢚కܳ ыҊ ੓׮.
    Composeח ߹ѐ੄ ࢚కܳ ы૑ ঋח׮.
    class SampleViewModel : ViewModel() {


    val name: LiveData = ...


    fun onNameChanged(name: String) { ... }


    }

    View Slide

  21. ✅ ؘ੉ఠ ߄ੋ٬
    @BindingAdapter("onTextChanged")


    fun EditText.onTextChanged(listener: (String) -> Unit){


    doOnTextChanged { text, _, _, _ ->


    listener(text.toString())


    }


    }
















    android:id="@+id/name_edit_text"


    android:text="@{viewmodel.name}"


    android:onTextChanged="@{viewmodel.onNameChanged}" />



    @Composable


    fun Sample() {


    val viewModel: SampleViewModel = viewModel()


    val name by viewModel.name.observeAsState()


    TextField(


    value = name,


    onValueChange = { viewModel.onNameChanged(it) }


    )


    }
    Viewীࢲח ؘ੉ఠ ߄ੋ٬ ݾ੸ਵ۽


    XMLҗ BindingAdapterܳ ੘ࢿ೧ঠ ೠ׮.
    Composeח ࢚క݅ ૑੿ೞݶ ੗زਵ۽ ߄ੋ٬ػ׮.


    ߹ѐ੄ composable ೣࣻ۽ ܻ࠙ೞݶ ػ׮.

    View Slide

  22. ✅ Custom View ҳഅ









    public class AppCompatImageView extends ImageView {


    public AppCompatImageView(...) {


    final int id = a.getResourceId(


    R.styleable.AppCompatImageView_srcCompat, -1);


    ...


    }


    }




    app:srcCompat="@drawable/image" />
    @Composable


    fun Image(


    painter: Painter,


    contentDescription: String?,


    modifier: Modifier = Modifier,


    ...


    ) { ... }


    @Composable


    fun Sample() {


    Image(painter = painterResource(R.drawable.image))


    }
    Viewীࢲ Custom Viewܳ ٜ݅ ٸب


    XML ௏٘ܳ ੘ࢿ೧ঠ ೠ׮.
    Composeח Kotlin ௏٘݅ ੘ࢿೞݶ ػ׮.

    View Slide

  23. ✅ ݾ۾ഋ UI ҳࢿ
    recyclerView.layoutManager = ...


    recyclerView.adapter = ...


    // RecyclerView.Adapter(...)


    // RecyclerView.ViewHolder(@layout/item)


    recyclerView.addItemDecoration(


    DividerItemDecoration(...))
    @Composable


    fun Sample(dataSet: Array) {


    LazyColumn {


    items(dataSet.size) { index ->


    Text(text = dataSet[index])


    Divider()


    }


    }


    }
    Viewীࢲח ݾ۾ഋ UIܳ ҳࢿೡ ٸ


    ցޖ ݆਷ ௏٘ܳ ੘ࢿ೧ঠ ೠ׮.
    Composeח ׮ܲ composable ೣࣻ৬


    زੌೠ ߑߨਵ۽ औѱ ҳࢿೡ ࣻ ੓׮.

    View Slide

  24. • Viewܳ ݽف ઁѢೞҊ Compose݅ ࢎਊೞݶ, APK ௼ӝ৬ ࠽٘ दр੉ хࣗೠ׮.


    • Compose৬ Viewܳ ೣԋ ॳݶ, APK ௼ӝ৬ ࠽٘ दр੉ ডр ૐоೠ׮.


    • Composeীࢲب Viewܳ ࢎਊೡ ࣻ ੓׮. (AndroidView)


    • Java৬ XML۽ ੘ࢿػ ۨ੉ইਓ ௏٘ܳ Kotlinਵ۽ ੹ജೞח ࠺ਊ


    • View ਬ૑ࠁࣻ ࠺ਊ vs Compose ೟ण ࠺ਊ


    • View৬ ࠺तೞѱ AOT ஹ౵ੌਸ ૑ਗೠ׮. (ProfileInstaller)
    ⚠ ࠺Үೞӝী ઑӘ গݒೠ ٜࠗ࠙

    View Slide

  25. • min sdk ઁডࢎ೦੉ ੓׮. (API 21+)


    • (Java/Kotlinҗ ׳ܻ) View৬ Composeח ࢲ۽ ߸ജغ૑ ঋח׮.


    • Material Theme݅ ӝࠄਵ۽ ૑ਗೠ׮.


    • ۨ੉ইਓ XML ؀࠺ Preview ࢿמ੉ ڄযઉࢲ ࢤ࢑ࢿ੉ ڄয૓׮.


    • ۨ੉ইਓ XMLਸ ࢎਊೡ ٸࠁ׮ ܻ࠭ೡ ௏٘о טযդ׮.


    • ૑ਗೞח 3rd party ۄ੉࠳۞ܻо ੸׮.
    ❌ Viewࠁ׮ Composeীࢲ ইए਍ Ѫ?

    View Slide

  26. Composeী হח Ѫ
    02
    (ҳ࠙: ✅ ੓਺, ⚠ ੌࠗ হ਺, ❌ হ਺)

    View Slide

  27. Link: https://jetpackcompose.app/What-is-the-equivalent-of-TextView-in-Jetpack-Compose

    View Slide

  28. ⚠ Theme


    android:theme="@style/Theme.Sample">




    parent="Theme.MaterialComponents">


    @color/red





    Theme.AppCompat


    android:Theme.DeviceDefault
    SampleTheme { … }


    @Composable


    fun SampleTheme(content: @Composable() () -> Unit) {


    MaterialTheme(


    colors = lightColors(primary = Red, ...),


    typography = Typography,


    shapes = Shapes,


    content = content


    )


    }


    // X


    // X
    ӒܻҊ


    ӝઓ ೐۽ં౟ী Composeܳ ੸ਊೞݶ


    Themeо ف ߥ੉ ػ׮. (XML, Kotlin)

    View Slide

  29. ✅ Theme: MDC-Android Compose Theme Adapter
    Link: https://github.com/material-components/material-components-android-compose-theme-adapter
    @Composable


    fun SampleTheme(content: @Composable() () -> Unit) {


    MdcTheme(content = content)


    }
    LocalContext.current੄ themeܳ ӝ߈ਵ۽


    MaterialTheme composable੄ ࣘࢿਸ ҳࢿ೧ળ׮.

    View Slide

  30. Theme.AppCompat
    android:Theme.DeviceDefault
    ૑Ә MDC Themeܳ ࢎਊೞҊ ੓૑ ঋ׮ݶ? 🤔

    View Slide

  31. ⚠ Theme: AppCompat Compose Theme Adapter
    Link: https://github.com/google/accompanist/tree/main/appcompat-theme
    AppCompat Themeܳ ࢎਊೞҊ ੓ਸ ٸ,


    ࢎਊೡ ࣻ ੓ח ۄ੉࠳۞ܻ. (Accompanist)


    MaterialTheme composable੄ ࣘࢿਸ ҳࢿ೧ળ׮.


    ೞ૑݅ MDC Themeо ؊ ݆਷ ࣘࢿ ыҊ ੓׮.
    @Composable


    fun SampleTheme(content: @Composable() () -> Unit) {


    AppCompatTheme(content = content)


    }

    View Slide

  32. Link: https://material.io/blog/migrate-android-material-components
    ૑Ә੄ ٣੗ੋ दझమ੉


    MDC Theme۽ ҳഅ оמೞ׮ݶ


    ੹ജਸ Ҋ۰೧ࠁח Ѫ੉ જਸ ٠.

    View Slide

  33. Custom Theme
    Link: https://github.com/android/compose-samples/tree/main/Jetsnack
    ӝࠄ Themeী ৈ۞о૑ ࣘࢿਸ ୶оೞח ١


    ߹ب੄ ٣੗ੋ दझమਸ ҳ୷ೞҊ ੓׮ݶ,


    Custom Themeܳ ૒੽ ҳഅೞח Ѫب Ҋ۰೧ࠅ ࣻ ੓׮.


    ׮݅ compose-materialী ੓ח ݆ࣻ਷ ஹನք౟ٜਸ


    Ӓ؀۽ ࢎਊೞӝ য۰ਕ૕ ࣻ ੓׮.


    पઁ ҳഅೞח ߑߨ਷


    উ٘۽੉٘ ҕध ࢠ೒ ೐۽ં౟ Jetsnackਸ ଵҊ೧ࠁ੗.

    View Slide

  34. ⚠ Resources
    context.getString(R.string.message)


    context.resources.getDimensionPixelSize(R.dimen.size)


    ContextCompat.getColor(context, R.color.red)


    ContextCompat.getColorStateList(context, R.color.red)


    ContextCompat.getDrawable(context, ...)


    R.drawable.bitmap


    R.drawable.vector


    R.drawable.shape //


    R.drawable.selector //


    R.drawable.asld //


    R.drawable.nine_patch // 9-patch


    context.resources.getInteger(R.integer.value)


    context.resources.getBoolean(R.bool.value)
    stringResource(R.string.message)


    dimensionResource(R.dimen.size)


    colorResource(R.color.black)


    // X


    painterResource(R.drawable.bitmap)


    painterResource(R.drawable.vector)


    // X


    // X


    // X, b/168848337


    // X, b/193907127


    integerResource(R.integer.value)


    booleanResource(R.bool.value)

    View Slide

  35. ImageView TextView
    Button
    ImageButton
    RecyclerView
    EditText
    ProgressBar
    SeekBar
    Space
    CheckBox
    WebView
    LinearLayout
    FrameLayout RelativeLayout
    CardView
    ConstraintLayout
    ScrollView ListView
    GridView
    TableLayout
    View

    View Slide

  36. View
    ImageView TextView
    Button
    ImageButton
    RecyclerView
    EditText
    ProgressBar
    SeekBar
    Space
    CheckBox
    WebView
    LinearLayout
    FrameLayout RelativeLayout
    CardView
    ConstraintLayout
    ScrollView ListView
    GridView
    TableLayout
    Widget

    View Slide

  37. View Compose
    Image
    Button + Text
    Button
    ImageView
    Slider
    CheckBox
    CircularProgressIndicator
    Text
    TextView

    ProgressBar
    SeekBar
    Spacer
    Space

    TextureView

    SurfaceView
    WebView
    IconButton + Image
    ImageButton
    EditText TextField
    View Compose
    CheckedTextView
    Checkbox + Text
    IconToggleButton
    -
    LinearProgressIndicator
    -

    Switch Switch + Text
    RadioButton RadioButton + Text

    ViewStub

    View Slide

  38. ⚠ View


    android:layout_width="match_parent"


    android:layout_height="50dp"


    android:layout_margin="4dp"


    android:padding="8dp"


    android:background="@color/red"


    android:foreground="@color/blue"


    android:clipToOutline="true"


    android:elevation="4dp"


    android:contentDescription="view"


    android:visibility="invisible"


    />
    Box(Modifier


    .fillMaxWidth()


    .height(50.dp)


    // X, paddingਵ۽ ؀୓


    .padding(8.dp)


    .background(Color.Red)


    // X, Box۽ ؀୓


    .clipToBounds()


    .graphicsLayer(shadowElevation =


    with(LocalDensity.current) { 5.dp.toPx() })


    .semantics { contentDescription = "view" }


    // X, b/158837937


    )

    View Slide

  39. ✅ View: Listener
    view.setOnClickListener { ... }


    view.setOnLongClickListener { ... }


    view.setOnDragListener { view, dragEvent ->


    ...


    }


    view.setOnTouchListener { view, motionEvent ->


    ...


    }


    view.setOnKeyListener { view, i, keyEvent ->


    ...


    }
    Box(Modifier.clickable { ... })


    Box(Modifier.combinedClickable( // experimental


    onClick = { ... },


    onLongClick = { ... })


    )


    Box(Modifier.pointerInput(key) {


    detectDragGestures { change, dragAmount -> ... }}


    )


    Box(Modifier.pointerInteropFilter( // experimental


    onTouchEvent = { motionEvent -> ... }


    ))


    Box(Modifier.onKeyEvent(


    onKeyEvent = { keyEvent -> ... }


    ))

    View Slide

  40. ⚠ TextView


    android:text="text"


    android:textColor="@color/red"


    android:textSize="12sp"


    android:textStyle="bold"


    android:fontFamily="sans-serif"


    android:maxLines="1"


    android:ellipsize="end"


    android:autoSizeTextType="uniform"


    android:includeFontPadding="false"


    android:autofillHints="emailAddress"


    android:drawableStart="@drawable/icon"


    android:textIsSelectable="true" />



    Text(


    text = "text",


    color = Color.Red,


    fontSize = 12.sp,


    fontWeight = FontWeight.Bold,


    fontFamily = FontFamily.SansSerif,


    maxLines = 1,


    overflow = TextOverflow.Ellipsis,


    // X


    // X, b/171394808


    // X, b/176949051, Unofficial Blog ଵҊ


    )


    Row { Icon(painterResource(id = R.drawable.icon))


    Text("text") }


    SelectionContainer { Text("text") }


    // X, b/139326806

    View Slide

  41. ✅ EditText
    editText.doOnTextChanged {


    text, start, count, after -> …


    }




    android:hint="hint"


    android:inputType="number"


    android:imeOptions="actionDone" />


    editText.setOnEditorActionListener {


    textView, i, keyEvent -> …


    }
    TextField(


    value = text,


    onValueChange = { text -> ... },


    placeholder = { Text("hint") },


    keyboardOptions = KeyboardOptions(


    keyboardType = KeyboardType.Number,


    imeAction = ImeAction.Done


    ),


    keyboardActions = KeyboardActions(onDone = { ... }),


    )

    View Slide

  42. ✅ Button


    android:text="text" />


    @style/Widget.MaterialComponents.Button"


    @style/Widget.MaterialComponents.Button.UnelevatedButton"


    @style/Widget.MaterialComponents.Button.OutlinedButton"


    @style/Widget.MaterialComponents.Button.TextButton"
    Button(onClick = { ... }) {


    Text("text")


    }


    Button {}


    Button(elevation = null) {}


    OutlinedButton {}


    TextButton {}

    View Slide

  43. ⚠ ImageView


    android:src="@drawable/icon"


    android:tint="@color/red"


    android:scaleType="centerInside"


    android:adjustViewBounds="true"


    />
    Image(


    painter = painterResource(id = R.drawable.icon),


    colorFilter = ColorFilter.tint(Color.Red),


    contentScale = ContentScale.Inside,


    // X, b/196358649


    )

    View Slide

  44. ✅ ImageButton








    android:drawable="@drawable/pressed" />










    android:src="@drawable/selector_icon" />
    val source = remember { MutableInteractionSource() }


    val pressed by source.collectIsPressedAsState()


    val iconResId = if (pressed) {


    R.drawable.pressed


    } else {


    R.drawable.normal


    }


    IconButton(


    onClick = { ... },


    interactionSource = source


    ) {


    Icon(painterResource(id = iconResId))


    }

    View Slide

  45. ✅ CheckBox


    android:checked="true"


    android:buttonTint="@color/red" />


    checkBox.setOnCheckedChangeListener {


    compoundButton, checked -> ...


    }
    Checkbox(


    checked = true,


    colors = CheckboxDefaults.colors(


    checkedColor = Color.Red


    ),


    onCheckedChange = { checked -> ... },


    )

    View Slide

  46. ⚠ ProgressBar


    style="@style/Widget.AppCompat.ProgressBar"


    android:indeterminate="true" />




    style="@style/Widget.AppCompat.ProgressBar.Horizontal"


    android:indeterminate="true" />




    style="@style/Widget.AppCompat.ProgressBar.Horizontal" />


    android:min="0"


    android:max="100"


    android:progress="1"


    android:secondaryProgress="10" />
    CircularProgressIndicator()


    LinearProgressIndicator()


    LinearProgressIndicator(


    // min: 0f


    // max: 1f


    progress = 0.01f,


    // X


    )

    View Slide

  47. ⚠ SeekBar


    android:min="0"


    android:max="100"


    android:progress="1"


    android:secondaryProgress="10"


    android:thumb="@drawable/thumb" />


    seekBar.setOnSeekBarChangeListener(


    object : SeekBar.OnSeekBarChangeListener {


    override fun onProgressChanged(


    seekBar: SeekBar,


    progress: Int, fromUser: Boolean


    ) {


    ...


    }


    })
    Slider(


    valueRange = 0f..100f,


    steps = 1,


    value = 1f,


    // X


    // X, b/195042785


    onValueChange = { offset -> ... },


    )

    View Slide

  48. ⚠ Custom View: Canvas
    class CanvasView(...) : View(...) {


    override fun draw(canvas: Canvas) {


    super.draw(canvas)


    canvas.withTranslation(x = width / 5f) {


    withRotation(degrees = 45F) {


    drawRect(...)


    }


    }


    drawable.draw(canvas)


    }


    }
    Canvas(modifier = Modifier.fillMaxSize()) {


    val canvasSize = size


    val canvasWidth = size.width


    val canvasHeight = size.height


    withTransform({


    translate(left = canvasWidth / 5F)


    rotate(degrees = 45F)


    }) {


    drawRect(...)


    }


    drawIntoCanvas { it ->


    drawable.draw(it.nativeCanvas)


    }


    }

    View Slide

  49. View
    ImageView TextView
    Button
    ImageButton
    RecyclerView
    EditText
    ProgressBar
    SeekBar
    Space
    CheckBox
    WebView
    LinearLayout
    FrameLayout RelativeLayout
    CardView
    ConstraintLayout
    ScrollView ListView
    GridView
    TableLayout
    ViewGroup

    View Slide

  50. Column
    LazyColumn
    RecyclerView
    LinearLayout
    ModalDrawer
    ViewPager
    Modifier.scrollable()
    ScrollView
    ConstraintLayout
    BottomNavigationView

    RelativeLayout
    LazyRow
    Toolbar
    Box
    FrameLayout
    DrawerLayout

    AppBar TopAppBar
    Row
    ConstraintLayout
    View Compose View Compose

    -
    -
    BottomNavigation
    (BottomSheet)
    Scaffold
    CoordinatorLayout SwipeRefreshLayout ❌
    b/194911951

    View Slide

  51. ✅ FrameLayout








    android:layout_gravity="center" />



    Box {


    Image()


    Text(text = "text",


    modifier = Modifier.align(Alignment.Center))


    }


    Box(contentAlignment = Alignment.Center) { ... }

    View Slide

  52. ✅ LinearLayout


    android:orientation="vertical"


    android:gravity="center">













    android:orientation="horizontal"


    android:gravity="center">









    Column(


    horizontalAlignment = Alignment.CenterHorizontally,


    verticalArrangement = Arrangement.Center


    ) {


    Image(modifier = Modifier.weight(1f))


    Text()


    }


    Row(


    verticalAlignment = Alignment.CenterVertically,


    horizontalArrangement = Arrangement.Center


    ) {


    Image()


    Text()


    }

    View Slide

  53. ✅ ScrollView
















    Column(


    Modifier.verticalScroll(rememberScrollState())


    ) { ... }


    Row(


    Modifier.horizontalScroll(rememberScrollState())


    ) { ... }

    View Slide

  54. ⚠ RecyclerView
    val adapter = CustomAdapter(...)


    recyclerView.adapter = adapter


    adapter.submitList(dataSet)


    recyclerView.addItemDecoration(


    DividerItemDecoration(...))


    recyclerView.itemAnimator = ...


    app:fastScrollEnabled="true"
    @Composable


    fun Sample(dataSet: Array) {


    LazyColumn {


    items(dataSet.size) { index ->


    Text(text = dataSet[index])


    Divider()


    }


    }


    }


    // X, b/188855907, Divider۽ ؀୓ оמ


    // X, b/150812265


    // X, b/182393097

    View Slide

  55. ⚠ RecyclerView: LayoutManager


    android:orientation="vertical"


    app:layoutManager="...LinearLayoutManager"


    app:reverseLayout="false" />


    android:orientation="horizontal"


    app:layoutManager="...LinearLayoutManager" />


    android:orientation="vertical"


    app:layoutManager="...GridLayoutManager"


    app:spanCount="3" />


    android:orientation="horizontal"


    app:layoutManager="...GridLayoutManager" />


    android:orientation="vertical"


    app:layoutManager="...StaggeredGridLayoutManager" />
    LazyColumn(


    reverseLayout = false


    ) { ... }


    LazyRow { ... }


    LazyVerticalGrid( // experimental


    cells = GridCells.Fixed(3)


    ) { ... }


    // X, b/191238807


    // X, b/190787900

    View Slide

  56. ✅ Card


    app:cardCornerRadius="4dp"


    app:cardBackgroundColor="@color/white"


    app:cardElevation="8dp">


    ...



    Card(


    shape = RoundedCornerShape(4.dp),


    backgroundColor = Color.White,


    elevation = 8.dp


    ) {


    ...


    }

    View Slide

  57. ✅ CoordinatorLayout








    "@string/appbar_scrolling_view_behavior"/>









    Scaffold(


    topBar = { TopAppBar { ... } },


    content = {


    },


    bottomBar = { BottomNavigation(...) },


    floatingActionButton = { FloatingActionButton { ... } }


    )


    // X, b/194911953
    Tooltip

    View Slide

  58. ⚠ Toast, Snackbar, Dialog, Fragment

    View Slide

  59. ❌ Toast
    Toast.makeText(


    context, "message", Toast.LENGTH_SHORT


    ).show()
    // X, ইې୊ۢ ࢎਊ оמ


    Toast.makeText(


    LocalContext.current, "message", Toast.LENGTH_SHORT


    ).show()

    View Slide

  60. ✅ Snackbar
    Snackbar


    .make(


    view, "Snackbar Message", Snackbar.LENGTH_SHORT


    )


    .setAction("Action") { /* onActionPerformed */ }


    .addCallback(object : Snackbar.Callback() {


    override fun onDismissed(


    transientBottomBar: Snackbar, event: Int


    ) { ... }


    })


    .show()
    val scaffoldState = rememberScaffoldState()


    val scope = rememberCoroutineScope()


    Scaffold(scaffoldState = scaffoldState) {


    Button(onClick = {


    scope.launch {


    val result = scaffoldState.snackbarHostState


    .showSnackbar(


    message = "Snackbar Message",


    actionLabel = "Action"


    )


    when (result) {


    SnackbarResult.ActionPerformed -> { ... }


    SnackbarResult.Dismissed -> { ... }


    }


    }


    }) { ... }


    }

    View Slide

  61. ⚠ Dialog
    AlertDialog.Builder(context)


    .setTitle("title")


    .setMessage("message")


    .setPositiveButton("") { ... }


    .setNegativeButton("") { ... }


    .show()


    // Additional Functions
    var isDialogShown by remember { mutableStateOf(true) }


    if (isDialogShown) {


    AlertDialog(


    title = { Text("title") },


    text = { Text("message") },


    confirmButton = { TextButton(onClick = {}) { ... }},


    dismissButton = { TextButton(onClick = {}) { ... }},


    onDismissRequest = { isDialogShown = false },


    )


    }


    // X, b/194911954

    View Slide

  62. ❌ Fragment
    override fun onSaveInstanceState(outState: Bundle) {


    super.onSaveInstanceState(outState)


    outState.putString(KEY, value)


    }


    override fun onCreate(savedInstanceState: Bundle?) {


    super.onCreate(savedInstanceState)


    value = savedInstanceState?.getString(KEY)


    ?: viewModel.generateValue()


    }


    // Multiple back stacks


    fragmentManager.saveBackStack("notifications")


    fragmentManager.restoreBackStack("profile")
    val value = rememberSaveable { // Null X, b/196995035


    mutableStateOf(viewModel.generateValue())


    }


    val current = ... // "notifications" -> "profile"


    val saveableStateHolder = rememberSaveableStateHolder()


    saveableStateHolder.SaveableStateProvider(current.route) {


    when (current) {


    Profile -> Profile()


    Notifications -> Notifications()


    }


    }

    View Slide

  63. ⚠ Interpolator, Animation, Transition

    View Slide

  64. ⚠ Interpolator
    interface TimeInterpolator


    FastOutSlowInInterpolator()


    FastOutLinearInInterpolator()


    LinearOutSlowInInterpolator()


    LinearInterpolator()


    PathInterpolator(0.33f, 0f) // Quad


    PathInterpolator(0.33f, 0f, 1f, 1f) // Cubic


    PathInterpolator((Path().apply {...}) // Path


    AccelerateInterpolator()


    OvershootInterpolator()


    ...
    interface Easing


    FastOutSlowInEasing


    FastOutLinearInEasing


    LinearOutSlowInEasing


    LinearEasing


    // X, b/168854254


    CubicBezierEasing(0.33f, 0f, 1f, 1f)


    // X, b/168854254


    // X


    // X


    // ...

    View Slide

  65. ✅ ViewPropertyAnimator
    val fraction: Float = if (enabled) 1f else 0f


    view.animate().cancel()


    view.animate()


    .setStartDelay(300L) // ms


    .setDuration(1000L) // ms


    .setInterpolator(FastOutSlowInInterpolator())


    .translationX(lerp(0f, 100f, fraction))


    .translationY(lerp(0f, 50f, fraction))


    .rotationX(lerp(0f, 90f, fraction))


    .rotationY(lerp(0f, 90f, fraction))


    .scaleX(lerp(1f, 1.5f, fraction))


    .scaleY(lerp(1f, 0.5f, fraction))


    .alpha(lerp(1f, 0f, fraction))


    .withLayer()
    val fraction: Float by animateFloatAsState(


    targetValue = if (enabled) 1f else 0f,


    animationSpec = tween(


    delayMillis = 300,


    durationMillis = 1000,


    easing = FastOutSlowInEasing))


    Box(modifier = Modifier.graphicsLayer {


    translationX = lerp(0f, 100f, fraction)


    translationY = lerp(0f, 50f, fraction)


    rotationX = lerp(0f, 90f, fraction)


    rotationY = lerp(0f, 90f, fraction)


    scaleX = lerp(1f, 1.5f, fraction)


    scaleY = lerp(1f, 0.5f, fraction)


    alpha = lerp(1f, 0f, fraction)


    // ೞ٘ਝয оࣘ੉ ӝࠄ, b/193123882


    })

    View Slide

  66. ✅ DynamicAnimation
    val spring = SpringAnimation(


    view,


    DynamicAnimation.TRANSLATION_X


    ).withSpringForceProperties {


    dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY


    stiffness = SpringForce.STIFFNESS_LOW


    }


    val targetValue = if (enabled) 100f else 0f


    spring.animateToFinalPosition(targetValue)
    val fraction: Float by animateFloatAsState(


    targetValue = if (enabled) 1f else 0f,


    animationSpec = spring(


    dampingRatio = Spring.DampingRatioLowBouncy,


    stiffness = Spring.StiffnessLow


    )


    )


    Box(modifier = Modifier.graphicsLayer {


    translationX = lerp(0f, 100f, fraction)


    })

    View Slide

  67. ✅ ValueAnimator: Infinite Animation
    ValueAnimator.ofArgb(


    Color.RED,


    Color.GREEN


    ).apply {


    repeatCount = ValueAnimator.INFINITE


    repeatMode = ValueAnimator.REVERSE


    duration = 1_000L


    interpolator = LinearInterpolator()


    addUpdateListener {


    val color = it.animatedValue as Int


    view.setBackgroundColor(color)


    }


    }.start()
    val infiniteTransition = rememberInfiniteTransition()


    val color by infiniteTransition.animateColor(


    initialValue = Color.Red,


    targetValue = Color.Green,


    animationSpec = infiniteRepeatable(


    repeatMode = RepeatMode.Reverse


    animation = tween(


    durationMillis = 1000,


    easing = LinearEasing),


    )


    )


    Box(Modifier.background(color))

    View Slide

  68. ⚠ Transition
    // FragmentA.kt


    override fun onCreate(savedInstanceState: Bundle?) {


    super.onCreate(savedInstanceState)


    exitTransition = MaterialFadeThrough()


    }


    // FragmentB.kt


    override fun onCreate(savedInstanceState: Bundle?) {


    super.onCreate(savedInstanceState)


    enterTransition = MaterialFadeThrough()


    }


    supportFragmentManager


    .beginTransaction()


    .replace(R.id.fragment_container, FragmentB())


    .commit()
    val screen = mutableStateOf("A")


    AnimatedContent(


    targetState = screen,


    transitionSpec = {


    fadeIn() + scaleIn() with // 1.1ࠗఠ, b/191325593


    fadeOut()


    }


    ) { currentScreen ->


    when (currentScreen) {


    "A" -> ScreenA()


    "B" -> ScreenB()


    }


    }


    screen.value = "B"

    View Slide

  69. ⚠ Transition
    // ViewGroup੄ Child View ݽفী ੸ਊػ׮


    TransitionManager.beginDelayedTransition(


    viewGroup, AutoTransition())


    child.isVisible = show


    AutoTransition


    =




    android:transitionOrdering="sequential">












    Box {


    // пп ѐ߹੸ਵ۽ ੸ਊ೧ঠ ೠ׮


    AnimatedVisibility(


    visible = show,


    enter = fadeIn() + expandIn(), // EnterTransition


    exit = shrinkOut() + fadeOut() // ExitTransition


    ) { Text(text = "child") }


    }


    // X, b/142451118

    View Slide

  70. ❌ View Animation
    fragmentManager.beginTransaction()


    .setCustomAnimations(


    R.anim.slide_in_right, // enterAnim


    R.anim.slide_out_left, // exitAnim


    )


    .replace(...)


    .commit()


















    // X


    // X


    // X


    // X


    // X

    View Slide

  71. ❌ Shared Elements
    fragmentManager.beginTransaction()


    .addSharedElement(view, "transitionName")


    .replace(...)


    .setReorderingAllowed(true)


    .commit()


    view.transitionName = "transition_name"


    class FragmentB : Fragment() {


    override fun onCreate(...) {


    super.onCreate(savedInstanceState)


    sharedElementEnterTransition = ...


    sharedElementReturnTransition = ...


    }


    }
    // X

    View Slide

  72. Compose۽੄ ੹ജਸ ળ࠺ೞӝ
    03

    View Slide

  73. Jetpack ۄ੉࠳۞ܻ
    ✅ ❌
    Activity ViewPager
    ViewPager2
    Preference
    Navigation
    Hilt ViewModel
    Paging Emoji
    ConstraintLayout
    Wear
    Link: https://developer.android.com/jetpack/compose/libraries

    View Slide

  74. Jetpack ۄ੉࠳۞ܻ: ConstraintLayout
    ConstraintLayout {


    val (button, text) = createRefs()


    Button(


    onClick = {},


    modifier = Modifier.constrainAs(button) {


    top.linkTo(parent.top, margin = 16.dp)


    }


    ) { Text("Button") }


    Text("Text", Modifier.constrainAs(text) {


    top.linkTo(button.bottom, margin = 16.dp)


    })


    }
    Link: https://developer.android.com/jetpack/compose/layouts/constraintlayout
    ConstraintLayout਷ ۨ੉ইਓ ઺୏ਵ۽ ੋೠ ࢿמ ੷ೞܳ ѐࢶೞ۰ח


    ݾ੸ਵ۽ ୶оغয, ۨ੉ইਓਸ ҳࢿೡ ٸ ࢎਊਸ ӂ੢ೞҊ ੓঻׮.


    Jetpack Composeীࢲח


    ۨ੉ইਓ੉ ઺୏غ؊ۄب ࢿמী ௼ѱ ৔ೱਸ ઱૑ ঋਵ޲۽,


    ConstraintLayout ؀न ઺୏ػ ۨ੉ইਓਵ۽ ҳࢿೞח Ѫ੉


    ѐߊ ࢤ࢑ࢿҗ ࢿמ ݶীࢲ ؊ աਸ ࣻب ੓׮.


    MotionLayout਷ ই૒ ૑ਗೞ૑ ঋח׮.

    View Slide

  75. ৻ࠗ ۄ੉࠳۞ܻ
    ✅ ❌
    PhotoView
    Coil
    Glide
    Lottie
    Picasso ExoPlayer
    Accompanist
    FlexboxLayout

    View Slide

  76. Coil
    val painter = rememberImagePainter(


    data = "https://www.example.com/image.jpg",


    builder = { crossfade(true) }


    )


    Image(


    painter = painter,


    contentDescription = null,


    modifier = Modifier.size(128.dp)


    )


    when (painter.state) {


    is ImagePainter.State.Loading -> { ... }


    is ImagePainter.State.Success -> { ... }


    is ImagePainter.State.Error -> { ... }


    }
    Link: https://coil-kt.github.io/coil/compose/
    Kotlin Coroutines ӝ߈੄ Android ੉޷૑ ۽٬ ۄ੉࠳۞ܻ.


    ׮ܲ ੉޷૑ ۽٬ ۄ੉࠳۞ܻח Jetpack Compose ૑ਗ ҅ദ੉ হ׮.


    ౠ൤ Picasso(#2203)ח ؊੉࢚ ӝמਸ ୶оೞ૑ ঋਸ Ѫਵ۽ ࠁੋ׮.


    Glide(#4459), Fresco ۄ੉࠳۞ܻܳ ࢎਊೞח ೐۽ં౟ীࢲח


    Coil۽ migrationೞѢա (Migration о੉٘ ޙࢲ)


    Landscapist ۄ੉࠳۞ܻܳ ؀উਵ۽ Ѩష೧ࠅ ࣻب ੓ਸ Ѫ э׮.

    View Slide

  77. Accompanist
    ѐߊೡ ٸ ੌ߈੸ਵ۽ ೙ਃೞ૑݅


    Jetpack Composeীࢲח ই૒ ࢎਊೡ ࣻ হח ӝמٜਸ ઁҕೞח ۄ੉࠳۞ܻ.


    ୶റ Composeীࢲ ӝמਸ ҕध੸ਵ۽ ૑ਗೞѢա,


    ৻ࠗ ۄ੉࠳۞ܻীࢲ ؀਽ೞח ҃਋ীח ۄ੉࠳۞ܻо ઁѢؼ ࣻ ੓׮.


    (ex. ୭Ӕ ੉޷૑ ۽٬ ۄ੉࠳۞ܻо ઁѢغ঻׮.)
    Link: https://github.com/google/accompanist
    • Coil


    • Glide


    • Insets


    • Permissions


    • Navigation Animation


    • Navigation Material


    • AppCompat Theme


    • Drawable Painter


    • System UI Controller


    • Swipe Refresh


    • Pager layouts


    • Flow layouts


    • Placeholder

    View Slide

  78. @Composable


    fun DrawDrawable() {


    val context = LocalContext.current


    val drawable = ContextCompat.getDrawable(


    LocalContext.current,


    R.drawable.shape //


    )


    Image(


    painter = rememberDrawablePainter(drawable),


    contentDescription = null


    )


    }
    Accompanist: Drawable Painter
    painterResource() ח


    BitmapDrawable ژח VectorDrawable݅ ૑ਗೠ׮.


    ӝઓী ࢎਊೞ؍ ৬ э਷ Drawableਸ


    ࢎਊೡ ࣻ ੓ѱ ೧઱ח ۄ੉࠳۞ܻ.


    ׮݅ , ୊ۢ


    ࢚కী ٮۄ ੉޷૑о ׳ۄ૑ח Drawable਷ ૑ਗغ૑ ঋח׮.

    View Slide

  79. Accompanist: Swipe Refresh
    val viewModel: MyViewModel = viewModel()


    val isRefreshing by viewModel.isRefreshing.collectAsState()


    SwipeRefresh(


    state = rememberSwipeRefreshState(isRefreshing),


    onRefresh = { viewModel.refresh() },


    ) {


    // list


    }
    SwipeRefreshLayoutਸ ؀୓ೞח ۄ੉࠳۞ܻ.


    View Slide

  80. Accompanist: Pager layouts
    // Display 10 items


    val pagerState = rememberPagerState(pageCount = 10)


    HorizontalPager(state = pagerState) { page ->


    Text(text = "Page: $page")


    }


    VerticalPager(state = pagerState) { page ->


    Text(text = "Page: $page")


    }
    ViewPagerܳ ؀୓ೞח ۄ੉࠳۞ܻ.


    View Slide

  81. Accompanist: Placeholder
    Text(


    text = "Content to display after content has loaded",


    modifier = Modifier


    .placeholder(


    visible = true,


    highlight = PlaceholderHighlight.fade(),


    )


    )
    Placeholder ӝמਸ ઁҕೞח ۄ੉࠳۞ܻ.


    View Slide

  82. ੼૓੸ਵ۽ ੸ਊೞӝ
    • ӝઓ জী ੼૓੸ਵ۽ Compose ੸ਊೞӝ

    https://developer.android.com/jetpack/compose/interop

    • Architecture

    https://developer.android.com/jetpack/compose/architecture

    • Accessibility

    https://developer.android.com/jetpack/compose/accessibility

    • Testing

    https://developer.android.com/jetpack/compose/testing

    • List of modifiers

    https://developer.android.com/jetpack/compose/modifiers-list

    View Slide

  83. Issue Tracker: ੉ग ١۾ೞӝ / Star ־ܰӝ
    Link: https://issuetracker.google.com/issues?q=componentid:610764
    componentid:


    856989 → foundation


    742043 → material


    610764 → compiler / runtime


    610478 → ui / animation

    View Slide

  84. Issue Tracker: ੉ग ١۾ೞӝ / Star ־ܰӝ
    👆

    View Slide

  85. Summary
    • ✅ ؊ ੸Ҋ ૒ҙ੸ੋ ௏٘۽ UIܳ ѐߊೡ ࣻ ੓׮.

    • 📚 ׮নೠ ҕध ޙࢲ৬ ௏٘ەਸ ా೧ ઑӘঀ ೟ण೧ࠁ੗.

    • ⚠ ੗઱ ࢎਊೞҊ ੓ח View API৬ زੌೠ APIо ઁҕغח૑ ԙ ഛੋ೧ࠁ੗.

    • 🚀 APIо হਵݶ Jetpack Compose Roadmapী ӝੑغয ੓ח૑ ଵҊ೧ࠁ੗.

    • 👾 ӝמ/ࢸ҅ য়ܨܳ ߊѼೞݶ, IssueTrackerী ੉गܳ թѹࠁ੗. (زੌೠ ੉गо ੓ਵݶ ⭐ ܳ ־ܰ੗.)

    • 💬 п੗ Compose ੸ਊೞӝ ੸׼ೠ द੼ਸ Ҋ޹೧ࠁ੗.

    View Slide

  86. Sungyong An


    Android Developer


    @fornewid
    Thank you!
    Slide: https://speakerdeck.com/fornewid/jetpack-compose-having-and-not

    View Slide