๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๊ณณ๊ฐ„์—์„œ ์ธ์‹ฌ๋‚œ๋‹ค/์‹ค๋ฌด

[Android][Compose][์‚ฝ์งˆ ๊ตํ›ˆ] Compose Migration + Expandable List

์‹œ์ž‘

๊ธฐ์กด ํ”„๋กœ์ ํŠธ๋Š” ์•„์ง๋„ deprecated ๋œ jcenter repository์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์„ ์“ฐ๊ณ  ์žˆ๋‹ค. Gradle 8.0์˜ ๊ธฐ๋Šฅ์„ ์˜จ์ „ํžˆ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ € jcenter์— ๋ฌผ๋ฆฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์„ ๊ต์ฒดํ•ด์•ผ ํ•œ๋‹ค.

๊ต์ฒด ๋Œ€์ƒ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ค‘ ExpandedRecyclerView๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์žˆ๋‹ค. ์•ฑ์—์„œ ๋งŽ์ด ์“ฐ์ด๋Š” ๊ธฐ๋Šฅ๋„ ์•„๋‹ˆ๊ณ , ์•ฝ๊ด€ ์„ค๋ช…์„ ์œ„ํ•ด ๊ฐ„๋‹จํžˆ ์“ฐ์ด๋Š” ๊ฒƒ ๋ฟ์ด๋ผ์„œ ์ง์ ‘ ๋งŒ๋“ค๊ธฐ๋กœ ํ–ˆ๋‹ค.

Compose๋กœ ๋งŒ๋“œ๋Š” ๊ฒŒ ๋” ๊ฐ„๋‹จํ•  ๊ฒƒ ๊ฐ™์•„์„œ ์‹œ์ž‘ํ–ˆ๋‹ค.

 

Gradle ์„ค์ •

์ฐธ๊ณ  : https://velog.io/@blucky8649/Android-%EA%B8%B0%EC%A1%B4-%EC%95%B1%EC%97%90-Compose-UI-%EC%A0%81%EC%9A%A9-%EC%8B%9C%ED%82%A4%EA%B8%B0-ComposeView

 

build.gradle ํŒŒ์ผ ์„ค์ •์„ ํ•œ๋‹ค.

์ •๋ง ํ•„์š”ํ•œ dependency๋งŒ ์ถ”๊ฐ€ํ•˜๋ ค๊ณ  ํ–ˆ๋‹ค.

์‚ฝ์งˆ ํฌ์ธํŠธ๋“ค์ด ์žˆ๋‹ค.

android {
    ...
    buildFeatures {
        ...
        compose = true
    }
    
    composeOptions {
        // ์‚ฝ์งˆ ํฌ์ธํŠธ 1 : ์š” ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”ํ‹€๋ฆฐ ๋ฒ„์ „์— ๋งž์ถฐ์•ผ ํ•œ๋‹ค.
        kotlinCompilerExtensionVersion '1.3.1'
    }
}

dependencies {
    ...
    // Compose
    def compose_version = "1.3.3"
    implementation 'androidx.activity:activity-compose:1.6.1'
    implementation "androidx.compose.ui:ui:$compose_version"
    implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
    // ์‚ฝ์งˆ ํฌ์ธํŠธ 2 : Preview๋ฅผ ์œ„ํ•ด ํ•„์š”ํ•˜๋‹ค.
    implementation "androidx.compose.ui:ui-tooling:$compose_version"
    implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
    debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
    // ์‚ฝ์งˆ ํฌ์ธํŠธ 3 : LazyColumn์„ ์“ฐ๊ธฐ ์œ„ํ•ด ํ•„์š”ํ–ˆ๋‹ค.
    implementation "androidx.compose.material:material:1.3.1"
}

 

์‚ฝ์งˆ ํฌ์ธํŠธ 1

์ฒ˜์Œ์— kotlinCompilerExtensionVersion  ๋ฒ„์ „์„ '1.1.1'๋กœ ๋งž์ท„๋‹ค. ์œ„์— ์˜ฌ๋ ค๋‘” ์ฐธ์กฐํ•œ ๋ธ”๋กœ๊ทธ์—์„œ ๋ณต์‚ฌ ๋ถ™์—ฌ๋„ฃ๊ธฐ ํ•œ ๋ฒ„์ „์ด์—ˆ๋‹ค.

Build๊ฐ€ Fail ๋๊ณ , ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—๋Ÿฌ๋ฅผ ๋ฐœ๊ฒฌํ–ˆ๋‹ค. 

e: This version (1.1.1) of the Compose Compiler requires Kotlin version 1.6.10 but you appear to be using Kotlin version 1.7.10 which is not known to be compatible.  Please fix your configuration (or `suppressKotlinVersionCompatibilityCheck` but don't say I didn't warn you!).

