Bir Bakışta ile kullanıcı arayüzü oluşturma

Bu sayfada, Glance ile boyutların nasıl ele alınacağı, esnek ve duyarlı düzenlerin nasıl sağlanacağı açıklanmaktadır.

Box, Column ve Row kullanın

Glance'ta üç ana composable düzen vardır:

  • Box: Öğeleri diğerlerinin üzerine yerleştirir. RelativeLayout diline çevrilir.

  • Column: Öğeleri dikey eksene birbirinin ardından yerleştirir. Dikey yönlü LinearLayout çevirir.

  • Row: Öğeleri yatay eksene birbirinin ardından yerleştirir. Yatay yönlü bir LinearLayout anlamına gelir.

Sütun, satır ve kutu düzeninin resmi.
Şekil 1. Sütun, Satır ve Kutu içeren düzen örnekleri.

Bu composable'ların her biri, içeriğinin dikey ve yatay hizalamasını ve genişlik, yükseklik, ağırlık veya dolgu kısıtlamalarını düzenleyiciler kullanarak tanımlamanıza olanak tanır. Buna ek olarak, her alt öğe, üst öğenin içindeki alanı ve yerleşimi değiştirmek için değiştiricisini tanımlayabilir.

Aşağıdaki örnekte, Şekil 1'de gösterildiği gibi alt öğelerini yatay olarak eşit şekilde dağıtan bir Row öğesinin nasıl oluşturulacağı gösterilmektedir:

Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) {
    val modifier = GlanceModifier.defaultWeight()
    Text("first", modifier)
    Text("second", modifier)
    Text("third", modifier)
}

Row, kullanılabilir maksimum genişliği doldurur ve her alt öğe aynı ağırlığa sahip olduğu için kullanılabilir alanı eşit olarak paylaşır. Düzenleri ihtiyaçlarınıza göre uyarlamak için farklı ağırlıklar, boyutlar, dolgular veya hizalamalar tanımlayabilirsiniz.

Kaydırılabilir düzenler kullan

Duyarlı içerik sağlamanın bir başka yolu da kaydırılabilir hale getirmektir. Bunu LazyColumn composable ile yapabilirsiniz. Bu composable, uygulama widget'ında kaydırılabilir bir kapsayıcı içinde görüntülenecek bir dizi öğe tanımlamanızı sağlar.

Aşağıdaki snippet'lerde LazyColumn içindeki öğeleri tanımlamanın farklı yolları gösterilmektedir.

Öğe sayısını sağlayabilirsiniz:

// Remember to import Glance Composables
// import androidx.glance.appwidget.layout.LazyColumn

LazyColumn {
    items(10) { index: Int ->
        Text(
            text = "Item $index",
            modifier = GlanceModifier.fillMaxWidth()
        )
    }
}

Öğeleri tek tek sağlayın:

LazyColumn {
    item {
        Text("First Item")
    }
    item {
        Text("Second Item")
    }
}

Bir öğe listesi veya dizisi sağlayın:

LazyColumn {
    items(peopleNameList) { name ->
        Text(name)
    }
}

Yukarıdaki örneklerin bir kombinasyonunu da kullanabilirsiniz:

LazyColumn {
    item {
        Text("Names:")
    }
    items(peopleNameList) { name ->
        Text(name)
    }

    // or in case you need the index:
    itemsIndexed(peopleNameList) { index, person ->
        Text("$person at index $index")
    }
}

Önceki snippet'in itemId belirtmediğini unutmayın. itemId belirtilmesi, performansı iyileştirmeye ve Android 12'den sonraki appWidget güncellemelerinin yanı sıra kaydırma konumunun korunmasına yardımcı olur (örneğin, listeye öğe eklerken veya listeden öğe kaldırırken). Aşağıdaki örnekte, bir itemId öğesinin nasıl belirtileceği gösterilmektedir:

items(items = peopleList, key = { person -> person.id }) { person ->
    Text(person.name)
}

SizeMode tanımlayın

AppWidget boyutları cihaza, kullanıcı seçimine veya başlatıcıya bağlı olarak değişebilir. Bu nedenle, Esnek widget düzenleri sağlama sayfasında açıklandığı gibi esnek düzenler sağlamak önemlidir. Glance, SizeMode tanımı ve LocalSize değeri ile bunu basitleştirir. Aşağıdaki bölümlerde üç mod açıklanmaktadır.

SizeMode.Single

SizeMode.Single varsayılan moddur. Yalnızca tek bir içerik türünün sağlandığını, yani AppWidget kullanılabilir boyut değişse bile içerik boyutunun değişmediğini gösterir.

class MyAppWidget : GlanceAppWidget() {

    override val sizeMode = SizeMode.Single

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // ...

