Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

mordern UI toolkit ௏٘ хࣗ ૒ҙ੸ declarative UI ъ۱ೠ ࢿמ ѐߊ ࣘب ೱ࢚ Kotlin APIs ҕध ѐߊ ޙࢲীࢲח ৈ۞ ੢੼ٜ੉ աৌغয ੓૑݅, ѐߊ੗݃׮ ׮ܲ ࢤпਸ ೞҊ ੓ਸ Ѫ э׮.

Slide 5

Slide 5 text

૑Әө૑ ੘ࢿ೧য়؍ XML ߑध੄ UI ҳഅীࢲ 
 Jetpack Compose۽ ߄۽ ੹ജ೧ঠ ೞաਃ? 🤔

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

೐۽ં౟݃׮ ࢚ട੉ ׮ܰ׮. ੿׹੉ হ׮. 👀

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

৵ Composeܳ ࢎਊ೧ঠ ೡө? 01

Slide 14

Slide 14 text

✅ View ࠁ׮ Compose о ա਷ Ѫ?

Slide 15

Slide 15 text

✅ ೒ۖಬ ઙࣘࢿ ୭ࣗച Composeח Jetpack ۄ੉࠳۞ܻ۽ ߓನغয ೒ۖಬী ؏ ઙࣘ੸੉׮. ೒ۖಬ Viewীח APIܳ ࢎਊೡ ࣻ ੓ח ୭ࣗ ߡ੹ ઁড੉ ੓׮. ੉ܳ ೧ࣗೞ۰Ҋ, Compat APIܳ ઁҕೞ૑݅ ೠ҅о ੓׮.

Slide 16

Slide 16 text

✅ ࢚ࣘਵ۽ ੋೠ ޙઁ ೧ࣗ 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 ೣٜࣻ۽ ҳࢿغয, ೙ਃೠ ӝמ݅ ੘ѱ ਬ૑ೡ ࣻ ੓׮.

Slide 17

Slide 17 text

✅ ઺୏ ۨ੉ইਓ ࢿמ @Composable fun Sample() { Column { Image() Image() Column { Text() Column { Text() Box { TextField() } } Column { Text() Box { TextField() ViewGroupী ٮۄ measureܳ ৈ۞ߣ ࣻ೯ೡ ࣻ ੓যࢲ ୭؀ೠ ۨ੉ইਓ੉ ઺୏غ૑ ঋب۾ न҃ॄঠ ೮׮. Composeח measureܳ ೠߣ݅ ࣻ೯ೞب۾ ъઁغয ઺୏ ۨ੉ইਓীࢲب ࢿמ੉ ௼ѱ ੷ೞغ૑ ঋח׮.

Slide 18

Slide 18 text

✅ UI ੤ࢎਊࢿ

Slide 19

Slide 19 text

✅ ૑ো ୡӝച // 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ח ઑѤޙ ݅ਵ۽ب рױ൤ ҳഅೡ ࣻ ੓׮.

Slide 20

Slide 20 text

✅ 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) { ... } }

Slide 21

Slide 21 text

✅ ؘ੉ఠ ߄ੋ٬ @BindingAdapter("onTextChanged") fun EditText.onTextChanged(listener: (String) -> Unit){ doOnTextChanged { text, _, _, _ -> listener(text.toString()) } } @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 ೣࣻ۽ ܻ࠙ೞݶ ػ׮.

Slide 22

Slide 22 text

✅ Custom View ҳഅ public class AppCompatImageView extends ImageView { public AppCompatImageView(...) { final int id = a.getResourceId( R.styleable.AppCompatImageView_srcCompat, -1); ... } } @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 ௏٘݅ ੘ࢿೞݶ ػ׮.

Slide 23

Slide 23 text

✅ ݾ۾ഋ 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 ೣࣻ৬ زੌೠ ߑߨਵ۽ औѱ ҳࢿೡ ࣻ ੓׮.

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

• min sdk ઁডࢎ೦੉ ੓׮. (API 21+) • (Java/Kotlinҗ ׳ܻ) View৬ Composeח ࢲ۽ ߸ജغ૑ ঋח׮. • Material Theme݅ ӝࠄਵ۽ ૑ਗೠ׮. • ۨ੉ইਓ XML ؀࠺ Preview ࢿמ੉ ڄযઉࢲ ࢤ࢑ࢿ੉ ڄয૓׮. • ۨ੉ইਓ XMLਸ ࢎਊೡ ٸࠁ׮ ܻ࠭ೡ ௏٘о טযդ׮. • ૑ਗೞח 3rd party ۄ੉࠳۞ܻо ੸׮. ❌ Viewࠁ׮ Composeীࢲ ইए਍ Ѫ?

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

⚠ Theme <item name="colorPrimary">@color/red</item> 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)

Slide 29

Slide 29 text

✅ 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੄ ࣘࢿਸ ҳࢿ೧ળ׮.

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

⚠ 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) }