Compose Compiler ๋ฒ„์ „์ด 1.1.1์ธ๋ฐ Kotlin version์ด 1.6.1์ด ์•„๋‹ˆ๊ณ  1.7.10์ด๋ผ์„œ ๋ฌธ์ œ๊ฐ€ ๋œ๋‹ค๊ณ  ํ–ˆ๋‹ค. '๋‚ด๊ฐ€ ๊ฒฝ๊ณ  ์•ˆํ–ˆ๋‹ค๊ณ  ๋งํ•˜์ง€ ๋งˆ๋ผ!'๋Š” ํ™”๋”ฑ์ง€๊ฐ€ ๋‹ด๊ธด ์—๋Ÿฌ๋ผ ์ธ์ƒ๊นŠ์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ฐพ์•„๋ดค๋‹ค.  

https://developer.android.com/jetpack/androidx/releases/compose-kotlin?hl=ko#pre-release_kotlin_compatibility
 

Compose์™€ Kotlin์˜ ํ˜ธํ™˜์„ฑ ์ง€๋„  |  Android ๊ฐœ๋ฐœ์ž  |  Android Developers

Compose์™€ Kotlin์˜ ํ˜ธํ™˜์„ฑ ์ง€๋„ ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด ์ •๋ฆฌํ•˜๊ธฐ ๋‚ด ํ™˜๊ฒฝ์„ค์ •์„ ๊ธฐ์ค€์œผ๋กœ ์ฝ˜ํ…์ธ ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•˜์„ธ์š”. ์ข…์† ํ•ญ๋ชฉ ์„ ์–ธ Compose ์ปดํŒŒ์ผ๋Ÿฌ์— ๊ด€ํ•œ ์ข…์† ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด ํ”„๋กœ์ ํŠธ์— Googl

developer.android.com

chttps://developer.android.com/jetpack/androidx/releases/compose-kotlin?hl=ko#pre-release_kotlin_compatibility

1.1.1 ์—์„œ 1.3.1๋กœ ์ˆ˜์ •ํ•˜๋‹ˆ ๋นŒ๋“œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ด๋ค„์กŒ๋‹ค.

 

 

์‚ฝ์งˆ ํฌ์ธํŠธ 2

Compose Preview๋ฅผ ์œ„ํ•ด์„œ๋„ dependency๊ฐ€ ํ•„์š”ํ–ˆ๋‹ค. ์‚ฝ์งˆํ•˜๋ฉด์„œ ์ € 3์ค„์ด ํ•„์š”ํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•˜๋‹ค.

 

์‚ฝ์งˆ ํฌ์ธํŠธ 3

RecyclerView ๋Œ€์‹  LazyColumn์„ ์“ฐ๊ณ ์ž ํ–ˆ๋Š”๋ฐ, LazyColumn์˜ ๋ ˆํผ๋Ÿฐ์Šค๋ฅผ ์ฐพ์„ ์ˆ˜๊ฐ€ ์—†์—ˆ๋‹ค. ๋‹ค๋ฅธ ์ปดํฌ์ฆˆ ํ”„๋กœ์ ํŠธ์—์„œ LazyColum์˜ ํŒจํ‚ค์ง€๋ฅผ ํ™•์ธํ–ˆ๋‹ค.

LazyColum์˜ ํŒจํ‚ค์ง€

ํ”„๋กœ์ ํŠธ์— ์•„์˜ˆ androidx.compose.foundation ํŒจํ‚ค์ง€๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์•˜๋‹ค.

 

๊ฒ€์ƒ‰ํ•ด๋ณด๋‹ˆ Compose Foundation ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

https://developer.android.com/jetpack/androidx/releases/compose-foundation 

 

Compose ๊ธฐ์ดˆ  |  Android ๊ฐœ๋ฐœ์ž  |  Android Developers

์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด ์ •๋ฆฌํ•˜๊ธฐ ๋‚ด ํ™˜๊ฒฝ์„ค์ •์„ ๊ธฐ์ค€์œผ๋กœ ์ฝ˜ํ…์ธ ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•˜์„ธ์š”. Compose ๊ธฐ์ดˆ ์ฆ‰์‹œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ตฌ์„ฑ์š”์†Œ๋ฅผ ์‚ฌ์šฉํ•ด Jetpack Compose ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ž‘์„ฑํ•˜๊ณ  ๊ธฐ์ดˆ๋ฅผ ํ™•์žฅํ•ด ๋‚˜๋งŒ

developer.android.com

์—ฌ๊ธฐ์„œ ๊ณ ๋ฏผํ–ˆ๋˜ ๊ฒƒ์ด, ์ € compose foundation๋งŒ์„ ์ถ”๊ฐ€ํ•  ๊ฒƒ์ด๋ƒ, ์•„๋‹ˆ๋ฉด ์ € compose foundation์„ ์ด๋ฏธ ํฌํ•จํ•˜๊ณ  ์žˆ๋Š”  ๋‹ค๋ฅธ library๋ฅผ ์ถ”๊ฐ€ํ•  ๊ฒƒ์ด๋ƒ ์˜€๋‹ค. LazyColumn์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋˜ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” compose foundation์ด compose material๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋˜ ๊ฑฐ์˜€๋‹ค.

