본문 바로가기
개발/안드로이드

위젯 만들기 (w/ Glance)

by EPdev 2024. 8. 6.
728x90

목적

Compose에서 Glance를 이용해서 Widget을 만들어본다

 

Widget이란?

안드로이드 앱의 소형 버전을 바탕화면에 간략하게 보여줄 수 있는 기능

 

방법

Dependency 추가

- libs.versions.toml

[versions]
glance = "1.1.0"

[libraries]
glance-appwidget = { group = "androidx.glance", name = "glance-appwidget", version.ref = "glance" }
glance-material = { group = "androidx.glance", name = "glance-material", version.ref = "glance" }

 

- build.gradle.kts

dependencies {
    implementation(libs.glance.appwidget)
    implementation(libs.glance.material)
}

 

AppWidget 기본 코드 추가

- MyAppWidget

class MyAppWidget : GlanceAppWidget() {
    override suspend fun provideGlance(context: Context, id: GlanceId) {
        provideContent {
            MyContent()
        }
    }
}

 

- MyAppWidgetReceiver

class MyAppWidgetReceiver: GlanceAppWidgetReceiver() {
    override val glanceAppWidget: GlanceAppWidget
        get() = MyAppWidget()
}

 

xml package 아래에 my_app_widget_info.xml 생성 및 AppWidgetProviderInfo 메타데이터 추가

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="100dp"
    android:minHeight="50dp"
    android:minResizeWidth="50dp"
    android:minResizeHeight="50dp"
    android:resizeMode="horizontal|vertical"
    >

</appwidget-provider>

 

 AndroidManifest.xml

<receiver
    android:name=".glance.MyAppWidgetReceiver"
    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/my_app_widget_info" />
</receiver>

 

UI 꾸미기

- my_app_widget_info.xml 내에 필요한 속성들을 공식 홈페이지 확인 후 추가. API 버전에 따라 지원 속성이 다르니 별도 구성 필요

- MyContent() 는 Glance Composable 코드로 아래와 같이 만듦

import android.content.Context
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.LocalSize
import androidx.glance.action.actionStartActivity
import androidx.glance.action.clickable
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.SizeMode
import androidx.glance.appwidget.lazy.LazyColumn
import androidx.glance.appwidget.provideContent
import androidx.glance.background
import androidx.glance.layout.Alignment
import androidx.glance.layout.Column
import androidx.glance.layout.Spacer
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.height
import androidx.glance.layout.padding
import androidx.glance.text.FontWeight
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import com.example.glancetest.MainActivity
import com.example.glancetest.R

class MyAppWidget : GlanceAppWidget() {

    companion object {
        val SMALL_WIDGET = DpSize(100.dp, 100.dp)
        val MEDIUM_WIDGET = DpSize(250.dp, 100.dp)
        val LARGE_WIDGET = DpSize(250.dp, 250.dp)
    }

    override val sizeMode: SizeMode
        get() = SizeMode.Responsive(
            setOf(
                SMALL_WIDGET,
                MEDIUM_WIDGET,
                LARGE_WIDGET,
            )
        )


    override suspend fun provideGlance(context: Context, id: GlanceId) {
        provideContent {
            GlanceTheme {
                MyContent()
            }
        }
    }

    @Composable
    private fun MyContent() {
        val widgetSize = LocalSize.current

        Column(
            modifier = GlanceModifier.fillMaxSize()
                .background(color = MaterialTheme.colorScheme.background)
                .clickable(actionStartActivity<MainActivity>()),
            verticalAlignment = Alignment.CenterVertically,
            horizontalAlignment = Alignment.CenterHorizontally,
        ) {


            when (widgetSize) {
                SMALL_WIDGET -> {
                    Image(
                        provider = ImageProvider(R.drawable.ic_launcher_foreground),
                        contentDescription = "icon"
                    )
                }

                MEDIUM_WIDGET -> {
                    Text(
                        text = "Hello, Glance!",
                        style = TextStyle(
                            color = GlanceTheme.colors.primary,
                            fontSize = 16.sp,
                            fontWeight = FontWeight.Bold
                        )
                    )

                    Spacer(modifier = GlanceModifier.height(8.dp))

                    Text(
                        text = "This is a medium widget",
                        style = TextStyle(
                            color = GlanceTheme.colors.secondary,
                            fontSize = 14.sp,
                        )
                    )
                }

                LARGE_WIDGET -> {
                    Text(
                        text = "This is a large widget",
                        style = TextStyle(
                            color = GlanceTheme.colors.primary,
                            fontSize = 16.sp,
                            fontWeight = FontWeight.Bold
                        )
                    )

                    Spacer(modifier = GlanceModifier.height(8.dp))

                    LazyColumn(
                        modifier = GlanceModifier.fillMaxWidth(),
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {
                        items(30) { index ->
                            Text(
                                text = "item ${index + 1}",
                                modifier = GlanceModifier.fillMaxWidth()
                                    .background(color = MaterialTheme.colorScheme.inversePrimary)
                                    .padding(4.dp),
                                )

                            if (index < 29) {
                                Spacer(modifier = GlanceModifier.height(4.dp))
                            }
                        }
                    }
                }
            }
        }
    }
}

 

유의깊게 봐야할 것은 import 부분에 androidx.glance 로 시작하는 부분. compose 처럼 구성되어 있지만 Widget은 Glance를 라이브러리를 사용하기 때문에, compose랑 코드가 약간 다름.

 

한가지 참고사항. Glance는 빌드할 때마다 업데이트가 자동으로 되지 않음. 즉, 빌드하고 기존 Widget으로 사용하고 있던 것이 있다면 삭제하거나 새로 Widget을 생성해야함

 

바탕화면에 Widget 추가하는 방법

 

 

Widget

끝!

728x90

댓글