Slide 32

Slide 32 text

Link: https://material.io/blog/migrate-android-material-components ૑Ә੄ ٣੗ੋ दझమ੉ MDC Theme۽ ҳഅ оמೞ׮ݶ ੹ജਸ Ҋ۰೧ࠁח Ѫ੉ જਸ ٠.

Slide 33

Slide 33 text

Custom Theme Link: https://github.com/android/compose-samples/tree/main/Jetsnack ӝࠄ Themeী ৈ۞о૑ ࣘࢿਸ ୶оೞח ١ ߹ب੄ ٣੗ੋ दझమਸ ҳ୷ೞҊ ੓׮ݶ, Custom Themeܳ ૒੽ ҳഅೞח Ѫب Ҋ۰೧ࠅ ࣻ ੓׮. ׮݅ compose-materialী ੓ח ݆ࣻ਷ ஹನք౟ٜਸ Ӓ؀۽ ࢎਊೞӝ য۰ਕ૕ ࣻ ੓׮. पઁ ҳഅೞח ߑߨ਷ উ٘۽੉٘ ҕध ࢠ೒ ೐۽ં౟ Jetsnackਸ ଵҊ೧ࠁ੗.

Slide 34

Slide 34 text

⚠ 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)

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

⚠ View 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 )

Slide 39

Slide 39 text

✅ 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 -> ... } ))

Slide 40

Slide 40 text

⚠ TextView 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

Slide 41

Slide 41 text

✅ EditText editText.doOnTextChanged { text, start, count, after -> … } editText.setOnEditorActionListener { textView, i, keyEvent -> … } TextField( value = text, onValueChange = { text -> ... }, placeholder = { Text("hint") }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number, imeAction = ImeAction.Done ), keyboardActions = KeyboardActions(onDone = { ... }), )

Slide 42

Slide 42 text

✅ Button @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 {}

Slide 43

Slide 43 text

⚠ ImageView Image( painter = painterResource(id = R.drawable.icon), colorFilter = ColorFilter.tint(Color.Red), contentScale = ContentScale.Inside, // X, b/196358649 )

Slide 44

Slide 44 text

✅ ImageButton 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)) }

Slide 45

Slide 45 text

✅ CheckBox checkBox.setOnCheckedChangeListener { compoundButton, checked -> ... } Checkbox( checked = true, colors = CheckboxDefaults.colors( checkedColor = Color.Red ), onCheckedChange = { checked -> ... }, )

Slide 46

Slide 46 text

⚠ ProgressBar android:min="0" android:max="100" android:progress="1" android:secondaryProgress="10" /> CircularProgressIndicator() LinearProgressIndicator() LinearProgressIndicator( // min: 0f // max: 1f progress = 0.01f, // X )

Slide 47

Slide 47 text

⚠ SeekBar 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 -> ... }, )

Slide 48

Slide 48 text

⚠ 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) } }

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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 ❌

Slide 51

Slide 51 text

✅ FrameLayout Box { Image() Text(text = "text", modifier = Modifier.align(Alignment.Center)) } Box(contentAlignment = Alignment.Center) { ... }

Slide 52

Slide 52 text

✅ LinearLayout Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Image(modifier = Modifier.weight(1f)) Text() } Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Image() Text() }

Slide 53

Slide 53 text

✅ ScrollView Column( Modifier.verticalScroll(rememberScrollState()) ) { ... } Row( Modifier.horizontalScroll(rememberScrollState()) ) { ... }