dependencies

compose foundation๋งŒ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค compose material์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์žฅ๊ธฐ์ ์œผ๋กœ ๋ณผ ๋•Œ ๋œ ์‚ฝ์งˆํ•  ๊ฒƒ ๊ฐ™์•„์„œ compose material์„ ์ถ”๊ฐ€ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค. compose material ํ•˜๋‚˜๋ฉด ๋‹ค๋ฅธ androidx ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์˜ dependency ์ถ”๊ฐ€๊ฐ€ ์ค„์–ด๋“ค์ง€ ์•Š์„๊นŒ ์‹ถ์—ˆ๋‹ค.

 

XML ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ง„ํ–‰

https://developer.android.com/codelabs/jetpack-compose-migration?hl=ko#4 

 

Jetpack Compose๋กœ ์ด์ „  |  Android Developers

์ด Codelab์—์„œ๋Š” ๋ทฐ ์‹œ์Šคํ…œ์˜ ํ™”๋ฉด ์ผ๋ถ€๋ฅผ Jetpack Compose๋กœ ์ด์ „ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ด…๋‹ˆ๋‹ค.

developer.android.com

 

๊ธฐ์กด xml ์ฝ”๋“œ์˜ RecyclerView ์œ„์ ฏ์„ ์‚ญ์ œํ•˜๊ณ  ๊ทธ ์ž๋ฆฌ์— ComposeView ์œ„์ ฏ์„ ๋„ฃ์—ˆ๋‹ค.

 

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        
        ...
        
        <androidx.compose.ui.platform.ComposeView
            android:id="@+id/composeView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
            
        <!--<androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />-->
            
    </LinearLayout>
</layout>

 

ViewModel ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ง„ํ–‰

๊ธฐ์กด์— ์“ฐ๋˜ ExpandedRecyclerView๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€ํ˜•ํ•˜๋Š” ์ž‘์—…์„ ๋นผ๋Š” ๊ฒƒ ์™ธ์—๋Š” ํ•  ๊ฒŒ ๊ฑฐ์˜ ์—†์—ˆ๋‹ค.

๊ธฐ์กด์˜ livedata๋ฅผ ๊ทธ๋Œ€๋กœ ๊ฐ€์ ธ๋‹ค ์“ฐ๋ฉด ๋  ์ •๋„๋ผ์„œ ํŽธํ–ˆ๋‹ค.

 

Activity ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ง„ํ–‰

๊ธฐ์กด์— ์“ฐ๋˜ ExpandedRecyclerView์™€ ๊ด€๋ จ๋œ ์ฝ”๋“œ๋“ค์€ ๋‹ค ์ฃผ์„์ฒ˜๋ฆฌํ–ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ์‚ฝ์งˆ/๊ณ ๋ฏผ ํฌ์ธํŠธ๋“ค์ด ๋ช‡ ๊ฐ€์ง€ ์žˆ์—ˆ๋‹ค.

 

์‚ฝ์งˆ ํฌ์ธํŠธ4

์ด ํšŒ์‚ฌ์˜ ๋””์ž์ธ ๊ฐ€์ด๋“œ๋Š” ํ…์ŠคํŠธ ํฌ๊ธฐ๋ฅผ sp๊ฐ€ ์•„๋‹ˆ๋ผ dp ๋‹จ์œ„๋กœ ์ง€์ •ํ•œ๋‹ค. ํ…์ŠคํŠธ๋ฅผ sp ๋Œ€์‹  dp๋กœ ์ง€์ •ํ•˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ๋””์Šคํ”Œ๋ ˆ์ด ์„ค์ •์œผ๋กœ ํ™”๋ฉด ๋ฐ ๊ธ€์ž ํฌ๊ธฐ ์„ค์ •์„ ๋ณ€๊ฒฝํ•ด๋„ ํ…์ŠคํŠธ์˜ ํฌ๊ธฐ๋Š” ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š”๋‹ค. ์‚ฌ์‹ค ์‚ฌ์šฉ์ž๋ฅผ ๊ณ ๋ คํ•˜๋‹ค๋ฉด ๋ถˆ์นœ์ ˆํ•œ๊ฑฐ๊ณ , ๋””์ž์ธ์„ ๊ณ ๋ คํ•œ๋‹ค๋ฉด ์‚ฌ์šฉ์ž ์„ค์ •์— ์˜ํ•ด ๋””์ž์ธ์ด ๊นจ์ง€๋Š” ์ƒํ™ฉ์€ ์—†๋‹ค.

Composable๋“ค์˜ fontSize๋Š” dp๋กœ ์ง€์ •ํ•˜๋Š” ๊ธฐ๋ณธ ๊ธฐ๋Šฅ ์ž์ฒด๊ฐ€ ์—†์—ˆ๋‹ค. 

 

Text

