Освой Compose играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Compose: Темы и стилизация

Обновлено 1 января 2025

Стилизация приложений происходит на основе Material Design.

Сейчас студия использует Material Design 3. В старых проектах использовался Material Design 2. При переходе многое поменялось, будьте внимательны при переписывании старых проектов.


// Старый вариант
implementation("androidx.compose.material:material:1.5.0")

// Новый вариант
implementation("androidx.compose.material3:material3:1.3.1")
// или, если используется BOM
implementation("androidx.compose.material3:material3")

// самый новый способ
implementation(libs.androidx.material3)

Затем нужно заменить имена импортируемых классов.


// Старый вариант
import androidx.compose.material.Text
import androidx.compose.material.Shapes
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.material.Typography

// Новый вариант
import androidx.compose.material3.Text
import androidx.compose.material3.Shapes
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.Typography

При этом некоторые имена параметров изменились. Например, параметр body1 был заменён на bodyLarge в классе Typography.

Темы

Если посмотреть на структуру проекта, то увидим, что у нас есть папка /ui/theme, которая содержит файлы Color.kt, Shape.kt (теперь нет, можете добавить самостоятельно), Theme.kt, Type.kt.

Вы можете редактировать эти файлы, если хотите подстроить ваше приложение под свои нужды, используя собственные варианты шрифтов, цветов, фигур т.д.

Theme.kt

Файл Theme.kt содержит всю важную информацию о цветах, шрифтах, фигурах вашего приложения. Название главное функции состоит из имени вашего приложения + Theme.


// Если ваше приложение называется MyApp
@Composable
fun MyAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }

        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}

private val DarkColorScheme = darkColorScheme(
    primary = Purple80,
    secondary = PurpleGrey80,
    tertiary = Pink80
)

private val LightColorScheme = lightColorScheme(
    primary = Purple40,
    secondary = PurpleGrey40,
    tertiary = Pink40

    /* Other default colors to override
    background = Color(0xFFFFFBFE),
    surface = Color(0xFFFFFBFE),
    onPrimary = Color.White,
    onSecondary = Color.White,
    onTertiary = Color.White,
    onBackground = Color(0xFF1C1B1F),
    onSurface = Color(0xFF1C1B1F),
    */
)

В MainActivity.kt тема подключается к приложению.


class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           MyAppTheme {
               Surface(
                   modifier = Modifier.fillMaxSize()
               ) {
                   MyApp()
               }
           }
       }
   }
}

В функции предварительного просмотра также подключается тема.


@Preview(showBackground = true)
@Composable
fun MyAppPreview() {
    MyAppTheme {
        Greeting("Android")
    }
}

Стилизация

Функция MaterialTheme() имеет несколько параметров, которые определяют основы дизайна - цвет, типографика, фигуры.


@Composable
public fun MaterialTheme(
    colors: Colors,
    typography: Typography,
    shapes: Shapes,
    content: @Composable
() → Unit
): Unit

Мы можем использовать системные цвета следующим образом.


@Composable
fun Content() {
    Column(
        modifier = Modifier.background(MaterialTheme.colors.background)
    ) {
        Text(
            text = "Cat",
            color = MaterialTheme.colors.primary
        )
        Text(
            text = "Cat",
            color = MaterialTheme.colors.onSurface.copy(alpha = 0.1f)
        )
    }
}

Важный момент - если мы используем цвета из Material Design, то система сама подбирает цвета для компонентов, для которых мы не указали цвета сами. Например, мы присвоим для фона Surface цвет primary, а текст внутри Surface станет белым, хотя мы нигде не указали данный цвет.


@Composable
fun Content() {
    Surface(color = MaterialTheme.colors.primary) {
        Text (text = "Hello Kitty!")
    }
}

Если указать secondary, то цвет текста станет чёрным.

Можно создать собственную палитру цветов на основе Material Design и применять её в своём приложении. Об этом в следующий раз.

Таким же образом можно использовать типографику. При этом можно переписать настройки стиля, например, изменить на свой размер шрифта вместо размера по умолчанию.


