Glance로 앱 위젯 만들기

매니페스트, 메타데이터

다음 섹션에서는 Glance를 사용하여 기본 앱 위젯을 만드는 방법을 설명합니다.

매니페스트에서 AppWidget 선언

설정 단계를 완료한 후 앱에서 AppWidget 및 메타데이터를 선언합니다.

  1. GlanceAppWidgetReceiver에서 AppWidget 수신기를 확장합니다.

    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
        override val glanceAppWidget: GlanceAppWidget = TODO("Create GlanceAppWidget")
    }

  2. AndroidManifest.xml 파일과 연결된 메타데이터 파일에 앱 위젯의 제공자를 등록합니다.

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

AppWidgetProviderInfo 메타데이터 추가

그런 다음 위젯 만들기 가이드에 따라 @xml/my_app_widget_info 파일에서 앱 위젯 정보를 만들고 정의합니다.

Glance의 유일한 차이점은 initialLayout XML이 없다는 점이지만 하나를 정의해야 합니다. 라이브러리에 제공된 사전 정의된 로드 레이아웃을 사용할 수 있습니다.

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/glance_default_loading_layout">
</appwidget-provider>

AppWidgetProviderInfo XML 선언

AppWidgetProviderInfo 객체는 위젯의 필수 속성을 정의합니다. <appwidget-provider> 요소 내 XML 메타데이터 리소스 파일(res/xml/my_app_widget_info.xml)에서 AppWidgetProviderInfo를 정의합니다.

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:targetCellWidth="1"
    android:targetCellHeight="1"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="120dp"
    android:updatePeriodMillis="86400000"
    android:description="@string/example_appwidget_description"
    android:previewLayout="@layout/example_appwidget_preview"
    android:initialLayout="@layout/glance_default_loading_layout"
    android:configure="com.example.android.ExampleAppWidgetConfigurationActivity"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen"
    android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>

위젯 크기 조정 속성

기본 홈 화면은 높이와 너비가 정의된 셀의 그리드를 기반으로 창에 위젯을 배치합니다. 대부분의 홈 화면에서는 위젯이 그리드 셀의 정수 배수인 크기만 사용할 수 있습니다(예: 가로 2개 셀, 세로 3개 셀).

위젯 크기 조정 속성을 사용하면 위젯의 기본 크기를 지정하고 위젯 크기의 하한과 상한을 제공할 수 있습니다. 이 컨텍스트에서 위젯의 기본 크기는 위젯이 홈 화면에 처음 추가될 때의 크기입니다.

다음 표에서는 위젯 크기와 관련된 <appwidget-provider> 속성을 설명합니다.

속성 및 설명
targetCellWidthtargetCellHeight (Android 12), minWidthminHeight
  • Android 12부터 targetCellWidthtargetCellHeight 속성은 그리드 셀 측면에서 위젯의 기본 크기를 지정합니다. 이러한 속성은 Android 11 이하에서 무시되며 홈 화면이 그리드 기반 레이아웃을 지원하지 않는 경우 무시할 수 있습니다.
  • minWidthminHeight 속성은 dp 단위의 위젯 기본 크기를 지정합니다. 위젯의 최소 너비 또는 높이 값이 셀의 크기와 일치하지 않으면 값이 가장 가까운 셀 크기로 반올림됩니다.