https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#Text(androidx.compose.ui.text.AnnotatedString,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.unit.TextUnit,androidx.compose.ui.text.font.FontStyle,androidx.compose.ui.text.font.FontWeight,androidx.compose.ui.text.font.FontFamily,androidx.compose.ui.unit.TextUnit,androidx.compose.ui.text.style.TextDecoration,androidx.compose.ui.text.style.TextAlign,androidx.compose.ui.unit.TextUnit,androidx.compose.ui.text.style.TextOverflow,kotlin.Boolean,kotlin.Int,kotlin.Int,kotlin.collections.Map,kotlin.Function1,androidx.compose.ui.text.TextStyle)
 

androidx.compose.material  |  Android Developers

androidx.car.app.managers

developer.android.com

Text์˜ ํฐํŠธ ์‚ฌ์ด์ฆˆ๋Š” TextUnit์œผ๋กœ ์„ค์ •ํ•˜๋Š” ๊ฑฐ์˜€๋‹ค.&nbsp;https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#Text(androidx.compose.ui.text.AnnotatedString,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.unit.TextUnit,androidx.compose.ui.text.font.FontStyle,androidx.compose.ui.text.font.FontWeight,androidx.compose.ui.text.font.FontFamily,androidx.compose.ui.unit.TextUnit,androidx.compose.ui.text.style.TextDecoration,androidx.compose.ui.text.style.TextAlign,androidx.compose.ui.unit.TextUnit,androidx.compose.ui.text.style.TextOverflow,kotlin.Boolean,kotlin.Int,kotlin.Int,kotlin.collections.Map,kotlin.Function1,androidx.compose.ui.text.TextStyle)

TextUnit

https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/TextUnit

 

TextUnit  |  Android Developers

androidx.car.app.managers

developer.android.com

 

TextUnit์€ ์˜ค์ง sp์™€ em๋งŒ ๋‹ค๋ฃฌ๋‹ค.

TextUnitType์€ sp์™€ em๋งŒ ์กด์žฌํ•œ๋‹ค.

https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/TextUnitType

 

TextUnitType  |  Android Developers

androidx.car.app.managers

developer.android.com

 

dp๋Š” ์—†๋Š” TextUniType.&nbsp;https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/TextUnitType

 

StackOverFlow์—์„œ ๋ฐฉ๋ฒ•์„ ์ฐพ์•˜๋‹ค.

https://stackoverflow.com/a/68603541

 

How to convert TextUnit to Dp in Jetpack Compose?

I know the difference between them. I want to calculate the text height base on lineHeight. The value of lineHeight is in TextUnit so I want to convert it into Dp.

stackoverflow.com

 

extension ํ•จ์ˆ˜๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ Int ๊ฐ’์„ dp > sp ๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋ฉด ๋˜๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.

 

์‚ฝ์งˆ ํฌ์ธํŠธ5

Compose๊ฐ€ ์ž๊ธฐ๋ฉ‹๋Œ€๋กœ ์•„์ด์ฝ˜ ์ƒ‰์„ ๋ฐ”๊ฟ”๋ฒ„๋ ธ๋‹ค. ์›๋ž˜ ๋ฆฌ์†Œ์Šค ์ƒ‰์€ ํšŒ์ƒ‰์ธ๋ฐ ์‹ค์ œ๋กœ๋Š” ๊ฒ€์ •์ƒ‰์œผ๋กœ ๋…ธ์ถœ๋๋‹ค.

StackOverFlow์—์„œ ์ด๊ฒƒ์„ ๋ง‰๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ์•˜๋‹ค.

https://stackoverflow.com/a/68238805

 

How to avoid tinting Icon with painterResource().It paints my vector in Black

Okay so I have created my own .SVG vector icon and imported it as an XML in Android Studio. Now I'm trying to create an Icon using that same vector. However when I specify that vector in painterRes...

stackoverflow.com

tint ๊ฐ’์„ Color.Unspecified๋กœ ์„ค์ •ํ•˜๋ฉด ๋๋‹ค.

 

์‚ฝ์งˆ ํฌ์ธํŠธ6์ด๋ผ๊ณ  ์ผ์ง€๋งŒ ์‚ฌ์‹ค ์ •๋ง ํŽธํ•˜๊ฒŒ ๋งŒ๋“  ๊ฒƒ

๋ผ์ธ ํ•œ ์ค„ ๊ทธ๋ ค๋„ฃ์–ด์•ผ ํ•˜๋Š”๋ฐ ๋ญ ์—†๋‚˜ ์‹ถ์—ˆ๋‹ค.

Divider๋ผ๋Š” Composable์ด ์žˆ์—ˆ๋‹ค. 

https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#Divider(androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.unit.Dp,androidx.compose.ui.unit.Dp)
 

androidx.compose.material  |  Android Developers

androidx.car.app.managers

developer.android.com

 

์‚ฝ์งˆ ํฌ์ธํŠธ7๋ผ๊ณ  ๋ถ€๋ฅด๊ธฐ์—๋Š” ๋‚ด๊ฐ€ ๊ธฐ๋ณธ๊ธฐ๊ฐ€ ๋ถ€์กฑํ–ˆ๋‹ค.