@Composable
fun Content() {
    Column {
        Text(
            text = "Cat",
            style = MaterialTheme.typography.subtitle1,
            fontSize = 22.sp // explicit size overrides the size in the style
        )
        Text(
            text = "Cat",
            style = MaterialTheme.typography.body1.copy(
                background = MaterialTheme.colors.secondary
            )
        )
    }
}

Для формирования собственных цветовых схем для темы можно использовать Material Theme Builder

Когда вы в указанном построителе выберите свой цвет, например, для Primary, то увидите cгенерированные варианты Light Scheme и Dark Scheme.

Самые важные цвета - primary, secondary, tertiary. Есть также множество другие вариантов, которые вы тоже можете использовать.

Когда вы выберите все необходимые настройки, выбирайте кнопку экспорта и получите архив готовых папок с файлами.

Создадим свою тему. Мы создаём собственные переменные MyTypography, MyColorPalette и применяем их в параметрах MaterialTheme.


fun MyCustomTheme(content: @Composable (PaddingValues) -> Unit) {
  MaterialTheme(
    typography = MyTypography, // Custom typography
    colors = MyColorPalette,   // Custom color palette
    content = content
  )
}

val MyTypography = Typography(
  defaultFontFamily = FontFamily.Serif,
  h1 = TextStyle(
    fontFamily = FontFamily.SansSerif,
    fontWeight = FontWeight.Bold,
    fontSize = 30.sp
  ),
)

val MyColorPalette = lightColors(
  primary = Color.Red,
  secondary = Color.Green,
  background = Color.White,
)

Color.kt

В Material Design существуют именованные цвета Primary, Primary Variants, Secondary, Secondary Variants. На сайте Material Design есть отдельная страница про используемую цветовую систему.

Откроем файл Color.kt. Там увидим несколько готовых переменных. Добавляем свои цвета по желанию.


package ru.alexanderklimov.compose.ui.theme

import androidx.compose.ui.graphics.Color

val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)

val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

// Добавляем свои цвета
val Navy = Color(0xFF073042)
val Blue = Color(0xFF4285F4)
val LightBlue = Color(0xFFD7EFFE)
val Chartreuse = Color(0xFFEFF7CF)

Значения цветов задаются по следующей схеме - задаётся значение в шестнадцатеричном виде, сначала идёт символ решётки (#), затем шесть или восемь символов (если используется прозрачность). Если используется прозрачность, то сначала идут два символа, которые отвечают за степень прозрачности в пределах от #00 (полная прозрачность) до #FF (сплошной цвет). Если прозрачность не указана, значит используется сплошной цвет #FF, тогда можно обойтись шестью символами вместо восьми.

После первых двух символов прозрачности (если используются), идут шесть символов, отвечающих за красный, зелёный и синий цвет в формате RGB (порядок важен).

Color value

Type.kt

Файл Type.kt содержит настройки для шрифтов.

Стандартный вариант использования темы в текстах.


Text(
    text = "Коты",
    style = MaterialTheme.typography.displayLarge
)

Здесь также можно добавить свои настройки.


// Вариант по умолчанию в студии
val Typography = Typography(
    bodyLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp,
        lineHeight = 24.sp,
        letterSpacing = 0.5.sp
    )
)

// Добавим свой вариант
val MyCustomTypography = Typography(
    bodyLarge = TextStyle(
        fontFamily = FontFamily.SansSerif,
        fontWeight = FontWeight.Normal,
        fontSize = 18.sp
    ),
    // другие стили
    titleLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.W500,
        fontSize = 14.sp
    )
)

В файле Theme.kt указываем свой вариант у MaterialTheme.


MaterialTheme(
    colors = colors,
    typography = MyCustomTypography, //вместо Typography,
    shapes = Shapes,
    content = content
)

Применяем стиль.


Text(
    text = "Customized TextStyle (BodyLarge)",
    style = MaterialTheme.typography.bodyLarge
)

