Шпаргалка Ultimate Jetpack Compose
16 февраля 2023 г.Последние несколько недель я изучал компоновку и решил написать пост, чтобы поделиться своими заметками. Этот пост не предназначен для того, чтобы научить вас всему, что касается Compose, а скорее как дорожную карту, которую вы можете использовать, чтобы изучить Compose или узнать, чего вы еще не знаете о Compose.
Эта статья будет постоянно обновляться по мере того, как я узнаю что-то новое.
Поднятие состояния
Поднятие состояния — это шаблон перемещения состояния вверх, чтобы сделать компонент без состояния.
Применительно к составным объектам это часто означает добавление к составному объекту двух параметров.
- value: T – текущее отображаемое значение
- при изменении значения: (T) -> Единица – событие, запрашивающее изменение значения, где T – предлагаемое новое значение.
Местная композиция
Инструмент для неявной передачи данных через композицию.
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(...)
}
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
Icon(...)
Text(...)
}
композицияLocalOf
Изменение значения, предоставленного во время перекомпоновки, делает недействительным только контент, который считывает его текущее значение.
data class Elevations(val card: Dp = 0.dp, val default: Dp = 0.dp)
val LocalElevations = compositionLocalOf { Elevations() } // Create composition with default value
val elevations = Elevations(card = 4.dp, default = 2.dp) // Provide different value here
CompositionLocalProvider(LocalElevations provides elevations) { ... }
staticCompositionLocalOf
Чтение staticCompositionLocalOf
не отслеживается Compose. Изменение значения приводит к перекомпоновке всего лямбда-выражения контента, в котором предоставляется CompositionLocal.
Если маловероятно, что значение, предоставленное CompositionLocal, изменится или никогда не изменится, используйте staticCompositionLocalOf
, чтобы повысить производительность.
Отслеживание изменений
помнить
Значение, вычисленное функцией запоминания, будет сохранено в дереве композиции и будет пересчитано только в случае изменения ключей для запоминания.
При добавлении памяти в компонуемый объект всегда спрашивайте себя: «Захочет ли какой-либо вызывающий объект управлять этим?»
- Если ответ положительный, создайте параметр.
- Если ответ отрицательный, сохраните его как локальную переменную.
Remember сохраняет значения в композиции и забывает о них, если компонуемый объект, вызывающий запоминание, удаляется. Это означает, что вы не должны полагаться на хранение важных вещей внутри составных объектов, которые добавляют и удаляют дочерние элементы, такие как LazyColumn
.
помните, что можно сохранить
Он ведет себя аналогично запоминанию, но сохраненное значение сохранится после действия или восстановления процесса с использованием механизма сохраненного состояния экземпляра
запомнитьUpdatedState
В некоторых ситуациях вам может понадобиться зафиксировать в эффекте значение, при изменении которого вы не хотите, чтобы эффект перезапускался. Создайте ссылку на это значение, которое можно захватить и обновить.
запомнитьLauncherForActivityResult
val result = remember { mutableStateOf<Bitmap?>(null) }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicturePreview()) {
result.value = it
}
Button(onClick = { launcher.launch() }) { ... }
result.value?.let { image ->
Image(image.asImageBitmap(), null, modifier = Modifier.fillMaxWidth())
}
запомнить CoroutineScope
Чтобы запустить сопрограмму за пределами составного объекта, но с такой областью действия, чтобы она автоматически отменялась после выхода из композиции
<цитата>LaunchedEffect
используется для определения объема заданий, инициированных композицией. rememberCoroutineScope
предназначен для определения объема заданий, инициированных взаимодействием с пользователем.
Эффект запуска
Безопасный вызов функций приостановки из составного объекта.
Сопрограмма будет отменена, если LaunchedEffect покинет композицию.
Если LaunchedEffect перекомпонован с другими ключами, существующая сопрограмма будет отменена, а новая функция приостановки будет запущена в новой сопрограмме.
val currentOnTimeout by rememberUpdatedState(onTimeout)
LaunchedEffect(true) {
delay(SplashWaitTimeMillis)
currentOnTimeout()
}
снимок потока
Преобразовать объекты Compose State
Одноразовый эффект
DisposableEffect предназначен для побочных эффектов, которые необходимо устранить после изменения ключей или выхода компонуемого из композиции.
DisposableEffect(dispatcher) {
dispatcher.addCallback(backCallback)
onDispose {
backCallback.remove()
}
}
производное состояние
derivedStateOf используется, когда вы хотите, чтобы состояние создания было производным от другого состояния.
Государственный держатель
О держателях состояний всегда нужно помнить, чтобы удерживать их в Композиции, а не создавать каждый раз новую. Хорошей практикой является создание метода в том же файле, который делает это, чтобы удалить шаблон и избежать возможных ошибок.
Макеты
LazyColumn и LazyRow
Не перерабатывайте их дочерние элементы, как RecyclerView. Он генерирует новые Composables по мере того, как вы прокручиваете его, и по-прежнему работает эффективно, поскольку создание Composables относительно дешево по сравнению с созданием экземпляров Android Views.
val listState = rememberLazyListState()
LazyColumn(
contentPadding,
verticalArrangement / horizontalArrangement = Arrangement.spacedBy
) {
stickyHeader { Header() } // Experimental
items(5) { index -> Text(text = "Item: $index") } // key = ...
item { Text(text = "Last item") }
}
LaunchedEffect(listState) {
snapshotFlow { listState.firstVisibleItemIndex }
}
firstVisibleItemIndex
firstVisibleItemScrollOffset
scrollToItem()
animateScrollToItem()
Макет ограничения
ConstraintLayout {
val (button, text) = createRefs()
Button(
modifier = Modifier.constrainAs(button) {
top.linkTo(parent.top, margin = 16.dp)
}
)
Text("Text", Modifier.constrainAs(text) {
top.linkTo(button.bottom, margin = 16.dp)
})
}
ConstraintSet {
val button = createRefFor("button")
val text = createRefFor("text")
constrain(button) { ... }
constrain(text) { ... }
}
Макет
Вместо того, чтобы контролировать, как один составной элемент измеряется и размещается на экране, вам может понадобиться группа составных элементов. Для этого вы можете использовать компоновку макета, чтобы вручную контролировать, как измерять и размещать дочерние элементы макета.
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// Don't constrain child views further, measure them with given constraints
val placeables = measurables.map { measurable -> measurable.measure(constraints) }
// Set the size of the layout as big as it can
layout(constraints.maxWidth, constraints.maxHeight) {
var yPosition = 0
placeables.forEach { placeable ->
placeable.placeRelative(x = 0, y = yPosition)
yPosition += placeable.height
}
}
}
Внутренние измерения
Строка(modifier = modifier.height(IntrinsicSize.Min))
Навигация
val navController = rememberNavController()
val backstackEntry = navController.currentBackStackEntryAsState()
NavHost(navController = navController, startDestination = "profile") {
composable(
"profile/{userId}/?mode={mode}",
arguments = listOf(
navArgument("userId") { type = NavType.StringType },
navArgument("mode") { defaultValue = "lite" },
),
deepLinks = listOf(navDeepLink {
uriPattern = "rally://$accountsName/{name}"
})
) {
Profile(/*...*/)
}
loginGraph()
}
fun NavGraphBuilder.loginGraph(navController: NavController) {
navigation(startDestination = "username", route = "login") {
composable("username") { ... }
}
}
navController.navigate("friends") {
popUpTo("home") { inclusive = true }
}
navController.navigate(screen.route) {
// Pop up to the start destination of the graph to avoid building up a large stack
// of destinations on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
Модель представления
@HiltViewModel
class ExampleViewModel @Inject constructor(
...
) : ViewModel() { /* ... */ }
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity()
@Composable
fun ExampleScreen(
exampleViewModel: ExampleViewModel = viewModel()
) { /* ... */ }
Анимация
Вы можете создать значение анимации, просто заключив изменяющееся значение в соответствующий вариант составных объектов animate*AsState
.
- Float, Color, Dp, Size, Bounds, Offset, Rect, Int, IntOffset и IntSize
Вы можете комбинировать несколько объектов перехода с помощью оператора +.
MutableTransitionState(false).apply {
targetState = true // Start the animation immediately
// isIdle, currentState
}
Анимированная видимость
- Содержимое в пределах
AnimatedVisibility
(прямые или косвенные дочерние элементы) может использовать модификатор animateEnterExit, чтобы указать различное поведение анимации для каждого из них.
Анимированный контент
SizeTransform
определяет, как размер должен анимироваться между исходным и целевым содержимым.
animateContentSize
- Анимирует изменение размера
Кроссфейд
- Анимация между двумя макетами с плавной анимацией.
Анимация
- Animatable — это держатель значения, который может анимировать значение по мере его изменения с помощью
animateTo
. snapTo
немедленно устанавливает текущее значение в целевое значение.animateDecay
запускает анимацию, которая замедляется с заданной скоростью.
val color = remember { Animatable(Color.Gray) }
LaunchedEffect(ok) { color.animateTo(if (ok) Color.Green else Color.Red) }InfiniteTransition
Бесконечный переход
- Содержит одну или несколько дочерних анимаций, таких как переход, но анимация запускается, как только входит в композицию, и не останавливается, пока не будет удалена.
- Используйте функцию
rememberInfiniteTransition
.
Переход
Transition управляет одной или несколькими анимациями как дочерними и запускает их одновременно между несколькими состояниями.
var currentState by remember { mutableStateOf(BoxState.Collapsed) }
val transition = updateTransition(currentState)
// Start in collapsed state and immediately animate to expanded
var currentState = remember { MutableTransitionState(BoxState.Collapsed) }
currentState.targetState = BoxState.Expanded
val transition = updateTransition(currentState)
transition.AnimatedVisibility
transition.AnimatedContent
Спецификация анимации
spring(
dampingRatio = Spring.DampingRatioHighBouncy,
stiffness = Spring.StiffnessMedium
)
tween(
durationMillis = 300,
delayMillis = 50,
easing = LinearOutSlowInEasing
)
// animates based on the snapshot values specified at different timestamps
keyframes {
durationMillis = 375
0.0f at 0 with LinearOutSlowInEasing // for 0-15 ms
0.2f at 15 with FastOutLinearInEasing // for 15-75 ms
0.4f at 75 // ms
0.4f at 225 // ms
}
// runs a animation repeatedly until it reaches the specified iteration count
repeatable(
iterations = 3,
animation = tween(durationMillis = 300),
repeatMode = RepeatMode.Reverse
)
// like repeatable, but it repeats for an infinite amount of iterations
infiniteRepeatable(
animation = tween(durationMillis = 300),
repeatMode = RepeatMode.Reverse
)
// immediately switches the value to the end value
snap(delayMillis = 50)
Тематика
Определяя цвета, мы называем их «буквально», исходя из значения цвета, а не «семантически», например. Red500 не основной. Это позволяет нам определять несколько тем, например. другой цвет может считаться основным в темной теме или на экране с другим стилем.
isSystemInDarkTheme() / MaterialTheme.colors.isLight
color: Color = MaterialTheme.colors.surface, contentColor: Color = contentColorFor(color)
При установке цвета любых элементов предпочтительнее использовать для этого поверхность, так как она устанавливает соответствующее значение цвета содержимого CompositionLocal
. Будьте осторожны с прямыми вызовами Modifier.background, которые не устанавливают соответствующее содержимое. цвет.
Текстовое поле по умолчанию
textFieldColors
outlinedTextFieldColors
Предоставить стиль текста
Как же компоненты задают стиль оформления темы? Под капотом они используют компонуемый ProvideTextStyle
(который сам использует CompositionLocal) для установки «текущего» TextStyle. Составляемый текст по умолчанию запрашивает этот «текущий» стиль, если вы не укажете конкретный параметр textStyle.
Ресурсы
stringResource(R.string.congratulate, "New Year", 2021)
dimensionResource(R.dimen.padding_small)
painterResource(id = R.drawable.ic_logo)
animatedVectorResource(id = R.drawable.animated_vector)
Icon(Icons.Rounded.Menu)
Шрифты
private val light = Font(R.font.raleway_light, FontWeight.W300)
private val regular = Font(R.font.raleway_regular, FontWeight.W400)
private val medium = Font(R.font.raleway_medium, FontWeight.W500)
private val semibold = Font(R.font.raleway_semibold, FontWeight.W600)
// Create a font family to use in TextStyles
private val craneFontFamily = FontFamily(light, regular, medium, semibold)
сборка AnnotatedString
buildAnnotatedString {
append("This is some unstyled textn")
withStyle(SpanStyle(color = Color.Red)) {
append("Red textn")
}
pushStringAnnotation(tag = "URL", annotation = "<https://developer.android.com>")
withStyle(SpanStyle(fontSize = 24.sp)) {
append("Large text")
}
pop()
}
Общие модификаторы
align
alignBy
// animateEnterExit modifier can be used for any direct or indirect children
// of AnimatedVisibility to create a different enter/exit animation than
// what's specified in AnimatedVisibility.
animateEnterExit(enter, exit)
border
background
clip
clipToBounds
drawBehind
drawWithCache
drawWithContent
// The draw layer can be invalidated separately from parents.
// scaleX, scaleY, rotationXYZ, alpha, shadowElevation, shape, clip, shape
// Use with state values such as ScrollState or LazyListState
graphicsLayer
shadow
zIndex
onKeyEvent
// Creates a LayoutModifier that allows changing how the wrapped element is
// measured and laid out.
layout
absoluteOffset
offset
fillMaxHeight, fillMaxWidth, fillMaxSize
// matches the size of the Box after all other children have been measured
// to obtain the Box's size.
matchParentSize
heighIn(min, max), widthIn(min, max)
Модификаторы жестов
combinedClickable(onLongClick, onDoubleClick, onClick)
draggable(
orientation = Orientation.Horizontal,
state = rememberDraggableState { delta ->
val newValue = offsetPosition.value + delta
offsetPosition.value = newValue.coerceIn(minPx, maxPx)
}
)
swipeable(
state = swipeableState,
anchors = anchors,
thresholds = { _, _ -> FractionalThreshold(0.3f) },
orientation = Orientation.Horizontal
)
pointerInput(Unit) {
detectTapGestures(onPress, onDoubleTap, onLongPress, onTap)
detectDragGestures { change, dragAmount ->
change.consumeAllChanges()
offsetX += dragAmount.x
offsetY += dragAmount.y
}
}
horizontalScroll, verticalScroll
// Detects the scroll gestures, but does not offset its contents.
// Has nested scroll built in
scrollable
nestedScroll(nestedScrollConnection, nestedScrollDispatcher)
Разное
Локальный программный контроллер клавиатуры
val keyboardController = LocalSoftwareKeyboardController.current
// Calling this function is considered a side-effect and should not be called directly from recomposition
keyboardController.hide()
LocalOnBackPressedDispatcherOwner и BackHandler
var backHandlingEnabled by remember { mutableStateOf(true) }
var backPressedCount by remember { mutableStateOf(0) }
BackHandler(backHandlingEnabled) { backPressedCount++ }
val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
Button(onClick = { dispatcher.onBackPressed() }) {
Text("Press Back count $backPressedCount")
}
Локальная плотность
val sizeInPx = with(LocalDensity.current) { 16.dp.toPx() }
val (minPx, maxPx) = with(LocalDensity.current) { min.toPx() to max.toPx() }
Фокусреквестер
val focusRequester = remember { FocusRequester() }
var color by remember { mutableStateOf(Black) }
Box(
Modifier
.clickable { focusRequester.requestFocus() }
.border(2.dp, color)
// The focusRequester should be added BEFORE the focusable.
.focusRequester(focusRequester)
// The onFocusChanged should be added BEFORE the focusable that is being observed.
.onFocusChanged { color = if (it.isFocused) Green else Black }
.focusable()
)
Спасибо, что прочитали. Если у вас есть какие-либо предложения, не стесняйтесь обращаться ко мне.
Ресурсы
https://developer.android.com/jetpack/compose/architecture
https://developer.android.com/jetpack/compose/resources
https://developer.android.com/jetpack/compose/animation
https://developer.android.com/jetpack/compose/gestures
https://developer.android.com/jetpack/compose/navigation
https://developer.android.com/jetpack/compose/modifiers-list р>
https://developer.android.com/jetpack/compose/mental-model р>
https://developer.android.com/jetpack/compose/compositionlocal
https://developer.android.com/codelabs/jetpack-compose-basics р>
https://developer.android.com/codelabs/jetpack-compose-layouts р>
https://developer.android.com/codelabs/jetpack-compose-state р>
https://developer.android.com/codelabs/jetpack-compose-theming р>
https://developer.android.com/codelabs/jetpack-compose-animation р>
https://developer.android.com/codelabs/jetpack-compose-navigation р>
https://developer.android.com/codelabs/jetpack-compose-advanced -состояние-побочные-эффекты
https://developer.android.com/reference/kotlin/androidx /compose/runtime/saveable/package-summary
:::информация Также опубликовано здесь.
:::
Оригинал