๋งŒ๋“ค๋ ค๊ณ  ํ•œ Expanded List์˜ ์•„์ดํ…œ์€ ๊ธฐ๋ณธ ํ—ค๋”๊ฐ€ ์žˆ๊ณ , ํ—ค๋”๋ฅผ ํด๋ฆญํ•˜๋ฉด ๊ทธ ์•„๋ž˜๋กœ ์ฝ˜ํ…์ธ ๊ฐ€ ๋‹ด๊ธด ๋ทฐ๊ฐ€ ๋…ธ์ถœ๋˜๊ฑฐ๋‚˜ ์‚ฌ๋ผ์ง€๋Š” ๊ฒƒ์ด์—ˆ๋‹ค. ํ—ค๋”๋ฅผ Text๋กœ ๋งŒ๋“ค๊ณ , padding ๊ฐ’๊ณผ clickable ๊ฐ’์„ ์„ค์ •ํ–ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๊ณ  ๋‚˜์„œ ๋ณด๋‹ˆ ํด๋ฆญ ์˜์—ญ์ด ๋‚ด๊ฐ€ ์ƒ๊ฐํ–ˆ๋˜ ์˜์—ญ๊ณผ๋Š” ๋‹ฌ๋ž๋‹ค. ๋‚˜๋Š” padding๋œ ์˜์—ญ๊นŒ์ง€๋„ Text composable์˜ ์˜์—ญ์œผ๋กœ ๋ณด์ด๋‹ˆ๊นŒ ๋‹ค ํด๋ฆญ์ด ๋˜๋Š” ์ค„ ์•Œ์•˜๋Š”๋ฐ, padding๋œ ์˜์—ญ์„ ์ œ์™ธํ•˜๊ณ  ํ…์ŠคํŠธ๊ฐ€ ๋ณด์ด๋Š” ๊ทธ ์˜์—ญ๋งŒ ํด๋ฆญ์ด ๊ฐ€๋Šฅํ–ˆ๋‹ค. Text composable์„ ๋‹ค๋ฅธ wrapper composable๋กœ ๊ฐ์‹ธ๊ณ , ๊ทธ wrapper composable์— clickable ๊ฐ’์„ ์„ค์ •ํ–ˆ๋‹ค. ์ด์ œ์•ผ ๋‚ด๊ฐ€ ์›ํ–ˆ๋˜ Text ์˜์—ญ ์ „๋ถ€์— ํด๋ฆญ ์ด๋ฒคํŠธ๊ฐ€ ๋จนํ˜”๋‹ค.

* ์ถ”๊ฐ€ : ๊ธ€์„ ๋‹ค ์“ฐ๊ณ  ๋‚˜์„œ ๋‚˜์ค‘์— ์•Œ์•˜๋‹ค. Modifier๋ฅผ ์“ธ ๋•Œ ํ•จ์ˆ˜์˜ ์ˆœ์„œ๊ฐ€ ์ค‘์š”ํ•œ ๊ฑฐ์˜€๋‹ค. padding()์„ ๋‹ค๋ฅธ ํ•จ์ˆ˜์™€ ๊ฐ™์ด ์“ฐ๋Š” ์ˆœ์„œ์— ๋”ฐ๋ผ์„œ ๊ธฐ์กด ๋ทฐ์—์„œ ์“ฐ๋˜ margin์ด๋‚˜ padding ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ์•„๋งˆ ๋‚ด๊ฐ€ Text์˜ Modifier๋ฅผ ์„ค์ •ํ–ˆ์„ ๋•Œ, padding()์„ ์“ฐ๊ณ  ๊ทธ ์•„๋ž˜์— clickable()์„ ์ผ์—ˆ๋‚˜๋ณด๋‹ค. ์‚ฝ์งˆ์ด๋ผ๊ณ  ๋ถ€๋ฅด๊ธฐ์—๋Š” ๋ถ€๋„๋Ÿฌ์šด ๊ธฐ๋ณธ๊ธฐ ๋ถ€์กฑ๐Ÿ™Š ๋ ˆ์ด์•„์›ƒ ๊ณต๋ถ€ํ•˜๋Ÿฌ ๊ฐ€์•ผ๊ฒ ๋‹ค. 

 

Compose์˜ ๋ ˆ์ด์•„์›ƒ  |  Jetpack Compose  |  Android Developers

Compose์˜ ๋ ˆ์ด์•„์›ƒ ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด ์ •๋ฆฌํ•˜๊ธฐ ๋‚ด ํ™˜๊ฒฝ์„ค์ •์„ ๊ธฐ์ค€์œผ๋กœ ์ฝ˜ํ…์ธ ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•˜์„ธ์š”. Jetpack Compose๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์•ฑ์˜ UI๋ฅผ ํ›จ์”ฌ ์‰ฝ๊ฒŒ ๋””์ž์ธํ•˜๊ณ  ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฌธ์„œ์—์„œ

