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

Bu sayfada, mevcut Glance bileşenlerini kullanarak Glance ile boyutların nasıl işleneceği ve esnek ve duyarlı düzenlerin nasıl sağlanacağı açıklanmaktadır.

Box, Column ve Row özelliğini kullanma

Glance'in üç ana composable düzeni vardır:

  • Box: Öğeleri birbirinin üzerine yerleştirir. Bu, RelativeLayout anlamına gelir.

  • Column: Öğeleri dikey eksende birbirinin arkasına yerleştirir. Bu, dikey yönlü bir LinearLayout olarak çevrilir.

  • Row: Öğeleri yatay eksende yan yana yerleştirir. Bu, yatay yönlü bir LinearLayout olarak çevrilir.

Bir Bakışta özelliği Scaffold nesnelerini destekler. Column, Row ve Box composable'larınızı belirli bir Scaffold nesnesinin içine yerleştirin.

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

Bu composable'ların her biri, değiştiricileri kullanarak içeriğinin dikey ve yatay hizalamalarını, genişliğini, yüksekliğini, ağırlığını veya dolgu kısıtlamalarını tanımlamanıza olanak tanır. Ayrıca her alt öğe, üst öğenin içindeki alanı ve yerleşimi değiştirmek için kendi 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ğı açıklanmaktadır:

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

Row, mevcut maksimum genişliği doldurur ve her çocuğun ağırlığı aynı olduğundan mevcut alanı eşit şekilde paylaşırlar. 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 kullanma

Duyarlı içerik sunmanın bir diğer yolu da içeriği kaydırılabilir hale getirmektir. Bu, LazyColumn composable ile mümkündür. Bu composable, uygulama widget'ında kaydırılabilir bir container içinde gösterilecek öğeler kümesini tanımlamanıza olanak tanır.

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

Öğe sayısını belirtebilirsiniz:

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

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

Ayrı ayrı öğeler sağlama:

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

Öğe listesi veya dizisi sağlayın:

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

Ayrıca 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'te itemId belirtilmediğini unutmayın. itemId belirtmek, performansı artırmaya ve Android 12'den itibaren liste ve appWidget güncellemeleri (ör. listeye öğe ekleme veya listeden öğe kaldırma) sırasında kaydırma konumunu korumaya yardımcı olur. Aşağıdaki örnekte, itemId nasıl belirtileceği gösterilmektedir:

items(items = peopleList, itemId = { person -> person.id.hashCode().toLong() }) { person ->
    Text(person.name)
}

Snap Scrolling

Snap scrolling, kaydırılabilir içeriğin widget kapsayıcısının en üstüne yerleşmesini sağlayan bir animasyondur.

1.Video Solda, kaydırma sırasında yerine oturmayan bir liste öğesi, sağda ise yerine oturan bir liste öğesi gösterilmektedir.


Anında kaydırma özelliğini uygulamak için aşağıdaki koşulları karşıladığınızdan emin olun:

  • Glance bağımlılığınızı 1.3.0-alpha02 veya sonraki bir sürüme güncelleyin.
  • Android 17 ve sonraki sürümlerin yüklü olduğu cihazlarda hızlı kaydırma desteklendiğinden compileSdk ayarınızı 37 veya daha yüksek bir değere ayarlayın.
  • LazyColumn cihazınızı VerticalScrollMode ile yapılandırın. Cihaz, hızlı kaydırma özelliğini destekliyorsa SnapScrollMatchHeight simgesini kullanın. Aksi takdirde, Normal kullanın.

Resimlerle birlikte hızlı kaydırma kullanıyorsanız taşan resim Standart Düzen'e bakın.

@Composable
fun SnapScrollLayout() {
    val height = LocalSize.current.height
    val items = listOf(
        ColorItem(Color.Red, "Red"),
        ColorItem(Color.Yellow, "Yellow"),
        ColorItem(Color.Blue, "Blue")
    )

    val scrollMode = if (Build.VERSION.SDK_INT >= 37) {
        VerticalScrollMode.SnapScrollMatchHeight(height)
    } else {
        VerticalScrollMode.Normal
    }

    LazyColumn(
        verticalScrollMode = scrollMode
    ) {
        items(items) { item ->
            ColorCard(item, height)
        }
    }
}

@Composable
private fun ColorCard(item: ColorItem, height: Dp) {
    Box(
        modifier = GlanceModifier
            .background(item.color)
            .fillMaxWidth()
            .height(height),
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = item.name,
            modifier = GlanceModifier.background(Color.White)
        )
    }
}

SizeMode tanımlayın