Slide 54

Slide 54 text

⚠ 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

Slide 55

Slide 55 text

⚠ RecyclerView: LayoutManager 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

Slide 56

Slide 56 text

✅ Card ... Card( shape = RoundedCornerShape(4.dp), backgroundColor = Color.White, elevation = 8.dp ) { ... }

Slide 57

Slide 57 text

✅ CoordinatorLayout Scaffold( topBar = { TopAppBar { ... } }, content = { }, bottomBar = { BottomNavigation(...) }, floatingActionButton = { FloatingActionButton { ... } } ) // X, b/194911953 Tooltip

Slide 58

Slide 58 text

⚠ Toast, Snackbar, Dialog, Fragment

Slide 59

Slide 59 text

❌ Toast Toast.makeText( context, "message", Toast.LENGTH_SHORT ).show() // X, ইې୊ۢ ࢎਊ оמ Toast.makeText( LocalContext.current, "message", Toast.LENGTH_SHORT ).show()

Slide 60

Slide 60 text

✅ 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 -> { ... } } } }) { ... } }

Slide 61

Slide 61 text

⚠ 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

Slide 62

Slide 62 text

❌ 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() } }

Slide 63

Slide 63 text

⚠ Interpolator, Animation, Transition

Slide 64

Slide 64 text

⚠ 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 // ...

Slide 65

Slide 65 text

✅ 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 })

Slide 66

Slide 66 text

✅ 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) })

Slide 67

Slide 67 text

✅ 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))

Slide 68

Slide 68 text

⚠ 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"

Slide 69

Slide 69 text

⚠ Transition // ViewGroup੄ Child View ݽفী ੸ਊػ׮ TransitionManager.beginDelayedTransition( viewGroup, AutoTransition()) child.isVisible = show AutoTransition = Box { // пп ѐ߹੸ਵ۽ ੸ਊ೧ঠ ೠ׮ AnimatedVisibility( visible = show, enter = fadeIn() + expandIn(), // EnterTransition exit = shrinkOut() + fadeOut() // ExitTransition ) { Text(text = "child") } } // X, b/142451118

Slide 70

Slide 70 text

❌ View Animation fragmentManager.beginTransaction() .setCustomAnimations( R.anim.slide_in_right, // enterAnim R.anim.slide_out_left, // exitAnim ) .replace(...) .commit() // X // X // X // X // X

Slide 71

Slide 71 text

❌ 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

Slide 72

Slide 72 text

Compose۽੄ ੹ജਸ ળ࠺ೞӝ 03

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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਷ ই૒ ૑ਗೞ૑ ঋח׮.

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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 ۄ੉࠳۞ܻܳ ؀উਵ۽ Ѩష೧ࠅ ࣻب ੓ਸ Ѫ э׮.

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

@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਷ ૑ਗغ૑ ঋח׮.

Slide 79

Slide 79 text

Accompanist: Swipe Refresh val viewModel: MyViewModel = viewModel() val isRefreshing by viewModel.isRefreshing.collectAsState() SwipeRefresh( state = rememberSwipeRefreshState(isRefreshing), onRefresh = { viewModel.refresh() }, ) { // list } SwipeRefreshLayoutਸ ؀୓ೞח ۄ੉࠳۞ܻ.

Slide 80

Slide 80 text

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ܳ ؀୓ೞח ۄ੉࠳۞ܻ.

Slide 81

Slide 81 text

Accompanist: Placeholder Text( text = "Content to display after content has loaded", modifier = Modifier .placeholder( visible = true, highlight = PlaceholderHighlight.fade(), ) ) Placeholder ӝמਸ ઁҕೞח ۄ੉࠳۞ܻ.

Slide 82

Slide 82 text

੼૓੸ਵ۽ ੸ਊೞӝ • ӝઓ জী ੼૓੸ਵ۽ 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

Slide 83

Slide 83 text

Issue Tracker: ੉ग ١۾ೞӝ / Star ־ܰӝ Link: https://issuetracker.google.com/issues?q=componentid:610764 componentid: 856989 → foundation 742043 → material 610764 → compiler / runtime 610478 → ui / animation

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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