        provideContent {
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        // Size will be the minimum size or resizable
        // size defined in the App Widget metadata
        val size = LocalSize.current
        // ...
    }
}

Bu modu kullanırken şunlardan emin olun:

  • Minimum ve maksimum boyut meta veri değerleri, içerik boyutuna göre doğru şekilde tanımlanır.
  • İçerik, beklenen boyut aralığında yeterince esnektir.

Genel olarak bu modu, aşağıdaki durumlardan herhangi biri geçerli olduğunda kullanmalısınız:

a) AppWidget sabit bir boyuta sahipse veya b) yeniden boyutlandırıldığında içeriğini değiştirmiyor.

SizeMode.Responsive

Bu mod, GlanceAppWidget öğesinin belirli boyutlarla sınırlanmış bir duyarlı düzen grubu tanımlamasına olanak tanıyan duyarlı düzenler sağlama ile eşdeğerdir. Tanımlanan her boyut için içerik oluşturulur ve AppWidget oluşturulurken veya güncellendiğinde belirli boyutla eşlenir. Daha sonra sistem, mevcut boyuta göre en uygun modeli seçer.

Örneğin, AppWidget hedefimizde üç boyut ve içeriğini tanımlayabilirsiniz:

class MyAppWidget : GlanceAppWidget() {

    companion object {
        private val SMALL_SQUARE = DpSize(100.dp, 100.dp)
        private val HORIZONTAL_RECTANGLE = DpSize(250.dp, 100.dp)
        private val BIG_SQUARE = DpSize(250.dp, 250.dp)
    }

    override val sizeMode = SizeMode.Responsive(
        setOf(
            SMALL_SQUARE,
            HORIZONTAL_RECTANGLE,
            BIG_SQUARE
        )
    )

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // ...

        provideContent {
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        // Size will be one of the sizes defined above.
        val size = LocalSize.current
        Column {
            if (size.height >= BIG_SQUARE.height) {
                Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
            }
            Row(horizontalAlignment = Alignment.CenterHorizontally) {
                Button()
                Button()
                if (size.width >= HORIZONTAL_RECTANGLE.width) {
                    Button("School")
                }
            }
            if (size.height >= BIG_SQUARE.height) {
                Text(text = "provided by X")
            }
        }
    }
}

Önceki örnekte, provideContent yöntemi üç kez çağrılır ve tanımlanan boyuta eşlenir.

  • İlk çağrıda boyut 100x100 olarak değerlendirilir. İçerikte ekstra düğme veya üst ve alt metinler yok.
  • İkinci çağrıda boyut 250x100 olarak değerlendirilir. İçerik ekstra düğme içeriyor, ancak üst ve alt metinleri içermiyor.
  • Üçüncü çağrıda boyut 250x250 olarak değerlendirilir. İçerikte "ekstra" düğmesi ve her iki metin de var.

SizeMode.Responsive, diğer iki modun birleşimidir ve önceden tanımlanmış sınırlar içinde duyarlı içeriği tanımlamanıza olanak tanır. Genel olarak bu mod, AppWidget yeniden boyutlandırıldığında daha iyi performans gösterir ve daha yumuşak geçişlere olanak tanır.

Aşağıdaki tabloda, SizeMode ve kullanılabilir AppWidget boyutuna bağlı olarak beden değeri gösterilmektedir:

Kullanılabilir boyut 105 x 110 203 x 112 72 x 72 203 x 150
SizeMode.Single 110 x 110 110 x 110 110 x 110 110 x 110
SizeMode.Exact 105 x 110 203 x 112 72 x 72 203 x 150
SizeMode.Responsive 80 x 100 80 x 100 80 x 100 150 x 120
* Kesin değerler yalnızca demo amaçlıdır.

SizeMode.Exact

SizeMode.Exact, kullanılabilir AppWidget boyutu her değiştiğinde (örneğin, kullanıcı ana ekranda AppWidget öğesini yeniden boyutlandırdığında) GlanceAppWidget içeriğini isteyen tam düzenler sağlama ile eşdeğerdir.

Örneğin, kullanılabilir genişlik belirli bir değerden büyükse hedef widget'ta fazladan bir düğme eklenebilir.

class MyAppWidget : GlanceAppWidget() {

    override val sizeMode = SizeMode.Exact

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // ...

        provideContent {
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        // Size will be the size of the AppWidget
        val size = LocalSize.current
        Column {
            Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
            Row(horizontalAlignment = Alignment.CenterHorizontally) {
                Button()
                Button()
                if (size.width > 250.dp) {
                    Button("School")
                }
            }
        }
    }
}

