Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up for free
Jetpack Compose, having and not.
Sungyong An
September 25, 2021
Programming
2
590
Jetpack Compose, having and not.
Jetpack Compose에 있는 것, 없는 것
드로이드나이츠 2021 행사에서 발표한 내용입니다.
https://sites.google.com/view/dk21/
Sungyong An
September 25, 2021
Tweet
Share
More Decks by Sungyong An
See All by Sungyong An
NAVER Map in Jetpack Compose
fornewid
1
430
Compose Camp 22KR Pathway3
fornewid
1
240
What's new in Jetpack 2022
fornewid
1
720
What's new in Jetpack 2021
fornewid
2
270
Material Motion for Jetpack Compose
fornewid
0
190
Neumorphism in android
fornewid
0
250
Android UI Animation 들이붓기
fornewid
0
680
XML details for Android UI
fornewid
1
430
What's new in Jetpack 2020
fornewid
2
830
Other Decks in Programming
See All in Programming
AWSにおける標的型Bot対策
hacomono
0
420
Excelの助けを借りて楽にシナリオを作ろう
rpa_niiyama
0
300
Azure Functionsをサクッと開発、サクッとデプロイ/vscodeconf2023-baba
nina01
1
340
Hono v3 - Do Everything, Run Anywhere, But Small, And Faster
yusukebe
4
130
ipa-medit: Memory search and patch tool for IPA without Jailbreaking/ipa-medit-bh2022-europe
tkmru
0
130
Step Functions Distributed Map を使ってみた
codemountains
0
110
Jetpack Compose 完全に理解した
mkeeda
1
470
ECS Service Connectでマイクロサービスを繋いでみた
xblood
0
550
フロントエンドで学んだことをデータ分析で使ってみた話
daichi_igarashi
0
180
What's new in Shopware 6.5
shyim
0
110
あなたと 「|」 したい・・・
track3jyo
PRO
2
1.1k
Qiita Night PHP 2023
fuwasegu
0
11k
Featured
See All Featured
Product Roadmaps are Hard
iamctodd
38
7.7k
Making Projects Easy
brettharned
102
4.8k
Pencils Down: Stop Designing & Start Developing
hursman
114
10k
Robots, Beer and Maslow
schacon
154
7.3k
How New CSS Is Changing Everything About Graphic Design on the Web
jensimmons
214
12k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
349
27k
JazzCon 2018 Closing Keynote - Leadership for the Reluctant Leader
reverentgeek
175
9.1k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
120
29k
Agile that works and the tools we love
rasmusluckow
320
20k
Stop Working from a Prison Cell
hatefulcrawdad
263
18k
Three Pipe Problems
jasonvnalue
89
8.9k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
152
13k
Transcript
Sungyong An Jetpack Composeী ח Ѫ, হח Ѫ
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
mordern UI toolkit ٘ хࣗ ҙ declarative UI ъ۱ೠ ࢿמ
ѐߊ ࣘب ೱ࢚ Kotlin APIs
mordern UI toolkit ٘ хࣗ ҙ declarative UI ъ۱ೠ ࢿמ
ѐߊ ࣘب ೱ࢚ Kotlin APIs ҕध ѐߊ ޙࢲীࢲח ৈ۞ ٜ աৌغয ݅, ѐߊ݃ ܲ ࢤпਸ ೞҊ ਸ Ѫ э.
Әө ࢿ೧য়؍ XML ߑध UI ҳഅীࢲ Jetpack Compose۽ ߄۽
ജ೧ঠ ೞաਃ? 🤔
M V P M V VM M V I M
V C ӝઓ Jetpack ۄ࠳۞ܻ৬ ܰѱ Jetpack Composeח জ .
۽ં݃ ࢚ട ܰ. হ. 👀
Compose ਊਸ Ѩష೧ࠅ ࣻ ѱ View ৬ ࠺Ү೧ࠁ ݾ
02 01 03 ৵ Composeܳ ࢎਊ೧ঠ ೡө Composeী হח Ѫ
Compose۽ ജਸ ળ࠺ೞӝ ݾର
୶ୌ ࢚ (ҕध) Understanding Compose AndroidDevSummit '19 I/O '19 Declarative
UI patterns Thinking in Compose 11 Weeks of Android Compose ӝࠄ ѐ֛ী ೧ࢲח ܖ ঋणפ. ҙ۲೧ࢲח ҕध ࢚ दਸ ୶ୌפ.
୶ୌ ࢚ (ೠҴয) Jetpack Compose: I/O 2021 Ӓ റ I/O
Extended Korea Android 2021 I/O Extended with doubleS Compose दೞӝ ৢ೧ ೠҴয ࣁ࣌ب ਵפ, ҙब ਵन ٜ࠙ द೧ࠁࣁਃ.
View Compose ⚠ ৽ଃ View, য়ܲଃ Compose ղਊੑפ.
৵ Composeܳ ࢎਊ೧ঠ ೡө? 01
✅ View ࠁ Compose о ա Ѫ?
✅ ۖಬ ઙࣘࢿ ୭ࣗച Composeח Jetpack ۄ࠳۞ܻ۽ ߓನغয ۖಬী ؏
ઙࣘ. ۖಬ Viewীח APIܳ ࢎਊೡ ࣻ ח ୭ࣗ ߡ ઁড . ܳ ೧ࣗೞ۰Ҋ, Compat APIܳ ઁҕೞ݅ ೠ҅о .
✅ ࢚ࣘਵ۽ ੋೠ ޙઁ ೧ࣗ 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 ೣٜࣻ۽ ҳࢿغয, ਃೠ ӝמ݅ ѱ ਬೡ ࣻ .
✅ ۨইਓ ࢿמ <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ܳ ೠߣ݅ ࣻ೯ೞب۾ ъઁغয ۨইਓীࢲب ࢿמ ѱ ೞغ ঋח.
✅ 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 ೣࣻ۽ ܻ࠙ೞݶ ػ.
✅ ো ୡӝച <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ח ઑѤޙ ݅ਵ۽ب рױ ҳഅೡ ࣻ .
✅ 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) { ... } }
✅ ؘఠ ߄ੋ٬ @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 ೣࣻ۽ ܻ࠙ೞݶ ػ.
✅ 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 ٘݅ ࢿೞݶ ػ.
✅ ݾ۾ഋ 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 ೣࣻ৬ زੌೠ ߑߨਵ۽ औѱ ҳࢿೡ ࣻ .
• Viewܳ ݽف ઁѢೞҊ Compose݅ ࢎਊೞݶ, APK ӝ৬ ࠽٘ दр
хࣗೠ. • Compose৬ Viewܳ ೣԋ ॳݶ, APK ӝ৬ ࠽٘ दр ডр ૐоೠ. • Composeীࢲب Viewܳ ࢎਊೡ ࣻ . (AndroidView) • Java৬ XML۽ ࢿػ ۨইਓ ٘ܳ Kotlinਵ۽ ജೞח ࠺ਊ • View ਬࠁࣻ ࠺ਊ vs Compose ण ࠺ਊ • View৬ ࠺तೞѱ AOT ஹੌਸ ਗೠ. (ProfileInstaller) ⚠ ࠺Үೞӝী ઑӘ গݒೠ ٜࠗ࠙
• min sdk ઁডࢎ೦ . (API 21+) • (Java/Kotlinҗ ׳ܻ)
View৬ Composeח ࢲ۽ ߸ജغ ঋח. • Material Theme݅ ӝࠄਵ۽ ਗೠ. • ۨইਓ XML ࠺ Preview ࢿמ ڄযઉࢲ ࢤࢿ ڄয. • ۨইਓ XMLਸ ࢎਊೡ ٸࠁ ܻ࠭ೡ ٘о טযդ. • ਗೞח 3rd party ۄ࠳۞ܻо . ❌ Viewࠁ Composeীࢲ ইए Ѫ?
Composeী হח Ѫ 02 (ҳ࠙: ✅ , ⚠ ੌࠗ হ,
❌ হ)
Link: https://jetpackcompose.app/What-is-the-equivalent-of-TextView-in-Jetpack-Compose
⚠ 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)
✅ 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 ࣘࢿਸ ҳࢿ೧ળ.
Theme.AppCompat android:Theme.DeviceDefault Ә MDC Themeܳ ࢎਊೞҊ ঋݶ? 🤔
⚠ 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) }
Link: https://material.io/blog/migrate-android-material-components Ә ٣ੋ दझమ MDC Theme۽ ҳഅ оמೞݶ ജਸ
Ҋ۰೧ࠁח Ѫ જਸ ٠.
Custom Theme Link: https://github.com/android/compose-samples/tree/main/Jetsnack ӝࠄ Themeী ৈ۞о ࣘࢿਸ ୶оೞח ١
߹ب ٣ੋ दझమਸ ҳ୷ೞҊ ݶ, Custom Themeܳ ҳഅೞח Ѫب Ҋ۰೧ࠅ ࣻ . ݅ compose-materialী ח ݆ࣻ ஹನքٜਸ Ӓ۽ ࢎਊೞӝ য۰ਕ ࣻ . पઁ ҳഅೞח ߑߨ উ٘۽٘ ҕध ࢠ ۽ં Jetsnackਸ ଵҊ೧ࠁ.
⚠ 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)
ImageView TextView Button ImageButton RecyclerView EditText ProgressBar SeekBar Space CheckBox
WebView LinearLayout FrameLayout RelativeLayout CardView ConstraintLayout ScrollView ListView GridView TableLayout View
View ImageView TextView Button ImageButton RecyclerView EditText ProgressBar SeekBar Space
CheckBox WebView LinearLayout FrameLayout RelativeLayout CardView ConstraintLayout ScrollView ListView GridView TableLayout Widget
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 <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: 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 -> ... } ))
⚠ 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
✅ 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 = { ... }), )
✅ Button <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 {}
⚠ 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 )
✅ 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)) }
✅ CheckBox <CheckBox android:checked="true" android:buttonTint="@color/red" /> checkBox.setOnCheckedChangeListener { compoundButton, checked
-> ... } Checkbox( checked = true, colors = CheckboxDefaults.colors( checkedColor = Color.Red ), onCheckedChange = { checked -> ... }, )
⚠ 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 )
⚠ 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 -> ... }, )
⚠ 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 ImageView TextView Button ImageButton RecyclerView EditText ProgressBar SeekBar Space
CheckBox WebView LinearLayout FrameLayout RelativeLayout CardView ConstraintLayout ScrollView ListView GridView TableLayout ViewGroup
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 ❌
✅ 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) { ... }
✅ 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() }
✅ ScrollView <ScrollView> <LinearLayout android:orientation="vertical"/> </ScrollView> <HorizontalScrollView> <LinearLayout android:orientation="horizontal"/> </HorizontalScrollView>
Column( Modifier.verticalScroll(rememberScrollState()) ) { ... } Row( Modifier.horizontalScroll(rememberScrollState()) ) { ... }
⚠ 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
⚠ 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
✅ Card <androidx.cardview.widget.CardView app:cardCornerRadius="4dp" app:cardBackgroundColor="@color/white" app:cardElevation="8dp"> ... </androidx.cardview.widget.CardView> Card( shape
= RoundedCornerShape(4.dp), backgroundColor = Color.White, elevation = 8.dp ) { ... }
✅ 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
⚠ Toast, Snackbar, Dialog, Fragment
❌ Toast Toast.makeText( context, "message", Toast.LENGTH_SHORT ).show() // X, ইېۢ
ࢎਊ оמ Toast.makeText( LocalContext.current, "message", Toast.LENGTH_SHORT ).show()
✅ 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 -> { ... } } } }) { ... } }
⚠ 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
❌ 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() } }
⚠ Interpolator, Animation, Transition
⚠ 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 // ...
✅ 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 })
✅ 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) })
✅ 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))
⚠ 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"
⚠ 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
❌ 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
❌ 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
Compose۽ ജਸ ળ࠺ೞӝ 03
Jetpack ۄ࠳۞ܻ ✅ ❌ Activity ViewPager ViewPager2 Preference Navigation Hilt
ViewModel Paging Emoji ConstraintLayout Wear Link: https://developer.android.com/jetpack/compose/libraries
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 ই ਗೞ ঋח.
৻ࠗ ۄ࠳۞ܻ ✅ ❌ PhotoView Coil Glide Lottie Picasso ExoPlayer
Accompanist FlexboxLayout
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 ۄ࠳۞ܻܳ উਵ۽ Ѩష೧ࠅ ࣻب ਸ Ѫ э.
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
@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 ਗغ ঋח.
Accompanist: Swipe Refresh val viewModel: MyViewModel = viewModel() val isRefreshing
by viewModel.isRefreshing.collectAsState() SwipeRefresh( state = rememberSwipeRefreshState(isRefreshing), onRefresh = { viewModel.refresh() }, ) { // list } SwipeRefreshLayoutਸ ೞח ۄ࠳۞ܻ.
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ܳ ೞח ۄ࠳۞ܻ.
Accompanist: Placeholder Text( text = "Content to display after content
has loaded", modifier = Modifier .placeholder( visible = true, highlight = PlaceholderHighlight.fade(), ) ) Placeholder ӝמਸ ઁҕೞח ۄ࠳۞ܻ.
ਵ۽ ਊೞӝ • ӝઓ জী ਵ۽ 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
Issue Tracker: ग ١۾ೞӝ / Star ־ܰӝ Link: https://issuetracker.google.com/issues?q=componentid:610764 componentid:
856989 → foundation 742043 → material 610764 → compiler / runtime 610478 → ui / animation
Issue Tracker: ग ١۾ೞӝ / Star ־ܰӝ 👆
Summary • ✅ ؊ Ҋ ҙੋ ٘۽ UIܳ ѐߊೡ ࣻ
. • 📚 নೠ ҕध ޙࢲ৬ ٘ەਸ ా೧ ઑӘঀ ण೧ࠁ. • ⚠ ࢎਊೞҊ ח View API৬ زੌೠ APIо ઁҕغח ԙ ഛੋ೧ࠁ. • 🚀 APIо হਵݶ Jetpack Compose Roadmapী ӝੑغয ח ଵҊ೧ࠁ. • 👾 ӝמ/ࢸ҅ য়ܨܳ ߊѼೞݶ, IssueTrackerী गܳ թѹࠁ. (زੌೠ गо ਵݶ ⭐ ܳ ־ܰ.) • 💬 п Compose ਊೞӝ ೠ दਸ Ҋ೧ࠁ.
Sungyong An Android Developer @fornewid Thank you! Slide: https://speakerdeck.com/fornewid/jetpack-compose-having-and-not