사용자의 기기에서 targetCellWidthtargetCellHeight를 지원하지 않는 경우 앱이 minWidthminHeight를 사용하도록 대체할 수 있도록 targetCellWidthtargetCellHeightminWidthminHeight 속성 집합을 모두 지정하는 것이 좋습니다. 지원되는 경우 targetCellWidthtargetCellHeight 속성이 minWidthminHeight 속성보다 우선합니다.
minResizeWidthminResizeHeight 위젯의 절대 최소 크기를 지정합니다. 이 값은 위젯을 알아볼 수 없거나 사용할 수 없는 크기를 지정합니다. 이러한 속성을 사용하면 사용자가 위젯의 크기를 기본 위젯 크기보다 작게 조정할 수 있습니다. minResizeWidth 속성은 minWidth보다 크거나 가로 크기 조절이 사용 설정되지 않은 경우 무시됩니다. resizeMode를 참고하세요. 마찬가지로 minResizeHeight 속성은 minHeight보다 크거나 세로 크기 조절이 사용 설정되지 않은 경우 무시됩니다.
maxResizeWidthmaxResizeHeight 위젯의 권장 최대 크기를 지정합니다. 값이 그리드 셀 크기의 배수가 아니면 가장 가까운 셀 크기로 반올림됩니다. maxResizeWidth 속성은 minWidth보다 작거나 가로 크기 조절이 사용 설정되지 않은 경우 무시됩니다. resizeMode를 참고하세요. 마찬가지로 maxResizeHeight 속성은 minHeight보다 작거나 세로 크기 조절이 사용 설정되지 않은 경우 무시됩니다. Android 12에서 도입되었습니다.
resizeMode 위젯의 크기를 조절할 수 있는 규칙을 지정합니다. 이 속성을 사용하여 홈 화면 위젯의 크기를 가로, 세로 또는 두 축 모두의 방향으로 조절 가능하도록 만들 수 있습니다. 사용자는 위젯을 길게 터치하여 크기 조절 핸들을 표시한 다음 가로 또는 세로 핸들을 드래그하여 레이아웃 그리드에서 크기를 변경합니다. resizeMode 속성의 값에는 horizontal, vertical, none이 포함됩니다. 위젯의 크기를 가로 및 세로로 조절 가능하도록 선언하려면 horizontal|vertical를 사용하세요.

위 표의 속성이 위젯 크기에 미치는 영향을 설명하기 위해 다음 사양을 가정해 보겠습니다.

  • 그리드 셀의 너비는 30dp, 높이는 50dp입니다.
  • 다음 속성 사양이 제공됩니다.
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="80dp"
    android:minHeight="80dp"
    android:targetCellWidth="2"
    android:targetCellHeight="2"
    android:minResizeWidth="40dp"
    android:minResizeHeight="40dp"
    android:maxResizeWidth="120dp"
    android:maxResizeHeight="120dp"
    android:resizeMode="horizontal|vertical" />

Android 12부터는 다음을 충족해야 합니다.

targetCellWidthtargetCellHeight 속성을 위젯의 기본 크기로 사용합니다.

위젯의 크기는 기본적으로 2x2입니다. 위젯의 크기는 2x1에서 4x3까지 조정할 수 있습니다.

Android 11 이하:

minWidthminHeight 속성을 사용하여 위젯의 기본 크기를 계산합니다.

기본 너비 = Math.ceil(80 / 30) = 3

기본 높이 = Math.ceil(80 / 50) = 2

위젯의 기본 크기는 3x2입니다. 위젯의 크기를 2x1에서 전체 화면까지 조정할 수 있습니다.

추가 위젯 속성

다음 표에서는 위젯 크기 조정 외의 품질과 관련된 <appwidget-provider> 속성을 설명합니다.

속성 및 설명
updatePeriodMillis 위젯 프레임워크가 onUpdate() 콜백 메서드를 호출하여 GlanceAppWidgetReceiver에 업데이트를 요청하는 빈도를 정의합니다. 배터리를 절약하기 위해 업데이트 주기를 최대한 길게 하여 한 시간에 한 번만 업데이트하는 것이 좋습니다. 자세한 내용은 훑어보기 상태 관리의 위젯 업데이트 시기 섹션을 참고하세요.
initialLayout Glance UI 컴포지션이 렌더링되기 전 위젯의 로드 레이아웃을 정의하는 레이아웃 리소스를 가리킵니다. 라이브러리(@layout/glance_default_loading_layout)에 제공된 사전 정의된 로드 레이아웃을 사용할 수 있습니다.
configure 사용자가 위젯을 추가할 때 실행되는 구성 활동을 정의합니다. 이 페이지의 위젯 구성 활동 구현 섹션을 참고하세요.
description 위젯에 표시할 위젯 선택 도구의 설명을 지정합니다. Android 12에서 도입되었습니다.
previewLayout (Android 12) 및 previewImage (Android 11 이하)
  • Android 12부터 previewLayout 속성은 확장 가능한 미리보기를 지정하며, 이 미리보기는 위젯의 기본 크기로 설정된 XML 레이아웃으로 제공됩니다. 이상적으로는 디자인 레이아웃과 일치하는 정적 XML 매핑을 가리킵니다.
  • Android 11 이하에서 previewImage 속성은 위젯 선택 도구에 표시되는 위젯의 정적 이미지 드로어블 스크린샷을 지정합니다.
