Development/Android

Android Glance Widget에서 SizeMode에 관하여 (위젯 크기에 따라 View에 변화 주기)

SeungYong.Lee 2025. 5. 21. 10:07
반응형

- 위젯을 구현하다 보면 사이즈 별로 다른 레이아웃을 제공하거나 높이 너비에 따라 View의 구성이 달라지는 구현을 필요로 할 때가 있다.

 

- 기존 RemoteViews에서는 AppWidgetInfo에 접근하여 사이즈 값에 대해 확인했으나 Glance에서는 좀 다르다.

appWidgetManager.getAppWidgetInfo(appWidgetId).minHeight

 

- SizeMode 라는 값을 통해 위젯 크기 변화에 따른 레이아웃 구성 대응이 가능하다.

 

- SizeMode는 Glance 위젯이 시스템에서 요구하는 사이즈 정보의 유연성을 어떻게 처리할지를 정의한다.

sealed interface SizeMode {
    /**
     * The [GlanceAppWidget] provides a single UI.
     *
     * The [LocalSize] will be the minimum size the App Widget can be, as defined in
     * the App Widget provider info (see [android.appwidget.AppWidgetManager.getAppWidgetInfo]).
     */
    object Single : SizeMode {
        override fun toString(): String = "SizeMode.Single"
    }

    /**
     * The [GlanceAppWidget] provides a UI for each size the App Widget may be displayed at. The
     * list of sizes is provided by the options bundle (see
     * [android.appwidget.AppWidgetManager.getAppWidgetOptions]).
     *
     * The composable will be called for each size. During that call, the [LocalSize] will be the
     * one for which the UI is generated.
     */
    object Exact : SizeMode {
        override fun toString(): String = "SizeMode.Exact"
    }

    /**
     * The [GlanceAppWidget] provides a UI for a fixed set of sizes.
     *
     * On Android 12 and later, the composable will be called once per size provided and the
     * mapping from size to view will be sent to the system. The framework will then decide which
     * view to display based on the current size of the App Widget (see
     * [android.widget.RemoteViews] for details)
     *
     * Before Android 12, the composable will be called for each size at which the app widget may be
     * displayed (like for [Exact]). For each size, the best view will be chosen, which is the
     * largest one that fits in the available space, or the smallest one if none fit.
     *
     * @param sizes List of sizes to use, must not be empty.
     */
    class Responsive(val sizes: Set<DpSize>) : SizeMode {

        init {
            require(sizes.isNotEmpty()) { "The set of sizes cannot be empty" }
        }

        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (javaClass != other?.javaClass) return false

            other as Responsive

            if (sizes != other.sizes) return false

            return true
        }

        override fun hashCode(): Int = sizes.hashCode()

        override fun toString(): String = "SizeMode.Responsive(sizes=$sizes)"
    }
}

 

SizeMode.Exact

  • provideGlance() 호출 시 지정된 크기에만 맞춰 레이아웃이 그려짐
  • 사용자가 위젯을 여러 사이즈로 조절하더라도 고정된 레이아웃만 사용한다.
  • 높이에 따라 특정 뷰를 처리하는 데에 용이하다. 예를 들어 리스트가 들어간 위젯인데, 현재 위젯 높이에서 리스트를 다 보여주지 못하면 보이지 않는 개수만큼 +x 표시를 한다던지..
val enableListHeight = LocalSize.current.height.value.toInt().dp - fixedHeight
val maxHolderCount = enableListHeight / holderHeight
val visibleList = blockList.take(maxHolderCount.toInt())

SizeMode.Single

  • Exact와 유사하게 동작하지만, 내부적으로는 SizeMode.Responsive 기반으로 처리될 수 있음 (거의 같은 의미)

SizeMode.Responsive

  • 여러 사이즈에 대응하는 레이아웃 제공 가능
  • 시스템은 다양한 위젯 크기에 따라 provideGlance()를 여러 번 호출하며 각각 다른 Glance UI를 만들 수 있음
  • provideContent() 안에서 LocalSize.current 값을 이용해 사이즈별로 UI 다르게 구성 가능
when {
    LocalSize.current.width > 200.dp -> { /* 큰 위젯용 UI */ }
    else -> { /* 작은 위젯용 UI */ }
}

 

- 이렇게 sizeMode를 지정해주고, 컴포저블에서 LocalSize로 접근할 수 있다.

class TodoListWidget : GlanceAppWidget() {

    override val sizeMode = SizeMode.Exact
    
    override suspend fun provideGlance(context: Context, id: GlanceId) {
        provideContent {
            val enableListHeight = LocalSize.current.height
        }
    }

반응형