Jetpack Compose lowers the threshold for animation implementation, but Compose does not currently support shared element transitions.
Realization of animation effects (local notebook developed by Jetpack Compose in the previous article)
Preparation before jumping
Define the State
enumeration class to represent the three states of the page:
Closing (closed state)
Closed (closed completion status)
Opening (expanded state)
\
enum class CreateNoteState {<!-- --> Closing, Closed, Opening }
The mutableStateOf()
function in Jetpack Compose creates a mutable state, and initializes three variables cardSize
, createNoteUIOffset
and currentCreateNoteState respectively
.
cardSize
is a variable state of IntSize
type, used to represent the size of the page, the initial value is (0, 0)
.
createNoteUIOffset
is a variable state of IntOffset
type, which is used to represent the offset of creating note interface, the initial value is (0, 0)
.
currentCreateNoteState
is a variable state of the enumeration type CreateNoteState
, which is used to represent the current state of the note creation interface. The initial value is State.Closed
, That is, the closed state. This enumeration type may include Closing
, Closed
, Opening
and other states.
var cardSize by mutableStateOf(IntSize(0, 0)) var createNoteUIOffset by mutableStateOf(IntOffset(0, 0)) var currentCreateNoteState by mutableStateOf(CreateNoteState.Closed)
Click the jump button
onSizeChanged
is used to update the layout when the size of the jump button changes, and pass the new size to the onSizedChanged
callback function.
onGloballyPositioned
is used to update the layout when the position of the jump button changes, passing the new position to the intOffset
variable.
Finally, when the user clicks the jump button , the onClick
callback function is called, passing the intOffset
variable as a parameter.
@Composable fun HomeAddButton( onSizedChanged: (IntSize) -> Unit, onClick: (offset: IntOffset) -> Unit, ) {<!-- --> var intOffset: IntOffset? by remember {<!-- --> mutableStateOf(null) } FloatingActionButton(onClick = {<!-- --> onClick(intOffset!!) }, Modifier .padding(16.dp) .onSizeChanged {<!-- --> onSizedChanged(it)} .onGloballyPositioned {<!-- --> val offset = it.localToRoot(Offset(0f, 0f)) intOffset = IntOffset(offset. x. toInt(), offset. y. toInt()) } ) {<!-- --> ?… } }
HomeAddButton( onSizedChanged = {<!-- --> viewModel. cardSize = it } ) {<!-- --> offset -> //click event viewModel.currentCreateNoteState = CreateNoteState.Opening viewModel.createNoteUIOffset = offset }
Jump interface
Record the size information of the page, including
cardSize (folded state size),
fullSize (fully expanded state size)
cardOffset (the offset position of the folded state page in the screen).
CreateNotePage( viewModel.currentCreateNoteState, viewModel. cardSize, viewModel. fullSize, viewModel.createNoteUIOffset, {<!-- --> viewModel.currentCreateNoteState = CreateNoteState.Closing }, {<!-- --> viewModel.currentCreateNoteState = CreateNoteState.Closed })
Define offsetAnimatable to record and control the offset change of the page in the screen during the animation process. Use the animateTo() function to realize the translation animation effect from cardOffset to fullOffset.
var animReady by remember {<!-- --> mutableStateOf(false) }//mark animation ready var animFinish by remember {<!-- --> mutableStateOf(false) }//mark animation completion val offsetAnimatable = remember {<!-- --> Animatable(IntOffset(0, 0), IntOffset. VectorConverter) } val DEPLOYMENT_DURATION = 500 // animation speed val cornerSize by animateDpAsState(if (animFinish) 0.dp else 16.dp) //rounded corners
Use LaunchedEffect
to monitor the changes of CreateNoteState
, and trigger corresponding animation effects according to different states: – Opening state: call offsetAnimatable
‘s The animateTo()
function realizes the expansion animation, and changes the page offset from cardOffset
to fullOffset
; set animFinish to true. – Closing state: Call the animateTo()
function of offsetAnimatable
to close the animation, and change the page offset from fullOffset
to cardOffset;
Sets animFinish to false and animReady to false. – Closed state: The page is closed and no action is required.
LaunchedEffect(pageState) {<!-- --> when (pageState) {<!-- --> CreateNoteState. Opening -> {<!-- --> animReady = true offsetAnimatable. snapTo(cardOffset) offsetAnimatable.animateTo(fullOffset, animationSpec = tween(DEPLOYMENT_DURATION)) animFinish = true } CreateNoteState.Closing -> {<!-- --> animFinish = false offsetAnimatable. snapTo(fullOffset) offsetAnimatable.animateTo(cardOffset, animationSpec = tween(DEPLOYMENT_DURATION)) animReady = false onPageClosed() } else -> {<!-- -->} } }
Use the Box component and its Modifier
to apply offsetAnimatable.value
, size change size
and rounded cornerSize
animation effects in displayed on the page.
if (pageState != CreateNoteState.Closed & amp; & amp; animReady) {<!-- --> Box( Modifier .offset {<!-- --> offsetAnimatable.value } .clip(RoundedCornerShape(cornerSize)) .width(with(LocalDensity.current) {<!-- --> size.width.toDp() }) .height(with(LocalDensity.current) {<!-- --> size.height.toDp() }) ) {<!-- --> ... your interface ... } }
Complete renderings
Complete code
Jump button
HomeAddButton( Modifier .navigationBarsPadding() .align(Alignment.BottomEnd), onSizedChanged = {<!-- --> viewModel. cardSize = it } ) {<!-- --> offset -> //click event viewModel.currentCreateNoteState = CreateNoteState.Opening viewModel.createNoteUIOffset = offset //shock feedback.performHapticFeedback(HapticFeedbackType.TextHandleMove) }
@Composable fun HomeAddButton( modifier: Modifier, onSizedChanged: (IntSize) -> Unit, onClick: (offset: IntOffset) -> Unit, ) {<!-- --> var intOffset: IntOffset? by remember {<!-- --> mutableStateOf(null) } FloatingActionButton(onClick = {<!-- --> onClick(intOffset!!) }, modifier .padding(16.dp) .onSizeChanged {<!-- --> onSizedChanged(it)} .onGloballyPositioned {<!-- --> val offset = it.localToRoot(Offset(0f, 0f)) intOffset = IntOffset(offset. x. toInt(), offset. y. toInt()) } ) {<!-- --> Icon( ?… ) } }
Record the size information of the page
/** create notes */ var cardSize by mutableStateOf(IntSize(0, 0)) var createNoteUIOffset by mutableStateOf(IntOffset(0, 0)) var currentCreateNoteState by mutableStateOf(CreateNoteState.Closed)
Jump interface
@OptIn(ExperimentalComposeUiApi::class) @Composable fun CreateNotePage( pageState: CreateNoteState, cardSize: IntSize, fullSize: IntSize, cardOffset: IntOffset, onPageClosing: () -> Unit, onPageClosed: () -> Unit ) { var animReady by remember { mutableStateOf(false) } var animFinish by remember { mutableStateOf(false) } val background by animateColorAsState( if (pageState == CreateNoteState.Closing) AppColor.themeColor else Color.Transparent) val alpha by animateFloatAsState( targetValue = if (pageState == CreateNoteState.Closing) 1f else 0.6f, animationSpec = tween(durationMillis = 300) ) val DEPLOYMENT_DURATION = 500 val size by animateIntSizeAsState(if (pageState > CreateNoteState.Closed) fullSize else cardSize, animationSpec = tween(DEPLOYMENT_DURATION)) val fullOffset = remember { IntOffset(0, 0) } val offsetAnimatable = remember { Animatable(IntOffset(0, 0), IntOffset. VectorConverter) } val cornerSize by animateDpAsState(if (animFinish) 0.dp else 16.dp) LaunchedEffect(pageState) { when (pageState) { CreateNoteState. Opening -> { animReady = true offsetAnimatable. snapTo(cardOffset) offsetAnimatable.animateTo(fullOffset, animationSpec = tween(DEPLOYMENT_DURATION)) animFinish = true } CreateNoteState. Closing -> { animFinish = false offsetAnimatable. snapTo(fullOffset) offsetAnimatable.animateTo(cardOffset, animationSpec = tween(DEPLOYMENT_DURATION)) animReady = false onPageClosed() } else -> {} } } if (pageState != CreateNoteState.Closed & amp; & amp; animReady) { Box( Modifier .offset { offsetAnimatable.value } .clip(RoundedCornerShape(cornerSize)) .width(with(LocalDensity.current) { size.width.toDp() }) .height(with(LocalDensity.current) { size.height.toDp() }) ) { CreateNoteUI(onBack = onPageClosing) // real interface if (pageState == CreateNoteState. Closing){ Box(Modifier. fillMaxSize() .alpha(alpha) .background(background)) } } }
Full source code
JIULANG9/WordsFairyNote: WordsFairyNote source code (github.com)