앱이 이전 플랫폼에서 정상적으로 대체되도록 두 가지를 모두 지정하는 것이 좋습니다. 최신 플랫폼 (Android 15 이상)의 경우 `GlanceAppWidget.providePreview`를 사용하여 Kotlin에서 실시간 생성 미리보기를 정의할 수 있습니다. 생성된 미리보기 가이드를 참고하세요.
autoAdvanceViewId 위젯의 호스트에서 자동으로 진행하는 위젯 하위 뷰의 뷰 ID를 지정합니다.
widgetCategory 위젯을 홈 화면(home_screen), 잠금 화면 (keyguard) 또는 둘 다에 표시할 수 있는지 여부를 선언합니다. Android 5.0 이상에서는 home_screen만 유효합니다.
widgetFeatures 위젯에서 지원하는 기능을 선언합니다. 예를 들어 위젯의 구성이 선택사항인 경우 configuration_optionalreconfigurable를 모두 지정합니다.

GlanceAppWidget 정의

  1. GlanceAppWidget에서 확장되고 provideGlance 메서드를 재정의하는 새 클래스를 만듭니다. 위젯을 렌더링하는 데 필요한 데이터를 로드할 수 있는 메서드는 다음과 같습니다.

    class MyAppWidget : GlanceAppWidget() {
    
        override suspend fun provideGlance(context: Context, id: GlanceId) {
    
            // In this method, load data needed to render the AppWidget.
            // Use `withContext` to switch to another thread for long running
            // operations.
    
            provideContent {
                // create your AppWidget here
                Text("Hello World")
            }
        }
    }

  2. GlanceAppWidgetReceiverglanceAppWidget에서 인스턴스화합니다.

    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
    
        // Let MyAppWidgetReceiver know which GlanceAppWidget to use
        override val glanceAppWidget: GlanceAppWidget = MyAppWidget()
    }

이제 Glance를 사용하여 AppWidget을 구성했습니다.

AppWidgetProvider 클래스를 사용하여 위젯 브로드캐스트 처리

GlanceAppWidgetReceiver 좌표 위젯은 기본 AppWidgetProvider를 확장하여 플랫폼 상태 업데이트를 브로드캐스트합니다. 위젯이 업데이트, 삭제, 사용 설정 또는 사용 중지될 때 플랫폼 이벤트를 수신하여 Compose 수명 주기 요청으로 변환합니다.

매니페스트에서 위젯 선언

AndroidManifest.xml 파일에서 GlanceAppWidgetReceiver 클래스 서브클래스를 브로드캐스트 수신기로 선언합니다.

<receiver android:name="ExampleAppWidgetReceiver"
          android:exported="false">
    <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>

<receiver> 요소에는 수신기 클래스를 지정하는 android:name 속성이 필요합니다. 수신자는 <intent-filter> 내에서 ACTION_APPWIDGET_UPDATE 브로드캐스트 작업을 수락해야 합니다.

<meta-data> 요소는 이름을 android.appwidget.provider로 식별해야 하며 android:resource 속성은 AppWidgetProviderInfo XML 메타데이터 리소스 (@xml/my_app_widget_info)를 가리켜야 합니다.

AppWidgetProvider 클래스 구현

Glance에서는 AppWidgetProvider 대신 GlanceAppWidgetReceiver를 직접 확장합니다. 수신기를 GlanceAppWidget 인스턴스에 연결하여 구현합니다. GlanceAppWidgetReceiver에서 사용할 수 있는 기본 콜백은 다음과 같이 작동합니다.

  • onUpdate(): 컴포지션 업데이트를 실행하기 위해 Glance에 의해 자동으로 재정의됩니다. onUpdate를 수동으로 재정의하는 경우 Glance가 컴포지션 스레드를 성공적으로 실행할 수 있도록 super.onUpdate를 호출해야 합니다.
  • onAppWidgetOptionsChanged(): 위젯이 처음 배치되거나 크기가 조절될 때 호출됩니다. Glance 읽기 옵션은 내부적으로 항목을 번들로 묶어 런타임 측정기준에 따라 레이아웃이 원활하게 조정되도록 합니다.
  • onDeleted(Context, IntArray): 사용자가 특정 위젯 인스턴스를 삭제할 때마다 호출됩니다.
  • onEnabled(Context): 위젯의 첫 번째 인스턴스가 성공적으로 생성될 때 트리거됩니다. 전역 마이그레이션을 실행하는 데 적합합니다.
  • onDisabled(Context): 제공자의 마지막 활성 인스턴스가 삭제될 때 호출됩니다.
  • onReceive(Context, Intent): 특정 콜백 메서드 전에 모든 플랫폼 브로드캐스트를 가로챕니다. Glance는 작업을 비동기적으로 자동 라우팅하므로 작성하는 맞춤 리시버 로직이 super.onReceive(context, intent)를 호출해야 하며 goAsync를 직접 호출해서는 안 됩니다.

