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

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. 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
  2. mordern UI toolkit ௏٘ хࣗ ૒ҙ੸ declarative UI ъ۱ೠ ࢿמ

    ѐߊ ࣘب ೱ࢚ Kotlin APIs ҕध ѐߊ ޙࢲীࢲח ৈ۞ ੢੼ٜ੉ աৌغয ੓૑݅, ѐߊ੗݃׮ ׮ܲ ࢤпਸ ೞҊ ੓ਸ Ѫ э׮.
  3. M V P M V VM M V I M

    V C ӝઓ੄ Jetpack ۄ੉࠳۞ܻ৬ ׮ܰѱ Jetpack Composeח জ .
  4. 02 01 03 ৵ Composeܳ ࢎਊ೧ঠ ೡө Composeী হח Ѫ

    Compose۽੄ ੹ജਸ ળ࠺ೞӝ ݾର
  5. ୶ୌ ৔࢚ (ҕध) Understanding Compose AndroidDevSummit '19 I/O '19 Declarative

    UI patterns Thinking in Compose 11 Weeks of Android Compose੄ ӝࠄ ѐ֛ী ؀೧ࢲח ׮ܖ૑ ঋणפ׮. ҙ۲೧ࢲח ҕध ৔࢚ द୒ਸ ୶ୌ೤פ׮.
  6. ୶ୌ ৔࢚ (ೠҴয) Jetpack Compose: I/O 2021 Ӓ ੉റ I/O

    Extended Korea Android 2021 I/O Extended with doubleS Compose द੘ೞӝ ৢ೧ ೠҴয ࣁ࣌ب ੓঻ਵפ, ҙब ੓ਵन ٜ࠙਷ द୒೧ࠁࣁਃ.
  7. ✅ ೒ۖಬ ઙࣘࢿ ୭ࣗച Composeח Jetpack ۄ੉࠳۞ܻ۽ ߓನغয ೒ۖಬী ؏

    ઙࣘ੸੉׮. ೒ۖಬ Viewীח APIܳ ࢎਊೡ ࣻ ੓ח ୭ࣗ ߡ੹ ઁড੉ ੓׮. ੉ܳ ೧ࣗೞ۰Ҋ, Compat APIܳ ઁҕೞ૑݅ ೠ҅о ੓׮.
  8. ✅ ࢚ࣘਵ۽ ੋೠ ޙઁ ೧ࣗ 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 ೣٜࣻ۽ ҳࢿغয, ೙ਃೠ ӝמ݅ ੘ѱ ਬ૑ೡ ࣻ ੓׮.
  9. ✅ ઺୏ ۨ੉ইਓ ࢿמ <RelativeLayout> <ImageView /> <ImageView /> <RelativeLayout>

    <TextView /> <LinearLayout> <TextView /> <RelativeLayout> <EditText /> </RelativeLayout> </LinearLayout> <LinearLayout> <TextView /> <RelativeLayout> <EditText /> <ConstraintLayout> <ImageView /> <ImageView /> <TextView /> <EditText /> <TextView /> <TextView /> <EditText /> <Button /> <Button /> <TextView /> </ConstraintLayout> @Composable fun Sample() { Column { Image() Image() Column { Text() Column { Text() Box { TextField() } } Column { Text() Box { TextField() ViewGroupী ٮۄ measureܳ ৈ۞ߣ ࣻ೯ೡ ࣻ ੓যࢲ ୭؀ೠ ۨ੉ইਓ੉ ઺୏غ૑ ঋب۾ न҃ॄঠ ೮׮. Composeח measureܳ ೠߣ݅ ࣻ೯ೞب۾ ъઁغয ઺୏ ۨ੉ইਓীࢲب ࢿמ੉ ௼ѱ ੷ೞغ૑ ঋח׮.
  10. ✅ UI ੤ࢎਊࢿ <!-- titlebar.xml —> <merge> <FrameLayout> <ImageView />

    </FrameLayout> </merge> <LinearLayout> <include layout="@layout/titlebar"/> <TextView /> ... </LinearLayout> @Composable fun TitleBar() { Box { Image() } } @Composable fun Sample() { Column { TitleBar() Text() ... } } Viewח includeܳ ੉ਊೞৈ ۨ੉ইਓਸ ੤ࢎਊೡ ࣻ ੓׮. ઺୏ ࢿמ ޙઁ۽ mergeܳ Ҋ۰ೡ ࣻب ੓׮. Composeח ੤ࢎਊ੉ ೙ਃೠ ࠗ࠙ਸ ߹ѐ੄ composable ೣࣻ۽ ܻ࠙ೞݶ ػ׮.
  11. ✅ ૑ো ୡӝച <FrameLayout> <ViewStub 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" /> </FrameLayout> // 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ח ઑѤޙ ݅ਵ۽ب рױ൤ ҳഅೡ ࣻ ੓׮.
  12. ✅ 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<String> = ... fun onNameChanged(name: String) { ... } }
  13. ✅ ؘ੉ఠ ߄ੋ٬ @BindingAdapter("onTextChanged") fun EditText.onTextChanged(listener: (String) -> Unit){ doOnTextChanged

    { text, _, _, _ -> listener(text.toString()) } } <layout> <data> <variable name="viewModel" type="SampleViewModel" /> </data> <EditText android:id="@+id/name_edit_text" android:text="@{viewmodel.name}" android:onTextChanged="@{viewmodel.onNameChanged}" /> </layout> @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 ೣࣻ۽ ܻ࠙ೞݶ ػ׮.
  14. ✅ Custom View ҳഅ <declare-styleable name="AppCompatImageView"> <attr name="srcCompat" format="reference" />

    </declare-styleable> public class AppCompatImageView extends ImageView { public AppCompatImageView(...) { final int id = a.getResourceId( R.styleable.AppCompatImageView_srcCompat, -1); ... } } <androidx.appcompat.widget.AppCompatImageView 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 ௏٘݅ ੘ࢿೞݶ ػ׮.
  15. ✅ ݾ۾ഋ UI ҳࢿ recyclerView.layoutManager = ... recyclerView.adapter = ...

    // RecyclerView.Adapter(...) // RecyclerView.ViewHolder(@layout/item) recyclerView.addItemDecoration( DividerItemDecoration(...)) @Composable fun Sample(dataSet: Array<String>) { LazyColumn { items(dataSet.size) { index -> Text(text = dataSet[index]) Divider() } } } Viewীࢲח ݾ۾ഋ UIܳ ҳࢿೡ ٸ ցޖ ݆਷ ௏٘ܳ ੘ࢿ೧ঠ ೠ׮. Composeח ׮ܲ composable ೣࣻ৬ زੌೠ ߑߨਵ۽ औѱ ҳࢿೡ ࣻ ੓׮.
  16. • Viewܳ ݽف ઁѢೞҊ Compose݅ ࢎਊೞݶ, APK ௼ӝ৬ ࠽٘ दр੉

    хࣗೠ׮. • Compose৬ Viewܳ ೣԋ ॳݶ, APK ௼ӝ৬ ࠽٘ दр੉ ডр ૐоೠ׮. • Composeীࢲب Viewܳ ࢎਊೡ ࣻ ੓׮. (AndroidView) • Java৬ XML۽ ੘ࢿػ ۨ੉ইਓ ௏٘ܳ Kotlinਵ۽ ੹ജೞח ࠺ਊ • View ਬ૑ࠁࣻ ࠺ਊ vs Compose ೟ण ࠺ਊ • View৬ ࠺तೞѱ AOT ஹ౵ੌਸ ૑ਗೠ׮. (ProfileInstaller) ⚠ ࠺Үೞӝী ઑӘ গݒೠ ٜࠗ࠙
  17. • min sdk ઁডࢎ೦੉ ੓׮. (API 21+) • (Java/Kotlinҗ ׳ܻ)

    View৬ Composeח ࢲ۽ ߸ജغ૑ ঋח׮. • Material Theme݅ ӝࠄਵ۽ ૑ਗೠ׮. • ۨ੉ইਓ XML ؀࠺ Preview ࢿמ੉ ڄযઉࢲ ࢤ࢑ࢿ੉ ڄয૓׮. • ۨ੉ইਓ XMLਸ ࢎਊೡ ٸࠁ׮ ܻ࠭ೡ ௏٘о טযդ׮. • ૑ਗೞח 3rd party ۄ੉࠳۞ܻо ੸׮. ❌ Viewࠁ׮ Composeীࢲ ইए਍ Ѫ?
  18. ⚠ Theme <activity android:theme="@style/Theme.Sample"> <style name="Theme.Sample" parent="Theme.MaterialComponents"> <item name="colorPrimary">@color/red</item> </style>

    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)
  19. ✅ 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੄ ࣘࢿਸ ҳࢿ೧ળ׮.
  20. ⚠ 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) }
  21. Custom Theme Link: https://github.com/android/compose-samples/tree/main/Jetsnack ӝࠄ Themeী ৈ۞о૑ ࣘࢿਸ ୶оೞח ١

    ߹ب੄ ٣੗ੋ दझమਸ ҳ୷ೞҊ ੓׮ݶ, Custom Themeܳ ૒੽ ҳഅೞח Ѫب Ҋ۰೧ࠅ ࣻ ੓׮. ׮݅ compose-materialী ੓ח ݆ࣻ਷ ஹನք౟ٜਸ Ӓ؀۽ ࢎਊೞӝ য۰ਕ૕ ࣻ ੓׮. पઁ ҳഅೞח ߑߨ਷ উ٘۽੉٘ ҕध ࢠ೒ ೐۽ં౟ Jetsnackਸ ଵҊ೧ࠁ੗.
  22. ⚠ 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 // <shape> R.drawable.selector // <selector> R.drawable.asld // <animated-selector> 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)
  23. ImageView TextView Button ImageButton RecyclerView EditText ProgressBar SeekBar Space CheckBox

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

    CheckBox WebView LinearLayout FrameLayout RelativeLayout CardView ConstraintLayout ScrollView ListView GridView TableLayout Widget
  25. 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
  26. ⚠ View <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 )
  27. ✅ 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 -> ... } ))
  28. ⚠ TextView <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" /> <androidx.emoji.widget.EmojiTextView /> 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
  29. ✅ EditText editText.doOnTextChanged { text, start, count, after -> …

    } <EditText 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 = { ... }), )
  30. ⚠ ImageView <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 )
  31. ✅ ImageButton <!-- res/drawable/selector_icon.xml --> <selector> <item android:state_pressed="true" android:drawable="@drawable/pressed" />

    <item android:drawable="@drawable/normal" /> </selector> <ImageButton 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)) }
  32. ✅ CheckBox <CheckBox android:checked="true" android:buttonTint="@color/red" /> checkBox.setOnCheckedChangeListener { compoundButton, checked

    -> ... } Checkbox( checked = true, colors = CheckboxDefaults.colors( checkedColor = Color.Red ), onCheckedChange = { checked -> ... }, )
  33. ⚠ ProgressBar <ProgressBar style="@style/Widget.AppCompat.ProgressBar" android:indeterminate="true" /> <ProgressBar style="@style/Widget.AppCompat.ProgressBar.Horizontal" android:indeterminate="true" />

    <ProgressBar 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 )
  34. ⚠ SeekBar <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 -> ... }, )
  35. ⚠ 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) } }
  36. View ImageView TextView Button ImageButton RecyclerView EditText ProgressBar SeekBar Space

    CheckBox WebView LinearLayout FrameLayout RelativeLayout CardView ConstraintLayout ScrollView ListView GridView TableLayout ViewGroup
  37. 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 ❌
  38. ✅ FrameLayout <FrameLayout> <ImageView /> <TextView android:text="text" android:layout_gravity="center" /> </FrameLayout>

    Box { Image() Text(text = "text", modifier = Modifier.align(Alignment.Center)) } Box(contentAlignment = Alignment.Center) { ... }
  39. ✅ LinearLayout <LinearLayout android:orientation="vertical" android:gravity="center"> <ImageView android:layout_weight="1" /> <TextView />

    </LinearLayout> <LinearLayout android:orientation="horizontal" android:gravity="center"> <ImageView /> <TextView /> </LinearLayout> Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Image(modifier = Modifier.weight(1f)) Text() } Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Image() Text() }
  40. ✅ ScrollView <ScrollView> <LinearLayout android:orientation="vertical"/> </ScrollView> <HorizontalScrollView> <LinearLayout android:orientation="horizontal"/> </HorizontalScrollView>

    Column( Modifier.verticalScroll(rememberScrollState()) ) { ... } Row( Modifier.horizontalScroll(rememberScrollState()) ) { ... }
  41. ⚠ RecyclerView val adapter = CustomAdapter(...) recyclerView.adapter = adapter adapter.submitList(dataSet)

    recyclerView.addItemDecoration( DividerItemDecoration(...)) recyclerView.itemAnimator = ... app:fastScrollEnabled="true" @Composable fun Sample(dataSet: Array<String>) { LazyColumn { items(dataSet.size) { index -> Text(text = dataSet[index]) Divider() } } } // X, b/188855907, Divider۽ ؀୓ оמ // X, b/150812265 // X, b/182393097
  42. ⚠ RecyclerView: LayoutManager <androidx.recyclerview.widget.RecyclerView 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
  43. ✅ CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout> <AppBarLayout /> <LinearLayout app:layout_behavior= "@string/appbar_scrolling_view_behavior"/> <BottomNavigationView />

    <FloatingActionButton /> </androidx.coordinatorlayout.widget.CoordinatorLayout> Scaffold( topBar = { TopAppBar { ... } }, content = { }, bottomBar = { BottomNavigation(...) }, floatingActionButton = { FloatingActionButton { ... } } ) // X, b/194911953 Tooltip
  44. ❌ Toast Toast.makeText( context, "message", Toast.LENGTH_SHORT ).show() // X, ইې୊ۢ

    ࢎਊ оמ Toast.makeText( LocalContext.current, "message", Toast.LENGTH_SHORT ).show()
  45. ✅ 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 -> { ... } } } }) { ... } }
  46. ⚠ 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
  47. ❌ 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() } }
  48. ⚠ 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 // ...
  49. ✅ 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 })
  50. ✅ 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) })
  51. ✅ 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))
  52. ⚠ 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"
  53. ⚠ Transition // ViewGroup੄ Child View ݽفী ੸ਊػ׮ TransitionManager.beginDelayedTransition( viewGroup,

    AutoTransition()) child.isVisible = show AutoTransition = <transitionSet android:transitionOrdering="sequential"> <fade android:fadingMode="fade_out" /> <changeBounds /> <fade android:fadingMode="fade_in" /> </transitionSet> Box { // пп ѐ߹੸ਵ۽ ੸ਊ೧ঠ ೠ׮ AnimatedVisibility( visible = show, enter = fadeIn() + expandIn(), // EnterTransition exit = shrinkOut() + fadeOut() // ExitTransition ) { Text(text = "child") } } // X, b/142451118
  54. ❌ View Animation fragmentManager.beginTransaction() .setCustomAnimations( R.anim.slide_in_right, // enterAnim R.anim.slide_out_left, //

    exitAnim ) .replace(...) .commit() <set> <alpha /> <translate /> <scale /> <rotate /> </set> // X // X // X // X // X
  55. ❌ 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
  56. Jetpack ۄ੉࠳۞ܻ ✅ ❌ Activity ViewPager ViewPager2 Preference Navigation Hilt

    ViewModel Paging Emoji ConstraintLayout Wear Link: https://developer.android.com/jetpack/compose/libraries
  57. 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਷ ই૒ ૑ਗೞ૑ ঋח׮.
  58. 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 ۄ੉࠳۞ܻܳ ؀উਵ۽ Ѩష೧ࠅ ࣻب ੓ਸ Ѫ э׮.
  59. 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
  60. @Composable fun DrawDrawable() { val context = LocalContext.current val drawable

    = ContextCompat.getDrawable( LocalContext.current, R.drawable.shape // <shape> ) Image( painter = rememberDrawablePainter(drawable), contentDescription = null ) } Accompanist: Drawable Painter painterResource() ח BitmapDrawable ژח VectorDrawable݅ ૑ਗೠ׮. ӝઓী ࢎਊೞ؍ <shape>৬ э਷ Drawableਸ ࢎਊೡ ࣻ ੓ѱ ೧઱ח ۄ੉࠳۞ܻ. ׮݅ <selector>, <level-list> ୊ۢ ࢚కী ٮۄ ੉޷૑о ׳ۄ૑ח Drawable਷ ૑ਗغ૑ ঋח׮.
  61. Accompanist: Swipe Refresh val viewModel: MyViewModel = viewModel() val isRefreshing

    by viewModel.isRefreshing.collectAsState() SwipeRefresh( state = rememberSwipeRefreshState(isRefreshing), onRefresh = { viewModel.refresh() }, ) { // list } SwipeRefreshLayoutਸ ؀୓ೞח ۄ੉࠳۞ܻ.
  62. 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ܳ ؀୓ೞח ۄ੉࠳۞ܻ.
  63. Accompanist: Placeholder Text( text = "Content to display after content

    has loaded", modifier = Modifier .placeholder( visible = true, highlight = PlaceholderHighlight.fade(), ) ) Placeholder ӝמਸ ઁҕೞח ۄ੉࠳۞ܻ.
  64. ੼૓੸ਵ۽ ੸ਊೞӝ • ӝઓ জী ੼૓੸ਵ۽ 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
  65. Issue Tracker: ੉ग ١۾ೞӝ / Star ־ܰӝ Link: https://issuetracker.google.com/issues?q=componentid:610764 componentid:

    856989 → foundation 742043 → material 610764 → compiler / runtime 610478 → ui / animation
  66. Summary • ✅ ؊ ੸Ҋ ૒ҙ੸ੋ ௏٘۽ UIܳ ѐߊೡ ࣻ

    ੓׮. 
 • 📚 ׮নೠ ҕध ޙࢲ৬ ௏٘ەਸ ా೧ ઑӘঀ ೟ण೧ࠁ੗. 
 • ⚠ ੗઱ ࢎਊೞҊ ੓ח View API৬ زੌೠ APIо ઁҕغח૑ ԙ ഛੋ೧ࠁ੗. 
 • 🚀 APIо হਵݶ Jetpack Compose Roadmapী ӝੑغয ੓ח૑ ଵҊ೧ࠁ੗. 
 • 👾 ӝמ/ࢸ҅ য়ܨܳ ߊѼೞݶ, IssueTrackerী ੉गܳ թѹࠁ੗. (زੌೠ ੉गо ੓ਵݶ ⭐ ܳ ־ܰ੗.) 
 • 💬 п੗ Compose ੸ਊೞӝ ੸׼ೠ द੼ਸ Ҋ޹೧ࠁ੗.