Tworzenie interfejsu użytkownika za pomocą Glance

Z tej strony dowiesz się, jak obsługiwać rozmiary oraz stosować elastyczne i elastyczne elementy układy graficzne z użyciem istniejących komponentów W skrócie.

Użyj usług Box, Column i Row

Funkcja Glance ma 3 główne układy kompozycyjne:

  • Box: powoduje umieszczenie elementów na drugim. To przekłada się na RelativeLayout.

  • Column: powoduje umieszczanie elementów po sobie na osi pionowej. Tłumaczy do LinearLayout w orientacji pionowej.

  • Row: powoduje umieszczenie elementów po sobie na osi poziomej. Tłumaczy do LinearLayout w orientacji poziomej.

Funkcja W skrócie obsługuje obiekty Scaffold. Umieść: Column, Row i Elementy kompozycyjne: Box w obrębie danego obiektu Scaffold.

Obraz układu kolumny, wiersza i pola.
Rysunek 1. Przykłady układów z kolumnami, wierszami i ramką.
.

Każdy z tych elementów kompozycyjnych pozwala określić wyrównanie w pionie i poziomie. jej zawartości oraz ograniczeń dotyczących szerokości, wysokości, wagi lub dopełnienia za pomocą funkcji modyfikatory. Dodatkowo każde wydawca podrzędny może zdefiniować swój modyfikator, aby zmienić pokój i umiejscowienie w środku elementu nadrzędnego.

Ten przykład pokazuje, jak utworzyć Row równomiernie rozłożony jego elementy podrzędne poziomo, tak jak na rys. 1:

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

Element Row wypełnia maksymalną dostępną szerokość, a ponieważ każde z nich ma taką samą szerokość będą równomiernie dzielić dostępne miejsce. Możesz zdefiniować różne wagi, rozmiary, dopełnienia i wyrównania, aby dostosować układy do swoich potrzeb.

Używaj układów z możliwością przewijania

Innym sposobem na dostarczanie elastycznych treści jest umożliwienie ich przewijania. To jest dzięki funkcji kompozycyjnej LazyColumn. Ta funkcja kompozycyjna pozwala zdefiniować zbiór elementów do wyświetlania w przewijanym kontenerze w widżecie aplikacji.

Poniższe fragmenty kodu pokazują różne sposoby definiowania elementów wewnątrz LazyColumn

Możesz podać liczbę elementów:

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

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

Podaj poszczególne elementy:

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

Podaj listę lub tablicę elementów:

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

Możesz też wykorzystać kombinację powyższych przykładów:

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")
    }
}

Pamiętaj, że poprzedni fragment kodu nie zawiera elementu itemId. Określanie parametru itemId pomaga poprawić wydajność i utrzymać przewijanie pozycji na liście i aktualizacji appWidget od Androida 12 (na np. podczas dodawania lub usuwania elementów z listy). Przykład poniżej pokazuje, jak określić itemId:

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

Zdefiniuj SizeMode

Rozmiary AppWidget mogą się różnić w zależności od urządzenia, wyboru użytkownika lub programu uruchamiającego, dlatego ważne jest, by zapewnić elastyczne układy, tak jak to opisano w narzędziu do elastycznych układów widżetów. Uprość to za pomocą funkcji Glance, używając SizeMode definicji i wartości LocalSize. W kolejnych sekcjach opisano 3 z nich: i trybów wyświetlania.

SizeMode.Single

SizeMode.Single to tryb domyślny. Wskazuje on, że tylko jeden typ została udostępniona, czyli nawet jeśli dostępny AppWidget rozmiar się zmieni, rozmiar treści się nie zmienia.

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
        // ...
    }
}

Podczas korzystania z tego trybu upewnij się, że:

  • Minimalny i maksymalny rozmiar wartości metadanych są prawidłowo zdefiniowane, na rozmiar treści.
  • Treść jest wystarczająco elastyczna i mieści się w oczekiwanym zakresie rozmiarów.

Ogólnie należy używać tego trybu, gdy:

a) AppWidget ma stały rozmiar lub b) nie zmienia swojej zawartości po zmianie rozmiaru.

SizeMode.Responsive

Ten tryb jest odpowiednikiem udostępniania układów elastycznych, co pozwala GlanceAppWidget, by zdefiniować zestaw układów elastycznych ograniczonych rozmiarów reklam Google Ads. W przypadku każdego zdefiniowanego rozmiaru treść jest tworzona i zmapowana na określony rozmiaru podczas tworzenia lub aktualizacji elementu AppWidget. System wybiera wtedy najlepiej dopasowanego na podstawie dostępnego rozmiaru.

Na przykład w miejscu docelowym AppWidget możesz zdefiniować trzy rozmiary i treść:

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")
            }
        }
    }
}

