์ด๋ฒ์ ๊ณต๋ถํ ๋ด์ฉ์ compose๋ก ์์ ฏ์ ๋ง๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ glance์ด๋ค.
glance์ ์กด์ฌ(?)๋ ์ด์ ์ ๋ค์ผ๋ฌ ๊ฐ๋ ์ปจํผ๋ฐ์ค ๊ฐ์ฐ์์ glance์ ๊ด๋ จ๋ ๊ฐ์ฐ์ ๋ค๊ฒ ๋์๋๋ฐ, ๊ทธ๋๋ถํฐ ๊ธฐํ๊ฐ ๋๋ฉด ๊ณต๋ถํด ๋ณด์ ๋ผ๋ ์๊ฐ์ ํ๊ฒ ๋์๋ค. ๊ทธ๊ฒ ์ง๊ธ์ด๋ค.
glance๋ compose์์ ์์ ฏ์ ์ฝ๊ฒ ๋ง๋ค ์ ์๊ฒ ํด์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๊ณ 2023๋ 8~9์์ 1.0.0์ ์ถ์ํ ๋ฐ๋๋ฐ๋ํ ์ ์(?) ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
์๋ฌดํผ ๊ทธ๋์ ์ด๋ฒ์ compose glance๋ฅผ ๊ณต๋ถํ ๋ด์ฉ์ ๋ธ๋ก๊ทธ์ ์ ๋ฆฌํด๋ณด๋ ค ํ๋ค.
๊ทธ๋ผ ๋ฐ๋ก glance ์ฌ์ฉ๋ฒ์ ์์๋ณด์.
1. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐํ๊ธฐ
๋จผ์ glance ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ถ๊ฐํด ์ค๋ค.
// glance
implementation("androidx.glance:glance-appwidget:1.0.0")
implementation("androidx.glance:glance-material3:1.0.0")
2. ์์ ฏ ๋ ์ด์์ ๊ตฌ์ฑํ๊ธฐ
๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐ ํ, ๋ค์์ผ๋ก๋ ์์ ฏ์ UI๋ฅผ ๊ตฌ์ฑํด ๋ณด๊ฒ ๋ค.
object CounterWidget: GlanceAppWidget() {
// widget body
override suspend fun provideGlance(context: Context, id: GlanceId) {
// use provideContent, you can use composable function
provideContent {
}
}
}
์์ ฏ์ ๋ง๋ค๊ธฐ ์ํด์๋ GlanceAppWidget์ ์์๋ฐ์์ provideGlance ํจ์ ์์ widget ui๋ฅผ ๊ตฌ์ฑํด์ผ ํ๋ค.
๊ทธ๋ฌ๋ ๊ทธ๋ฅ provideGlance ํจ์ ์์์๋ Text๋ Button ๊ฐ์ composable function์ ์ฌ์ฉํ ์ ์์ผ๋ฏ๋ก provideContent๋ฅผ ์ฌ์ฉํด ๊ทธ ์์ ui๋ฅผ ๊ตฌ์ฑํ๋ค.
์ด๋ฒ์ ํ์๊ฐ ๋ง๋ค ์์ ๋ philipp lackner์ ์์์ผ๋ก ๊ณต๋ถํ๋ค.
๋ฐ๋ผ์ ์์์ ๋์ค๋ ์์ ฏ ์์ ๋ฒํผ์ ๋๋ฅด๋ฉด ์ซ์๊ฐ +1 ๋๋ ๊ฐ๋จํ ์์ ๋ฅผ ๋ง๋ค์ด ๋ณด๊ฒ ๋ค.
https://www.youtube.com/watch?v=bhrN7yFG0D4&t=729s&pp=ygUWYW5kcm9pZCBjb21wb3NlIGdsYW5jZQ%3D%3D
๊ทธ๋ฌ๋ฉด ๋ค์๊ณผ ๊ฐ์ด UI๋ฅผ ๊ตฌ์ฑํ ์ ์๋ค.
Text(
text = "0",
style = TextStyle(
fontWeight = FontWeight.Medium,
color = ColorProvider(Color.White),
fontSize = 26.sp
)
)
Button(
text = "์ถ๊ฐ",
onClick = // add action
)
Text ๋ถ๋ถ์๋ ํด๋ฆญํ๋ฉด text๊ฐ ๋ณ๊ฒฝ๋ ์ ์๋๋ก ๋ก์ง์ ์ง์ผํ๊ณ , Button์์๋ click ํ์ ๋ click action์ ์์ฑํด ์ฃผ์ด์ผ ํ๋ค.
ํ์ง๋ง ๋์น๊ฐ ๋น ๋ฅธ ๋ถ๋ค์ ์ด์ํจ์ ๋๊ผ์ ๊ฒ์ด๋ค.
๋ฐ๋ก onClick๋ถ๋ถ์ด lambda(๋๋ค) ํ์์ด ์๋๋ผ๋ ์ ์ธ๋ฐ, ์์ ํ์๊ฐ ์ธ๊ธํ ์์์ ๋ณด๋ฉด ๊ธ๋ฐฉ ์ ์ ์์ ๊ฒ์ด๋ค.
์์ Text์ Button Composable function๋ค์ android์์ ์์ฒด ์ ๊ณตํ๋ ํจ์๊ฐ ์๋ glance์์ ์ ๊ณตํ๋ ํจ์๋ค์ด๋ค.
๊ทธ๋์ ๊ธฐ๋ณธ composable function๋ค๊ณผ ํผ๋ํ์ง ์๋๋ก ์ฃผ์ํ์(ํจ์ ๊ตฌ์ฑ ์์๋ค๋ ๋ค๋ฅด๊ธฐ์)
ํ์ง๋ง ๊ทธ๋ฅ glance function ๋ง๊ณ ๊ธฐ๋ณธ function์ ์ฌ์ฉํ๋ฉด ์ ๋๋ค.
๋น๋๋๋ ๋ฐ๋ ๋ฌธ์ ๊ฐ ์์ง๋ง, ์์ ฏ์ ํฐ์์ ๋ง๋ค๋ฉด UI ์์ ๋์ ์์ ฏ์ ๋ก๋ํ ์ ์๋ค๋ ํ ์คํธ๊ฐ ์ฌ๋ฌ๋ถ๋ค์ ๋ฐ๊ฒจ์ค ๊ฒ์ด๋ค.
๊ทธ๋ฌ๋ฉด ์ค๊ฐ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค.
object CounterWidget: GlanceAppWidget() {
// widget body
override suspend fun provideGlance(context: Context, id: GlanceId) {
// use provideContent, you can use composable function
provideContent {
Text(
text = "0",
style = TextStyle(
fontWeight = FontWeight.Medium,
color = ColorProvider(Color.White),
fontSize = 26.sp
)
)
Button(
text = "์ถ๊ฐ",
onClick = // add action
)
}
}
}
์ด์ ๋ Button์ click action์ ๋ง๋ค์ด์ค ์ฐจ๋ก์ด๋ค.
3. Click action ๋ง๋ค๊ธฐ
๋จผ์ ๋ฒํผ ํด๋ฆญ ์ก์ ํด๋์ค๋ฅผ ๋ฐ๋ก ์์ฑ ํ, glance์ ActionCallback์ ์์๋ฐ์ ์ค๋ค.
// widget update action callback
object IncrementActionCallback: ActionCallback {
override suspend fun onAction(
context: Context,
glanceId: GlanceId,
parameters: ActionParameters
) {
}
}
onAction์์ ํด๋ฆญ action์ ์ค ์ ์๋ค.
์์์์๋ Text์ ์ ๋ณด๋ฅผ preferenceKey๋ก ์ ์ฅํ์๊ธฐ์ preferenceKey๋ฅผ ๋ด์ ๋ณ์๋ฅผ ๋ง๋ค์ด ์ค๋ค.
val countKey = intPreferencesKey("count")
๊ทธ๋ฆฌ๊ณ Text์ ์ ์ฉํ ํด๋ฆญ ์นด์ดํธ ๋ณ์๋ฅผ ๋ง๋ค์ด ์ค๋ค.
val count = currentState(key = countKey) ?: 0
countKey์ value๊ฐ null์ด๋ฉด ๊ธฐ๋ณธ๊ฐ(0)์ผ๋ก ์์ํ๋๋ก ๋ง๋ค์ด ์ฃผ์๋ค.
์ด์ ๋ click action ํด๋์ค์์ ์์ ์ ํด์ค ์ฐจ๋ก์ด๋ค.
glance์์๋ action์ ์ํ ์ ์์ ฏ์ update ํ๋ ํ์์ผ๋ก ๋ณ๊ฒฝ๋ UI๋ฅผ ์ ์ฉํ๋ ๊ฒ ๊ฐ๋ค.
updateAppWidgetState(context, glanceId) { prefs ->
val currentCount = prefs[CounterWidget.countKey]
if (currentCount != null) prefs[CounterWidget.countKey] = currentCount + 1
else prefs[CounterWidget.countKey] = 1
}
CounterWidget.update(context, glanceId)
updateAppWidgetState์ param์ผ๋ก ์ด์ ์ ์์ฑํ onAction์ param์ ์ถ๊ฐํด์ ์ฌ์ฉํ ์ ์๋ค.
ํ์ฌ count๋ฅผ ๋ถ๋ฌ์์ null์ด ์๋ ๊ฒฝ์ฐ์๋ ์ด์ count์ +1์,
null(0)์ธ ๊ฒฝ์ฐ์๋ 1๋ก ๋ฐ๋๋๋ก ์์ ์ ํด ์ฃผ์๋ค.
updateAppWidgetState ์์ ์ ํด์ฃผ๊ณ ๋์ ์์ ฏ UI๋ฅผ ์ ๋ฐ์ดํธํด ์ฃผ๋ ์์ ์ ํ์์ด๋ค.
Click action callback์ ์ ์ฒด ์ฝ๋
// widget update action callback
object IncrementActionCallback: ActionCallback {
override suspend fun onAction(
context: Context,
glanceId: GlanceId,
parameters: ActionParameters
) {
updateAppWidgetState(context, glanceId) { prefs ->
val currentCount = prefs[CounterWidget.countKey]
if (currentCount != null) prefs[CounterWidget.countKey] = currentCount + 1
else prefs[CounterWidget.countKey] = 1
}
CounterWidget.update(context, glanceId)
}
}
์ด์ ๊ธฐ๋ฅ์ ๋ถ๋ถ์ ์๋ฃํ์์ผ๋ ํธ๋ํฐ์์ ๋ณด์ด๊ฒ ํ๋ ์์ ์ ํด์ค ๊ฒ์ด๋ค.
4. ์์ ฏ ๋ง๋ฌด๋ฆฌ ์์
์ด๋ฒ์ ๋ณ๊ฒฝํด์ผ ํ ์์ ์ ํด๋์ ํ์์ ์์ ฏ์ด ๋ณด์ด๋ ๊ฒ์ ๋ํ ์ค์ ์ ํด์ฃผ์ด์ผ ํ๋ค.
์๋ฅผ ๋ค๋ฉด,
์์ ์๊ณ ์ฑ์ ์์ ฏ์ฒ๋ผ, ํ๋จ์ Title๊ณผ ๊ธฐ๋ณธ ํฌ๊ธฐ ๋ฑ์ ์ค์ ํ ์ ์๋ค.
res -> xml ํจํค์ง ์์ ํด๋ํฐ์ ๋ณด์ด๋ ์์ ฏ์ ์ ๋ณด๋ฅผ ์์ ํ ์ ์๋๋ก ํ์ผ์ ๋ง๋ค์ด ์ฃผ๊ฒ ๋ค.
ํ์ผ ์ด๋ฆ ์ค์ ํ, root element๋ฅผ appwidget-provider๋ก ์ค์ ํ๊ณ ์์ฑํ๋ค.
counter_widget_info.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:previewImage="@drawable/compose"
android:description="@string/app_name"
android:minResizeWidth="50dp"
android:minResizeHeight="50dp"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen" />
description์ด ํฐ์์ ๋ณด์ด๋ Title์ด๊ณ minReziseWidth/Height๋ก ๊ฐ๋ฐ์๊ฐ ์ง์ ํฌ๊ธฐ๋ฅผ ์ ํด์ค ์ ์๋ค.
resizeMode๋ ์ฌ์ฉ์๊ฐ ์์ ฏ์ ๊ธธ๊ฒ ๋๋ฅด๋ฉด ์์ ฏ์ ๊ฐ๋ก ๋๋ ์ธ๋ก๋ก ๋๋ฆด ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
previewImage๋ ์์ ฏ์ ํ๋ฉด์ ๋ฃ์ ๋ ๋จ๋ ๊ธฐ๋ณธ ์ด๋ฏธ์ง๋ฅผ ๋ณด์ฌ์ค๋ค.(์์ ์๊ณ ์์ ฏ์ ๊ธฐ๋ณธ ์ด๋ฏธ์ง๊ฐ ์๊ณ์ธ ๊ฒ์ฒ๋ผ)
์ด์ ๋ง์ง๋ง์ผ๋ก manifest๋ฅผ ๊ฑด๋๋ ค์ผ ํ ์ฐจ๋ก์ด๋ค.
manifest์์๋ ์์ ฏ์ receiver ํ๊ทธ๋ฅผ ์ด์ฉํด ์์ ฏ ๋ฆฌ์๋ฒ๋ฅผ ๋ฌ์ ์ฃผ์ด์ผ ํ๋๋ฐ ๊ทธ๊ฒ์ด GlanceAppWidgetReceiver์ด๋ค.
ํ์ง๋ง ์์ง ๋ง๋ค์ด์ฃผ์ง ์์๊ธฐ ๋๋ฌธ์ ๋น ๋ฅด๊ฒ ๋ง๋ค์ด ์ฃผ๋๋ก ํ๊ฒ ๋ค.
class SimpleCountWidgetReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget
get() = CounterWidget
}
ํด๋์ค๋ฅผ ๋ง๋ค์ด GlanceAppWidgetReceiver๋ฅผ ์์๋ฐ์ ํ, glanceAppWidget ๋ณ์๋ฅผ ๋ฐ์์ get() ๋ถ๋ถ์ ์์ ์ด ์์ฑํ ์์ ฏ ์ด๋ฆ์ ๋ฃ์ด ์ค๋ค.
์์ ฏ ๋ฆฌ์๋ฒ ์์ฑ๊น์ง ์๋ฃํ์ผ๋ฉด ์ง์ง๋ก manifest์ ๋ฆฌ์๋ฒ๋ฅผ ๋ฃ์ด์ฃผ๋ ์์ ์ ํด์ฃผ๊ฒ ๋ค.
๋ฆฌ์๋ฒ๋ application ์์ ๋ฃ์ด์ฃผ์.
<receiver android:name=".SimpleCountWidgetReceiver" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/counter_widget_info" />
</receiver>
receiver name์ ๋ฐ๋ก ์ ์ ๋ง๋ GlanceAppWidgetReceiver ํด๋์ค๋ฅผ ๋ฃ์ด์ฃผ๊ณ , meta data์ resource์ ์ด์ ์ ์์ฑํ xml ํ์ผ์ ๋ฃ์ด์ค๋ค.
intent filter์ name๊ณผ meta data์ name์ ์๋์์ฑ์ด ์ ๋๋ ๊ฒฝ์ฐ๊ฐ ์์ ๊ฑด๋ฐ, ๋นํฉํ์ง ๋ง๊ณ ์ ํํ ํ์ดํ์ ํด์ฃผ๊ธฐ๋ง ํ๋ฉด ์ ํ ๋ฌธ์ ๋ ๊ฒ ์์ผ๋ ์์ฌํด๋ ๋๋ค.
์์ ์์ ๊น์ง ์๋ฃํ์ผ๋ฉด, ๊ฒฐ๊ณผ๋ฅผ ํ์ธํด ๋ณด์.
5. ๊ฒฐ๊ณผ
์ง์ ๋ง๋ ์์ ฏ์ด ์ ์์ ์ผ๋ก ์ ๋ก๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
์์ ฏ์ ๋ฃ์ด์ฃผ๊ณ ๋๋ฉด, ์ฒ์์๋ ๋ค์๊ณผ ๊ฐ์ด ์์ ฏ์ด ์์ฑ๋ ๊ฒ์ด๋ค.
์ถ๊ฐ ๋ฒํผ์ ํด๋ฆญํ๋ฉด text๊ฐ ์ ์์ ์ผ๋ก count ๋์ด ๋ฐ๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
์ ๋ฆฌ
- compose๋ก ์์ ฏ์ ๋ง๋ค์ด๋ณผ ๊ธฐํ๊ฐ ์๊ธฐ๋ ํ๊ณ , ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์๋ ์ค๋ ๋ชฐ๋์๋๋ฐ ์ปจํผ๋ฐ์ค์์ ์ฐ์ฐํ ๋ฃ๊ฒ ๋ ๋ด์ฉ์ผ๋ก glance๋ฅผ ๊ณต๋ถํ์ฌ ์ง์ ์์ ๋ฅผ ๋ง๋ค์ด ๋ณด์๋ค. ์ปจํผ๋ฐ์ค ๊ฐ์ฌ๋๊ป ๊ฐ์ฌ๋ฅผ ํํ๋ค :)
- glance๋ ์์ง ์ถ์ ์ด๊ธฐ์ธ๋ฐ๋ ์๋นํ ์์ฑ๋๊ฐ ๋์ ๋ณด์๋ค. ์์ ์ด ์ํ๋ ์์ ฏ์ ์์ ๋กญ๊ฒ ๋ง๋ค ์ ์๋ค๋ ์ ์ด ์ฅ์ ๊ฐ๋ค. ๋จ์ ์ ๋ฝ์๋ฉด glance์ composable๊ณผ ๊ธฐ์กด compose์ composable์ ์ด๋ฆ์ด ๋๊ฐ์์ ํผ๋์ด ์ค๋ ์ ๋?
- ์๊ฐ์ด ์ง๋๋ฉด์ ์์ฐ ์ ์ฉํ compose ์ ์ฉ ์์ ฏ ์์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ ๊ฒ ๊ฐ๋ค.
glance์ ๊ดํ ์ ์ฉํ ์ ๋ณด์ ์ ๊ธ์ ์๋ชป๋ ์ ์ ์๊ณ ๊ณ์ ๋ถ๋ค์ ๋๊ธ์ ์ ๊ทน ํ์์ ๋๋ค.
'๐ฑ| Android > ๐ | Jetpack' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Android, Kotlin] jetpack navigation์์ safe args๋ก ๋ฐ์ดํฐ ์ ๋ฌํ๊ธฐ (0) | 2023.09.26 |
---|---|
[Android, Kotlin] compose custom theme ๋ง๋ค๊ธฐ (0) | 2023.08.18 |
[Android, Kotlin] compose๋ก custom checkbox ๋ง๋ค๊ธฐ (0) | 2023.08.18 |
[Android, Kotlin] compose๋ก tablayout ๊ตฌํํ๊ธฐ (2) | 2023.08.15 |
[Android, Kotlin] compose navigation์์ data class tpye ๋๊ธฐ๊ธฐ (2) | 2023.08.11 |