AppWidget boyutları cihaza, kullanıcı tercihine veya başlatıcıya göre 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ğeriyle bu süreci 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ı gösterir. Yani, AppWidget kullanılabilir boyut değişse bile içerik boyutu değişmez.

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 şu durumlarda kullanmalısınız:

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

SizeMode.Responsive

Bu mod, duyarlı düzenler sağlamaya eşdeğerdir. Bu sayede GlanceAppWidget, belirli boyutlarla sınırlanmış bir dizi duyarlı düzen tanımlayabilir. İçerik, tanımlanan her boyut için oluşturulur ve AppWidget oluşturulduğunda veya güncellendiğinde belirli bir boyuta eşlenir. Sistem daha sonra mevcut boyuta göre en uygun olanı seçer.

Örneğin, hedefimizde AppWidget üç 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 ek düğme, üst ve alt metinler yer almıyor.
  • İkinci çağrıda boyut 250x100 olarak değerlendirilir. İçerikte ek düğme var ancak üst ve alt metinler yok.
  • Üçüncü çağrıda boyut 250x250 olarak değerlendirilir. İçerikte ek düğme ve her iki metin de yer alıyor.

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

Aşağıdaki tabloda, SizeMode ve AppWidget kullanılabilir boyutuna bağlı olarak boyutun 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
* Tam değerler yalnızca demo amaçlıdır.

SizeMode.Exact

SizeMode.Exact, tam düzenler sağlama ile eşdeğerdir. Bu, GlanceAppWidget içeriğinin, kullanılabilir AppWidget boyutu her değiştiğinde (örneğin, kullanıcı ana ekrandaki AppWidget boyutunu değiştirdiğinde) istenmesini sağlar.

Örneğin, kullanılabilir genişlik belirli bir değerden büyükse hedef widget'ına ek 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ğerlerine göre daha fazla esneklik sağlar ancak bazı dezavantajları 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 yol açabilir.
  • Kullanılabilir boyut, başlatıcının uygulanmasına bağlı olarak değişebilir. Örneğin, başlatıcı boyut listesini sağlamıyorsa mümkün olan en küçük boyut kullanılır.
  • Android 12'den önceki cihazlarda, boyut hesaplama mantığı her durumda çalışmayabilir.

Genel olarak, SizeMode.Responsive kullanılamıyorsa (yani duyarlı düzenlerin küçük bir grubu uygun değilse) bu modu kullanmanız gerekir.

Kaynaklara erişim

Aşağıdaki örnekte gösterildiği gibi, herhangi bir Android kaynağına erişmek için LocalContext.current 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 kaynak kimliklerini doğrudan sağlamanızı öneririz.

Composable'lar ve yöntemler, ImageProvider gibi bir "sağlayıcı" veya GlanceModifier.background(R.color.blue) gibi bir aşırı yükleme yöntemi kullanarak kaynakları kabul eder. Örneğin:

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

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

Herkese açık kullanıcı adı metni

Glance 1.1.0, metin stillerinizi ayarlamak için bir API içerir. TextStyle sınıfının fontSize, fontWeight veya fontFamily özelliklerini kullanarak metin stillerini ayarlayın.

fontFamily, aşağıdaki örnekte gösterildiği gibi tüm sistem yazı tiplerini destekler ancak uygulamalardaki özel yazı tipleri desteklenmez:

Text(
    style = TextStyle(
        fontWeight = FontWeight.Bold,
        fontSize = 18.sp,
        fontFamily = FontFamily.Monospace
    ),
    text = "Example Text"
)

Bileşik düğmeler ekleme

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

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

var isApplesChecked by remember { mutableStateOf(false) }
var isEnabledSwitched by remember { mutableStateOf(false) }
var isRadioChecked by remember { mutableIntStateOf(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, işaretleme 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, renklerini özelleştirmek için colors özelliğini CheckBox, Switch ve RadioButton için 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)
    ),

)

Ek bileşenler

Glance 1.1.0, aşağıdaki tabloda açıklandığı gibi ek bileşenlerin yayınlanmasını içerir:

Ad Resim Referans bağlantısı Ek notlar
Dolu Düğme alt_text Bileşen
Dış Çizgili Düğmeler alt_text Bileşen
Simge Düğmeleri alt_text Bileşen Birincil / İkincil / Yalnızca simge
Başlık Çubuğu alt_text Bileşen
İskele İskele ve başlık çubuğu aynı demoda yer alıyor.

Tasarım ayrıntıları hakkında daha fazla bilgi için Figma'daki bu tasarım kitinde yer alan bileşen tasarımlarına bakın.

Standart düzenler hakkında daha fazla bilgi için Standart widget düzenleri başlıklı makaleyi inceleyin.