developer.android.com

 

์‚ฝ์งˆ ํฌ์ธํŠธ8

ํด๋ฆญ ์ด๋ฒคํŠธ์˜ ๋ฆฌํ”Œ ํšจ๊ณผ๊ฐ€ ๋ณด๊ธฐ ์‹ซ์—ˆ๋‹ค. ์ด๊ฒƒ์„ ์ œ๊ฑฐํ•˜๊ณ  ์‹ถ์—ˆ๋‹ค. 

Compose๋กœ ๋งŒ๋“  Expanded List ์˜ˆ์ œ๋“ค์„ ๋ณด๋‹ค๊ฐ€ ๋ฐฉ๋ฒ•์„ ์•Œ์•˜๋‹ค. ์ฐธ๊ณ ํ•œ ๊ธ€์ด๋‹ค.

https://johncodeos.com/how-to-make-expandable-list-with-jetpack-compose/

 

How to make Expandable List with Jetpack Compose | John Codeos - Blog with Free iOS & Android Development Tutorials

In this Jetpack Compose tutorial, you'll learn how to create an expandable list. It's the equivalent of the Expandable RecyclerView that we do in XML.

johncodeos.com

 

clickable ์„ค์ •์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•˜๋ฉด ๋๋‹ค.

modifier = Modifier
            .clickable(
                indication = null,
                interactionSource = remember {
                    MutableInteractionSource()
                },
                onClick = { /** ์ž‘์„ฑ **/ })

 

์‚ฝ์งˆ ํฌ์ธํŠธ9๊ฐ€ ๋  ๋ป”ํ•œ ๊ฒƒ

์•„์ดํ…œ์ด ํ™•์žฅํ•˜๊ฑฐ๋‚˜ ์ถ•์†Œ๋  ๋•Œ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋“ค์–ด๊ฐ€๊ธธ ์›ํ–ˆ๋‹ค. ์—„์ฒญ๋‚œ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์›ํ•œ ๊ฒƒ์ด ์•„๋‹ˆ๋ผ์„œ, ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•˜๊ณ  ์‹ถ์—ˆ๋‹ค.

 

์• ๋‹ˆ๋ฉ”์ด์…˜  |  Jetpack Compose  |  Android Developers

์• ๋‹ˆ๋ฉ”์ด์…˜ ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด ์ •๋ฆฌํ•˜๊ธฐ ๋‚ด ํ™˜๊ฒฝ์„ค์ •์„ ๊ธฐ์ค€์œผ๋กœ ์ฝ˜ํ…์ธ ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•˜์„ธ์š”. Jetpack Compose์—์„œ๋Š” ์•ฑ UI์—์„œ ๋‹ค์–‘ํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•˜๋„๋ก ์ง€์›ํ•˜๋Š” ๊ฐ•๋ ฅํ•˜๊ณ  ํ™•์žฅ ๊ฐ€

developer.android.com

 Modifier์— animateContentSize()๋ผ๋Š” ๊ธฐ๋Šฅ์ด ์žˆ๋Š” ๊ฒƒ์„ ์ฐพ์•˜๋‹ค.  ์ด๊ฒƒ์œผ๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋„ฃ์—ˆ๋‹ค.

 

์‚ฝ์งˆ ํฌ์ธํŠธ10

์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•  ๊ฒƒ์ธ๊ฐ€๋ฅผ ๊ณ ๋ฏผํ–ˆ๋‹ค. ์•„์ดํ…œ composable์„ stateful ํ•˜๊ฒŒ ๋งŒ๋“ค ๊ฒƒ์ธ๊ฐ€, statelessํ•˜๊ฒŒ ๋งŒ๋“ค ๊ฒƒ์ธ๊ฐ€ ํ•˜๋Š” ๊ณ ๋ฏผ์ด์—ˆ๋‹ค.

https://developer.android.com/jetpack/compose/state#stateful-vs-stateless
 

์ƒํƒœ ๋ฐ Jetpack Compose  |  Android Developers

์ƒํƒœ ๋ฐ Jetpack Compose ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด ์ •๋ฆฌํ•˜๊ธฐ ๋‚ด ํ™˜๊ฒฝ์„ค์ •์„ ๊ธฐ์ค€์œผ๋กœ ์ฝ˜ํ…์ธ ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•˜์„ธ์š”. ์•ฑ์˜ ์ƒํƒœ๋Š” ์‹œ๊ฐ„์ด ์ง€๋‚จ์— ๋”ฐ๋ผ ๋ณ€ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ’์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋งค์šฐ ๊ด‘๋ฒ”์œ„ํ•œ ์ •

developer.android.com

 

๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด

Stateful Composable

- ํ˜ธ์ถœ์ž๊ฐ€ ์ƒํƒœ๋ฅผ ์ œ์–ดํ•  ํ•„์š”๊ฐ€ ์—†์„ ๋•Œ ํŽธํ•˜๋‹ค.