Как вариант, вы можете добавить свой собственный стиль в существующий набор. Для этого создаём функцию-расширение в Type.kt.


val Typography.catTitle: TextStyle
@Composable
get() {
    return  TextStyle(
        fontFamily = FontFamily.Monospace,
        fontWeight = FontWeight.Bold,
        fontSize = 30.sp
    )
}

Применяем стиль к компоненту.


// Стиль для заголовков про котов
Text(text = "Барсик", style = MaterialTheme.typography.catTitle)

Шрифты и типографика также описаны на сайте Material Design.

Добавить свой шрифт

Создаём папку res/font. Размещаем в ней свой шрифт, например, cat_bold.oft (имя придумано).

Прописываем шрифт.


val CatFont = FontFamily(
Font(R.font.cat_regular,FontWeight.Normal),
Font(R.font.cat_bold,FontWeight.Bold)
)

Добавляем в Typography.


val Typography = Typography(
    
    subtitle1 = TextStyle(
        fontFamily = CatFont,
        fontWeight = FontWeight.Bold,
        fontSize = 30.sp,
    ),
    subtitle2 = TextStyle(
        fontFamily = CatFont,
        fontSize = 20.sp,
    )
)

Применяем.


Text(text = "Cat bold", style = MaterialTheme.typography.subtitle1)
Text(text = "Cat regular", style = MaterialTheme.typography.subtitle2)

Shape.kt

Третий важный раздел Material Design - поддерживаемые фигуры.

Темы для фигур обычно определяют в файле Shape.kt в ui.theme. Раньше такой файл был в студии. Сейчас его убрали, но можно создать самостоятельно.

Наполним файл содержанием.


import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp

val Shapes = Shapes(
    small = RoundedCornerShape(50.dp),
    medium = RoundedCornerShape(bottomStart = 16.dp, topEnd = 16.dp)
)

В Theme.kt добавляем новую строчку.


MaterialTheme(
    colorScheme = colorScheme,
    shapes = Shapes,
    typography = Typography,
    content = content
)

Теперь все компоненты, которые используют темы (практически все стандартные компоненты), будут выглядеть по другому. Например, Card использует вариант medium, поэтому он станет не с закруглёнными углами по умолчанию, а с закруглёнными углами только в двух местах. Проверьте сами.

Пример использования фигур на кнопках.


@Composable
fun Content() {
    Column(modifier = Modifier.padding(8.dp)) {
        Button(
            onClick = { /*TODO*/ },
        ) {
            Text(text = "Click me")
        }
        Spacer(modifier = Modifier.height(8.dp))
        Button(
            onClick = { /*TODO*/ },
            shape = MaterialTheme.shapes.large
        ) {
            Text(text = "Click me")
        }
        Spacer(modifier = Modifier.height(8.dp))
        Button(
            onClick = { /*TODO*/ },
            shape = MaterialTheme.shapes.small.copy(
                bottomStart = ZeroCornerSize, // overrides small theme style
                bottomEnd = ZeroCornerSize // overrides small theme style
            )
        ) {
            Text(text = "Click me",
            style = MaterialTheme.typography.body2,
            color = MaterialTheme.colors.secondary)
        }
    }
}

Material Design

Программное определение тёмной темы

Также мы можем разрабатывать адаптивное приложение, узнавая текущую тему. Если на данный момент пользователь переключился на тёмную тему, то можно поменять, например, цвет прямоугольника на более подходящий.


@Composable
fun Content(modifier: Modifier = Modifier) {
    Column(
        modifier = modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Box(
            modifier = modifier
                .size(400.dp)
                .background(
                    if (isSystemInDarkTheme()) Purple40
                    else Pink40
                )
        )
    }
}

Предпросмотр

Вы можете использовать параметр darkTheme в @Preview, чтобы видеть внешний вид приложения или отдельных компонентов в тёмной теме.


@Preview
@Composable
fun MyAppDarkThemePreview() {
    MyAppTheme(darkTheme = true) {
        Content()
    }
}

Дополнительное чтение

Реклама