Loading packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/ActionList.kt +81 −33 Original line number Diff line number Diff line Loading @@ -16,66 +16,114 @@ package com.android.systemui.ambientcue.ui.compose import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.gestures.AnchoredDraggableState import androidx.compose.foundation.gestures.DraggableAnchors import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.anchoredDraggable import androidx.compose.foundation.gestures.animateTo import androidx.compose.foundation.gestures.snapping.SnapPosition.End import androidx.compose.foundation.gestures.snapping.SnapPosition.Start import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.unit.dp import com.android.systemui.ambientcue.ui.viewmodel.ActionViewModel import kotlin.math.abs import kotlin.math.max @Composable fun ActionList( actions: List<ActionViewModel>, visible: Boolean, onDismiss: () -> Unit, modifier: Modifier = Modifier, horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, ) { val density = LocalDensity.current var containerHeightPx by remember { mutableIntStateOf(0) } // User should be able to drag down vertically to dismiss the action list. // The list will shrink as the user drags. val anchoredDraggableState = remember { AnchoredDraggableState(initialValue = if (visible) End else Start) } // A ratio from 0..1 representing the expansion of the list val progress by remember { derivedStateOf { abs(anchoredDraggableState.offset) / max(1, containerHeightPx) } } LaunchedEffect(progress) { if (progress == 0f) { onDismiss() } } LaunchedEffect(visible) { anchoredDraggableState.animateTo(if (visible) End else Start) } Column( modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp), modifier = modifier .anchoredDraggable( state = anchoredDraggableState, orientation = Orientation.Vertical, enabled = visible, ) .onGloballyPositioned { layoutCoordinates -> containerHeightPx = layoutCoordinates.size.height anchoredDraggableState.updateAnchors( DraggableAnchors { Start at 0f // Hidden End at -containerHeightPx.toFloat() // Visible } ) } .defaultMinSize(minHeight = 200.dp) .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.Bottom), horizontalAlignment = horizontalAlignment, ) { actions.forEachIndexed { index, action -> AnimatedVisibility( visible = visible, enter = slideInVertically( spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow, ) ) { with(density) { it * (actions.size - index) } } + scaleIn( val scale by animateFloatAsState( progress, animationSpec = spring( dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessLow, ) ), exit = slideOutVertically( ) val translation by animateFloatAsState( progress, animationSpec = spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow, ), ) Chip( action = action, modifier = Modifier.graphicsLayer { translationY = (1f - translation) * size.height * (actions.size - index) scaleX = scale scaleY = scale }, ) ) { with(density) { it * (actions.size - index) } } + scaleOut(tween(250)), ) { Chip(action) } } } } packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/AmbientCueContainer.kt +2 −0 Original line number Diff line number Diff line Loading @@ -176,6 +176,7 @@ private fun TaskBarAnd3ButtonAmbientCue( ActionList( actions = actions, visible = visible && expanded, onDismiss = { viewModel.collapse() }, horizontalAlignment = Alignment.End, modifier = modifier.graphicsLayer { Loading Loading @@ -214,6 +215,7 @@ private fun NavBarAmbientCue( ActionList( actions = actions, visible = visible && expanded, onDismiss = { viewModel.collapse() }, modifier = modifier.padding( bottom = NAV_BAR_ACTIONS_PADDING.dp, Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/ActionList.kt +81 −33 Original line number Diff line number Diff line Loading @@ -16,66 +16,114 @@ package com.android.systemui.ambientcue.ui.compose import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.gestures.AnchoredDraggableState import androidx.compose.foundation.gestures.DraggableAnchors import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.anchoredDraggable import androidx.compose.foundation.gestures.animateTo import androidx.compose.foundation.gestures.snapping.SnapPosition.End import androidx.compose.foundation.gestures.snapping.SnapPosition.Start import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.unit.dp import com.android.systemui.ambientcue.ui.viewmodel.ActionViewModel import kotlin.math.abs import kotlin.math.max @Composable fun ActionList( actions: List<ActionViewModel>, visible: Boolean, onDismiss: () -> Unit, modifier: Modifier = Modifier, horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, ) { val density = LocalDensity.current var containerHeightPx by remember { mutableIntStateOf(0) } // User should be able to drag down vertically to dismiss the action list. // The list will shrink as the user drags. val anchoredDraggableState = remember { AnchoredDraggableState(initialValue = if (visible) End else Start) } // A ratio from 0..1 representing the expansion of the list val progress by remember { derivedStateOf { abs(anchoredDraggableState.offset) / max(1, containerHeightPx) } } LaunchedEffect(progress) { if (progress == 0f) { onDismiss() } } LaunchedEffect(visible) { anchoredDraggableState.animateTo(if (visible) End else Start) } Column( modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp), modifier = modifier .anchoredDraggable( state = anchoredDraggableState, orientation = Orientation.Vertical, enabled = visible, ) .onGloballyPositioned { layoutCoordinates -> containerHeightPx = layoutCoordinates.size.height anchoredDraggableState.updateAnchors( DraggableAnchors { Start at 0f // Hidden End at -containerHeightPx.toFloat() // Visible } ) } .defaultMinSize(minHeight = 200.dp) .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.Bottom), horizontalAlignment = horizontalAlignment, ) { actions.forEachIndexed { index, action -> AnimatedVisibility( visible = visible, enter = slideInVertically( spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow, ) ) { with(density) { it * (actions.size - index) } } + scaleIn( val scale by animateFloatAsState( progress, animationSpec = spring( dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessLow, ) ), exit = slideOutVertically( ) val translation by animateFloatAsState( progress, animationSpec = spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow, ), ) Chip( action = action, modifier = Modifier.graphicsLayer { translationY = (1f - translation) * size.height * (actions.size - index) scaleX = scale scaleY = scale }, ) ) { with(density) { it * (actions.size - index) } } + scaleOut(tween(250)), ) { Chip(action) } } } }
packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/AmbientCueContainer.kt +2 −0 Original line number Diff line number Diff line Loading @@ -176,6 +176,7 @@ private fun TaskBarAnd3ButtonAmbientCue( ActionList( actions = actions, visible = visible && expanded, onDismiss = { viewModel.collapse() }, horizontalAlignment = Alignment.End, modifier = modifier.graphicsLayer { Loading Loading @@ -214,6 +215,7 @@ private fun NavBarAmbientCue( ActionList( actions = actions, visible = visible && expanded, onDismiss = { viewModel.collapse() }, modifier = modifier.padding( bottom = NAV_BAR_ACTIONS_PADDING.dp, Loading