Bu mod, diğerlerinden daha fazla esneklik sağlar, ancak dikkat edilmesi gereken birkaç nokta vardır:

  • Boyut her değiştiğinde AppWidget tamamen yeniden oluşturulmalıdır. Bu durum, içerik karmaşık olduğunda performans sorunlarına ve kullanıcı arayüzünde atlamalara neden olabilir.
  • Kullanılabilir boyut, başlatıcının uygulamasına bağlı olarak değişebilir. Örneğin, başlatıcı boyut listesini sağlamazsa olası minimum boyut kullanılır.
  • Android 12 sürümünden önceki cihazlarda boyut hesaplama mantığı tüm durumlarda çalışmayabilir.

Genel olarak, SizeMode.Responsive kullanılamıyorsa (yani duyarlı düzenlerden oluşan küçük bir grup uygun değilse) kullanmalısınız.

Kaynaklara erişin

Aşağıdaki örnekte gösterildiği gibi, herhangi bir Android kaynağına erişmek için LocalContext.current öğesini kullanın:

LocalContext.current.getString(R.string.glance_title)

Son RemoteViews nesnesinin boyutunu küçültmek ve dinamik renkler gibi dinamik kaynakları etkinleştirmek için doğrudan kaynak kimlikleri sağlamanızı öneririz.

Oluşturulabilir öğeler ve yöntemler, ImageProvider gibi bir "sağlayıcı" veya GlanceModifier.background(R.color.blue) gibi bir aşırı yükleme yöntemi kullanan kaynakları kabul eder. Örnek:

Column(
    modifier = GlanceModifier.background(R.color.default_widget_background)
) { /**...*/ }

Image(
    provider = ImageProvider(R.drawable.ic_logo),
    contentDescription = "My image",
)

Birleşik düğme ekleme

Birleşik düğmeler Android 12'de kullanıma sunuldu. Glance, aşağıdaki birleşik düğme türleri için geriye dönük uyumluluğu destekler:

Bu birleşik düğmelerin her biri, "işaretli" durumunu temsil eden tıklanabilir bir görünüm görüntüler.

var isApplesChecked by remember { mutableStateOf(false) }
var isEnabledSwitched by remember { mutableStateOf(false) }
var isRadioChecked by remember { mutableStateOf(0) }

CheckBox(
    checked = isApplesChecked,
    onCheckedChange = { isApplesChecked = !isApplesChecked },
    text = "Apples"
)

Switch(
    checked = isEnabledSwitched,
    onCheckedChange = { isEnabledSwitched = !isEnabledSwitched },
    text = "Enabled"
)

RadioButton(
    checked = isRadioChecked == 1,
    onClick = { isRadioChecked = 1 },
    text = "Checked"
)

Durum değiştiğinde, sağlanan lambda tetiklenir. Aşağıdaki örnekte gösterildiği gibi kontrol durumunu saklayabilirsiniz:

class MyAppWidget : GlanceAppWidget() {

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        val myRepository = MyRepository.getInstance()

        provideContent {
            val scope = rememberCoroutineScope()

            val saveApple: (Boolean) -> Unit =
                { scope.launch { myRepository.saveApple(it) } }
            MyContent(saveApple)
        }
    }

    @Composable
    private fun MyContent(saveApple: (Boolean) -> Unit) {

        var isAppleChecked by remember { mutableStateOf(false) }

        Button(
            text = "Save",
            onClick = { saveApple(isAppleChecked) }
        )
    }
}

Ayrıca CheckBox, Switch ve RadioButton öğelerinin renklerini özelleştirmek için colors özelliğini de sağlayabilirsiniz:

CheckBox(
    // ...
    colors = CheckboxDefaults.colors(
        checkedColor = ColorProvider(day = colorAccentDay, night = colorAccentNight),
        uncheckedColor = ColorProvider(day = Color.DarkGray, night = Color.LightGray)
    ),
    checked = isChecked,
    onCheckedChange = { isChecked = !isChecked }
)

Switch(
    // ...
    colors = SwitchDefaults.colors(
        checkedThumbColor = ColorProvider(day = Color.Red, night = Color.Cyan),
        uncheckedThumbColor = ColorProvider(day = Color.Green, night = Color.Magenta),
        checkedTrackColor = ColorProvider(day = Color.Blue, night = Color.Yellow),
        uncheckedTrackColor = ColorProvider(day = Color.Magenta, night = Color.Green)
    ),
    checked = isChecked,
    onCheckedChange = { isChecked = !isChecked },
    text = "Enabled"
)

RadioButton(
    // ...
    colors = RadioButtonDefaults.colors(
        checkedColor = ColorProvider(day = Color.Cyan, night = Color.Yellow),
        uncheckedColor = ColorProvider(day = Color.Red, night = Color.Blue)
    ),

)