- ์žฌ์‚ฌ์šฉํ•˜๊ธฐ ์–ด๋ ต๋‹ค.

- ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ต๋‹ค.

 

Stateless Composable

- ํ˜ธ์ถœ์ž๊ฐ€ ์ƒํƒœ๋ฅผ ์ œ์–ดํ•  ํ•„์š”๊ฐ€ ์žˆ์„ ๋•Œ ์œ ์šฉํ•˜๋‹ค.

- ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค.

- ํ…Œ์ŠคํŠธ๊ฐ€ ์šฉ์ดํ•˜๋‹ค.

 

Stateful, Stateless์— ๋Œ€ํ•ด ๋ฌด์—‡์ด ๋” ์ข‹๊ณ  ๋‚˜์˜๊ณ ๋ฅผ ์ด์•ผ๊ธฐํ•˜์ง€ ์•Š์•˜๋‹ค.

๋งŒ๋“ค๊ณ ์ž ํ•˜๋Š” ๊ธฐ๋Šฅ์€ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋…ธ์ถœ๋  ์ผ์ด ์žฆ์ง€๋„ ์•Š๊ณ , ์•„์ดํ…œ์ด ์—ด๋ฆฌ๊ณ  ๋‹ซํžˆ๊ณ  ์ƒํƒœ๋ฅผ ๋ทฐ๋ชจ๋ธ์—๊นŒ์ง€ ์ €์žฅํ•ด์•ผํ•  ์ •๋„๋กœ ์ค‘์š”ํ•˜์ง€๋„ ์•Š๋‹ค. 

Statefulํ•˜๊ฒŒ ๋งŒ๋“ค๊ธฐ๋กœ ํ–ˆ๋‹ค.

 

class AgreementActivity : BaseActivity() {
   ...
   
   override fun onCreate(savedInstanceState: Bundle?) {
      ...
      with(binding.coposeView) {
         setContent {
            val list by viewModel.list.observeAsState()
            list?.let { ExpandealbeList(it) }
         }
      }
   
   }
}

@Composable
fun ExpandableList(list: List<Agreement>) {
    LazyColumn {
        items(list) { agreement -> 
            ExpandableItem(title = agreement.name, content = agreement.content)
        }
    }
}