위젯 브로드캐스트 인텐트 수신

내부적으로 GlanceAppWidgetReceiver은 다음과 같은 기본 플랫폼 위젯 브로드캐스트 인텐트를 필터링하고 처리합니다.

UI 만들기

다음 스니펫은 UI를 만드는 방법을 보여줍니다.

/* Import Glance Composables
 In the event there is a name clash with the Compose classes of the same name,
 you may rename the imports per https://kotlinlang.org/docs/packages.html#imports
 using the `as` keyword.

import androidx.glance.Button
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.text.Text
*/
class MyAppWidget : GlanceAppWidget() {

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // Load data needed to render the AppWidget.
        // Use `withContext` to switch to another thread for long running
        // operations.

        provideContent {
            // create your AppWidget here
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        Column(
            modifier = GlanceModifier.fillMaxSize(),
            verticalAlignment = Alignment.Top,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
            Row(horizontalAlignment = Alignment.CenterHorizontally) {
                Button(
                    text = "Home",
                    onClick = actionStartActivity<MyActivity>()
                )
                Button(
                    text = "Work",
                    onClick = actionStartActivity<MyActivity>()
                )
            }
        }
    }
}

위의 코드 샘플은 다음을 실행합니다.

  • 최상위 수준 Column에서 항목은 세로로 하나씩 배치됩니다.
  • Column는 사용 가능한 공간에 맞게 크기를 확장하고 (GlanceModifier) 콘텐츠를 상단(verticalAlignment)에 정렬하고 가로로 중앙에 배치합니다 (horizontalAlignment).
  • Column의 콘텐츠는 람다를 사용하여 정의됩니다. 순서가 중요합니다.
    • Column의 첫 번째 항목은 패딩이 12.dpText 구성요소입니다.
    • 두 번째 항목은 Row입니다. 항목이 가로로 나란히 배치되고 두 개의 Buttons이 가로로 가운데에 배치됩니다(horizontalAlignment). 최종 디스플레이는 사용 가능한 공간에 따라 달라집니다. 다음 이미지는 예시입니다.
destination_widget
그림 1. UI 예시

정렬 값을 변경하거나 다양한 수정자 값 (예: 패딩)을 적용하여 구성요소의 배치와 크기를 변경할 수 있습니다. 각 클래스의 구성요소, 매개변수, 사용 가능한 수정자의 전체 목록은 참조 문서를 참고하세요.

둥근 모서리 구현

Android 12에서는 앱 위젯의 모서리 반경을 동적으로 맞춤설정하는 시스템 매개변수를 도입합니다.

  • system_app_widget_background_radius: 위젯 배경 컨테이너의 모서리 반경을 지정합니다 (28dp보다 클 수 없음).
  • 내부 반지름: 콘텐츠가 잘리지 않도록 시스템 배경 윤곽선을 기반으로 내부 콘텐츠의 비례 반지름을 계산합니다. systemRadiusValue - widgetPadding

Glance에서는 GlanceModifier.cornerRadius(android.R.dimen.system_app_widget_background_radius)를 사용하여 컴포지션에서 동적으로 모서리 반경 크기 조정 속성을 적용할 수 있습니다.

Android 11 (API 수준 30) 이하를 실행하는 기기에서 이전 버전과의 호환성을 위해 맞춤 속성과 맞춤 테마 리소스 대체 값을 구현합니다.

  • /values/attrs.xml

    <resources>
    <attr name="backgroundRadius" format="dimension" />
    </resources>
    
  • /values/styles.xml

    <resources>
    <style name="MyWidgetTheme">
      <item name="backgroundRadius">@dimen/my_background_radius_dimen</item>
    </style>
    </resources>
    
  • /values-31/styles.xml

    <resources>
    <style name="MyWidgetTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
      <item name="backgroundRadius">@android:dimen/system_app_widget_background_radius</item>
    </style>
    </resources>
    
  • /drawable/my_widget_background.xml

    <shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="?attr/backgroundRadius" />
    </shape>