Освой Compose играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Обновлено 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.
// Если ваше приложение называется 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,
)
В 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 (порядок важен).
Файл 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)
Третий важный раздел 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)
}
}
}
Также мы можем разрабатывать адаптивное приложение, узнавая текущую тему. Если на данный момент пользователь переключился на тёмную тему, то можно поменять, например, цвет прямоугольника на более подходящий.
@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()
}
}