@Composable
fun ExpandableItem(title : String, content: String) {
    Column(
        modifier = Modifier
                    .fillMaxWidth()
                    .animateContentSize()
    ) {
        var isExpanded by remember {
            mutableStateOf(false)
        }
        Row(
            modifier = Modifier
                        .fillMaxWidth()
                        .height(49.dp)
                        .background(color = Color.White)
                        .cilckable(
                            indication = null,
                            interactionSource = remember {
                                MutableInteractionSource()
                            },
                            onClick = { isExpanded = != isExpanded })
        ) {
            Text(
                text = title,
                color = colorResource(id = R.color.title),
                fontSize = 15.textDp,
                modifier = Modifier
                    .padding(all = 16.dp)
                    .weight(1f)
            )
            Icon(
                painter = painterResource(id = if (isExpanded) R.drawable.arrow_close else R.drwable.arrow_open),
                contentDescription = null,
                modifier = Modifier
                    .align(Alignment.CenterVertically)
                    .padding(10.dp),
                tint = Color.Unspecified
            )
        }
        Divider(color = colorResource(id = R.color.divider), thickness = 1.dp)
        if (isExpanded) {
            Text(
                text = content,
                modifier = Modifier
                    .fillMaxWidth()
                    .background(color = colorResource(id = R.color.content_background)
                    .padding(all = 16.dp),
                color = colorResource(id = R.color.content),
                fontSize = 12.textDp
            )
        }
    }
}

fun Int.textDp(density: Density) : TextUnit = with(density) {
    this@textDp.dp.toSp()
}

val Int.textDp: TextUnit
    @Composable get() = this.textDp(density = LocalDensity.current)

 

 

Stateless ๋ฒ„์ „๋„ ๋งŒ๋“ค์–ด ๋ณด๊ธฐ๋Š” ํ–ˆ๋‹ค. Agreement ๊ฐ์ฒด์— isExpanded ๊ฐ’์ด ์žˆ๋‹ค๋ฉด ์ด๋ ‡๊ฒŒ ์‰ฝ๊ฒŒ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

@Composable
fun ExpandableList(list: List<Agreement>) {
    LazyColumn {
        val expandedStatusList = list.map { it.isExpanded }
        itemsIndexed(list) { index, item -> 
            var isExpanded by remember {
                mutableStateOf(statusList[index])
            }
            ExpandableItem(
                title = item.name, 
                content = item.content,
                isExpanded = isExpanded,
                onClick = { isExpanded = !isExpanded }
            )
        }
    }
}

@Composable
fun ExpandableItem(title: String, content: String, isExpanded: Boolean, onClick: () -> Unit) {
    Column(
        modifier = Modifier
                    .fillMaxWidth()
                    .animateContentSize()
    ) {
        Row(
            modifier = Modifier
                        .fillMaxWidth()
                        .height(49.dp)
                        .background(color = Color.White)
                        .cilckable(
                            indication = null,
                            interactionSource = remember {
                                MutableInteractionSource()
                            },
                            onClick = onClick)
        ) {
            ...
            // ์œ„์˜ ์ฝ”๋“œ์™€ ๋™์ผ
        }
        ...
        // ์œ„์˜ ์ฝ”๋“œ์™€ ๋™์ผ
    }
}

 

์‚ฝ์งˆ ํฌ์ธํŠธ8์—์„œ ์ฐธ์กฐํ•œ ๋ธ”๋กœ๊ทธ์—์„œ์˜ ๋ฐฉ์‹์œผ๋กœ ๋งŒ๋“ค๋ฉด, ์•„์ดํ…œ Composable์ด ๋ชจ๋‘ recomposition ๋œ๋‹ค. Compose์˜ ์žฅ์ ์ด ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š”ํ•œ Compose๋งŒ recomposeํ•œ๋‹ค๋Š” ๊ฒƒ์ธ๋ฐ, ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด๋‹ˆ ์ด ์žฅ์ ์„ ์‚ด๋ฆด ์ˆ˜ ์—†๋Š” ๊ตฌํ˜„์ด๋ผ๋Š” ์ƒ๊ฐ์„ ํ•˜๊ฒŒ ๋๋‹ค. 

๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ Stateless ๋ฒ„์ „์—์„œ๋Š”, ์•„์ดํ…œ Composable์˜ isExpanded ์ƒํƒœ๋ฅผ ๊ฐœ๋ณ„๋กœ ์ €์žฅํ•œ๋‹ค. ๊ทธ๋ž˜์„œ Composable ๊ฐœ๋ณ„๋กœ ์ƒํƒœ๋ฅผ ๊ฐ–๊ณ , ๋ณ€๊ฒฝ๋œ ์ƒํƒœ๋ฅผ ๊ฐ–๋Š” Composable๋งŒ recompose๋œ๋‹ค. ๋ธ”๋กœ๊ทธ ์˜ˆ์‹œ ์ฝ”๋“œ์—์„œ๋Š” isExpanded ๊ฐ’์˜ ๋ฆฌ์ŠคํŠธ ์ž์ฒด๋ฅผ ์ƒํƒœ๋กœ ์ €์žฅํ•œ๋‹ค. ์•„์ดํ…œ์„ ํด๋ฆญํ•˜๋ฉด ๋ฆฌ์ŠคํŠธ ์ •๋ณด๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๊ณ , ๊ทธ๋ ‡๊ฒŒ ์ƒํƒœ(๋ฆฌ์ŠคํŠธ ์ƒํƒœ ์ •๋ณด)์™€ ์—ฐ๊ฒฐ๋œ ๋ชจ๋“  ์•„์ดํ…œ๋“ค์ด recompose๋œ๋‹ค. ์ด๊ฑด ๋‚ด๊ฐ€ ์›ํ•˜๋˜ ๊ทธ๋ฆผ์ด ์•„๋‹ˆ์—ˆ๋‹ค.

 

์‚ฝ์งˆ ํฌ์ธํŠธ11

๊ธฐ์กด ๋ฆฌ์‚ฌ์ดํด๋Ÿฌ๋ทฐ์—๋Š” android:overScrollMode="never" ์„ค์ •์ด ๋˜์–ด ์žˆ๋˜ ๊ฒƒ์„ ํ™•์ธํ–ˆ๋‹ค. 

https://stackoverflow.com/a/69478797

 

Remove LazyColumn overscroll effect in Jetpack Compose

I am using version 1.1.0-alpha05 of Jetpack Compose and I wanted to know if there is a way to turn off the scrolling effect for LazyColumn like xml (android:overScrollMode="never")?

stackoverflow.com

LazyColumn์˜ wrapper๋กœ CompositionLocalProvider๋ฅผ ๊ฐ์‹ธ์คฌ๊ณ , LocalOverscrollConfiguration์„ null๋กœ ๋„ฃ์—ˆ๋‹ค. compose foundation ๋ฒ„์ „ 1.2.0์„ ์“ฐ๊ณ  ์žˆ์–ด์„œ @OptIn(ExperimentalFoundationApi::class) ์–ด๋…ธํ…Œ์ด์…˜์„ ๋„ฃ์–ด์ค˜์•ผ ํ–ˆ๋‹ค.

๊ฒฐ๊ณผ

๊ธฐ์กด ExpandedRecyclerView ๊ด€๋ จ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ œ๊ฑฐ์— ์„ฑ๊ณตํ–ˆ๋‹ค. 

 

ํ™•์‹คํžˆ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋„ฃ๋Š” ๊ฒƒ์ด ํŽธํ–ˆ๊ณ , RecyclerView๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์— ๋น„ํ•ด ์ฝ”๋“œ๋ฅผ ๋œ ์ผ๋‹ค.