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

Bu sayfada, mevcut Glance bileşenlerini kullanarak boyutları nasıl yöneteceğiniz ve Glance ile esnek ve duyarlı düzenleri nasıl sağlayacağınız açıklanmaktadır.

Box, Column ve Row kullanın

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

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

  • Column: Öğeleri dikey eksende birbiri ardına yerleştirir. Bu, dikey yönlendirmeli bir LinearLayout anlamına gelir.

  • Row: Öğeleri yatay eksende birbirinin ardına yerleştirir. Yatay yönlendirmeli bir LinearLayout olarak çevrilir.

Glance, Scaffold nesnelerini destekler. Column, Row ve Box composable'larınızı belirli bir Scaffold nesnesine yerleştirin.

Sütun, satır ve kutu temelli düzen.
Ş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ı ve genişlik, yükseklik, ağırlık veya dolgu kısıtlamalarını tanımlamanıza olanak tanır. Ayrıca her alt öğe, üst öğe içindeki boşluğu ve yerleşimi değiştirmek için kendi değiştiricisini tanımlayabilir.

Aşağıdaki örnekte, alt öğelerini Şekil 1'de gösterildiği gibi yatay olarak eşit şekilde dağıtan bir Row oluşturma işlemi 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ğundan 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üzenleri kullanma

Uyumlu içerik sunmanın bir başka yolu da içeriği kaydırılabilir hale getirmektir. Bunu LazyColumn composable'ı ile yapabilirsiniz. Bu composable, uygulama widget'ındaki kaydırılabilir bir kapsayıcıda gösterilecek bir öğe grubu 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()
        )
    }
}

Öğeleri tek tek sağlama:

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

Öğ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'te itemId belirtilmediğini unutmayın. itemId değerini belirtmek, Android 12'den itibaren liste ve appWidget güncellemeleri aracılığıyla performansı iyileştirmeye ve kaydırma konumunu korumaya yardımcı olur (örneğin, listeye öğe eklerken veya listeden öğe kaldırırken). Aşağıdaki örnekte, itemId öğesinin nasıl belirtileceği gösterilmektedir:

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

Snap Scrolling

Kaydırarak sabitleme, kaydırılabilir içeriğin widget kapsayıcısının üst kısmına sabitlenmesini sağlayan bir animasyondur.

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


Kaydırarak geçiş ö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.
  • Kaydırarak geçiş özelliği Android 17 ve sonraki sürümleri çalıştıran cihazlarda desteklendiğinden compileSdk değerini 37 veya daha yüksek bir değere ayarlayın.
  • LazyColumn cihazınızı VerticalScrollMode ile yapılandırın. Cihaz, kaydırma özelliğini destekliyorsa SnapScrollMatchHeight kullanın. Aksi takdirde Normal özelliğini kullanın.

Resimlerle kaydırma özelliğini kullanıyorsanız tam sayfa resim standart düzenine 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 farklılık gösterebilir. 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 bu üç mod açıklanmaktadır.

SizeMode.Single

SizeMode.Single varsayılan moddur. Yalnızca bir tür içerik sağlandığını gösterir. Yani AppWidget kullanılabilir boyutu 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ımlanmalıdır.
  • İçerik, beklenen boyut aralığında yeterince esnek.

Genel olarak bu modu aşağıdaki durumlarda kullanmalısınız:

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

SizeMode.Responsive

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

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

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 daha iyi performans gösterir ve AppWidget yeniden boyutlandırıldığında daha yumuşak geçişler sağlar.

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

Mevcut 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 tanıtım amaçlıdır.

SizeMode.Exact

SizeMode.Exact, tam düzenler sağlamanın eşdeğeridir. Bu, kullanılabilir AppWidget boyutu her değiştiğinde (örneğin, kullanıcı ana ekranda AppWidget boyutunu değiştirdiğinde) GlanceAppWidget içeriğini ister.

Örneğin, hedef widget'ında kullanılabilir genişlik belirli bir değerden büyükse ekstra 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 birkaç uyarıyı da beraberinde getirir:

  • 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 uygulamasına bağlı olarak farklılık gösterebilir. Örneğin, başlatıcı boyut listesini sağlamıyorsa mümkün olan minimum boyut kullanılır.
  • Android 12 öncesi cihazlarda boyut hesaplama mantığı her durumda çalışmayabilir.

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

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)

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

Birleştirilebilir öğeler ve yöntemler, ImageProvider gibi bir "sağlayıcı" kullanarak 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" durumu 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şaretli durumu 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) }
        )
    }
}

Renklerini özelleştirmek için colors özelliğini CheckBox, Switch ve RadioButton öğelerine de ekleyebilirsiniz:

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
Dolgulu Düğme alt_text Bileşen
Ana Hat Düğmeleri 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 Scaffold ve başlık çubuğu aynı demoda yer alır.

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.