W poprzednim przykładzie metoda provideContent jest wywoływana 3 razy. mapowane na zdefiniowany rozmiar.

  • Przy pierwszym wywołaniu rozmiar przyjmuje wartość 100x100. Nie są one zgodne zawierać dodatkowy przycisk ani tekst na górze i na dole.
  • W drugim wywołaniu rozmiar przyjmuje wartość 250x100. Są to między innymi: dodatkowego przycisku, ale nie tekstu na górze i na dole.
  • W trzecim wywołaniu rozmiar przyjmuje wartość 250x250. Są to między innymi: dodatkowego przycisku i obu tekstów.

SizeMode.Responsive to połączenie 2 pozostałych trybów i umożliwia: tworzyć elastyczne treści w zdefiniowanych wcześniej granicach. Ogólnie ten tryb działa lepiej i umożliwia płynniejsze przejścia po zmianie rozmiaru elementu AppWidget.

W tabeli poniżej znajdziesz wartość rozmiaru w zależności od tych atrybutów: SizeMode AppWidget dostępny rozmiar:

Dostępny rozmiar 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
* Dokładne wartości są podane tylko w celach demonstracyjnych.

SizeMode.Exact

SizeMode.Exact jest odpowiednikiem podawania dokładnych układów, które żąda treści GlanceAppWidget za każdym razem, gdy dostępny rozmiar AppWidget zmian (na przykład gdy użytkownik zmieni rozmiar elementu AppWidget na ekranie głównym).

Na przykład w widżecie docelowym można dodać dodatkowy przycisk, jeśli dostępna szerokość jest większa od określonej wartości.

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")
                }
            }
        }
    }
}

Ten tryb zapewnia większą elastyczność niż pozostałe, ale oferuje kilka zastrzeżenia:

  • Przy każdej zmianie rozmiaru element AppWidget musi być całkowicie odtworzony. Ten może prowadzić do problemów z wydajnością i skoków interfejsu, gdy treść witryny jest skomplikowana.
  • Dostępny rozmiar może się różnić w zależności od implementacji programu uruchamiającego. Jeśli na przykład program uruchamiający nie podaje listy rozmiarów, minimalna wartość możliwy rozmiar.
  • Na urządzeniach z Androidem 12 lub starszym logika obliczania rozmiaru może nie działać w różnych sytuacjach.

Ogólnie należy używać tego trybu, jeśli nie można użyć usługi SizeMode.Responsive (np. zastosowanie niewielkiego zestawu układów elastycznych jest niemożliwe).

Dostęp do zasobów

Użyj kodu LocalContext.current, aby uzyskać dostęp do wszystkich zasobów Androida, tak jak to pokazuje następujący przykład:

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

Zalecamy bezpośrednie podanie identyfikatorów zasobów, aby zmniejszyć rozmiar ostatecznej RemoteViews i włączyć zasoby dynamiczne, takie jak dynamiczne

Elementy kompozycyjne i metody akceptują zasoby korzystające z usług „dostawcy”, takich jak ImageProvider lub użycie metody przeciążenia takiej jak GlanceModifier.background(R.color.blue) Na przykład:

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

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

Tekst uchwytu

Glance 1.1.0 zawiera interfejs API do ustawiania stylów tekstu. Ustaw style tekstu za pomocą: Atrybuty fontSize, fontWeight i fontFamily klasy TextStyle.

fontFamily obsługuje wszystkie czcionki systemowe, jak w przykładzie poniżej, ale czcionki niestandardowe w aplikacjach nie są obsługiwane:

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

Dodaj przyciski złożone

Złożone przyciski zostały wprowadzone w Androidzie 12. Spojrzenie w tył z tymi typami przycisków złożonych:

Każdy z tych przycisków złożonych wyświetla klikalny widok, który przedstawia „zaznaczone” stanu.

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"
)

Po zmianie stanu uruchomiona jest lambda. Możesz przechowywać jak w tym przykładzie:

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) }
        )
    }
}

Możesz też podać atrybut colors w elementach CheckBox, Switch i RadioButton, aby dostosować kolory:

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)
    ),

)

Dodatkowe komponenty

Glance 1.1.0 obejmuje udostępnienie dodatkowych komponentów, zgodnie z ta tabela:

Nazwa Obraz Link referencyjny Uwagi dodatkowe
Wypełniony przycisk tekst_alternatywny Komponent
Przyciski z konspektem tekst_alternatywny Komponent
Przyciski ikon tekst_alternatywny Komponent Główne / Dodatkowe / Tylko ikony
Pasek tytułu tekst_alternatywny Komponent
Ruszt Ruszt i pasek tytułu są w tej samej wersji demonstracyjnej.

Więcej informacji o projektach komponentów znajdziesz w tym zestaw projektowy na Figma.