Jetpack Compose 将动画完结的门槛降低了,不过Compose现在还不支持同享元素过渡。
(上篇文章Jetpack Compose开发的本地笔记本)的动画作用的完结
转跳前的准备工作
界说State
枚举类来表明页面的三种状况:
Closing(封闭状况)
Closed(封闭完结状况)
Opening(打开状况)
\
enum class CreateNoteState {
Closing, Closed, Opening
}
Jetpack Compose中的mutableStateOf()
函数来创立可变状况,并别离初始化了三个变量cardSize
、createNoteUIOffset
和currentCreateNoteState
。
cardSize
是一个IntSize
类型的可变状况,用来表明页面的尺寸,初始值为(0, 0)
。
createNoteUIOffset
是一个IntOffset
类型的可变状况,用来表明创立笔记界面的偏移量,初始值为(0, 0)
。
currentCreateNoteState
是一个枚举类型CreateNoteState
的可变状况,用来表明创立笔记界面的当时状况,初始值为State.Closed
,即封闭状况。这个枚举类型可能包含Closing
、Closed
、Opening
等状况。
var cardSize by mutableStateOf(IntSize(0, 0))
var createNoteUIOffset by mutableStateOf(IntOffset(0, 0))
var currentCreateNoteState by mutableStateOf(CreateNoteState.Closed)
点击转跳的按钮
onSizeChanged
用于在转跳按钮 的巨细发生改变时更新布局,并将新的巨细传递给 onSizedChanged
回调函数。
onGloballyPositioned
用于在 转跳按钮 的方位发生改变时更新布局,并将新的方位传递给 intOffset
变量。
最终,当用户点击 转跳按钮 时,会调用 onClick
回调函数,并将 intOffset
变量作为参数传递出去。
@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 ->
//点击事情
viewModel.currentCreateNoteState = CreateNoteState.Opening
viewModel.createNoteUIOffset = offset
}
转跳界面
记载页面的巨细信息,包含
cardSize(折叠状况巨细)、
fullSize(彻底打开状况巨细)
cardOffset(折叠状况页面在屏幕中的偏移方位)。
CreateNotePage(
viewModel.currentCreateNoteState,
viewModel.cardSize,
viewModel.fullSize,
viewModel.createNoteUIOffset,
{
viewModel.currentCreateNoteState = CreateNoteState.Closing
},
{
viewModel.currentCreateNoteState = CreateNoteState.Closed
})
界说offsetAnimatable来记载和操控页面在动画过程中在屏幕中的偏移改变。运用animateTo()函数来完结从cardOffset改变到fullOffset的平移动画作用。
var animReady by remember { mutableStateOf(false) }//标记动画准备
var animFinish by remember { mutableStateOf(false) }//标记动完结
val offsetAnimatable = remember { Animatable(IntOffset(0, 0), IntOffset.VectorConverter) }
val DEPLOYMENT_DURATION = 500 //动画速度
val cornerSize by animateDpAsState(if (animFinish) 0.dp else 16.dp) //圆角
运用LaunchedEffect
来监听CreateNoteState
的改变,并依据不同的状况触发相应的动画作用:- Opening状况:调用offsetAnimatable
的animateTo()
函数完结打开动画,将页面偏移从cardOffset
改变到fullOffset
;设置animFinish为true。- Closing状况:调用offsetAnimatable
的animateTo()
函数完结封闭动画,将页面偏移从fullOffset
改变到cardOffset;
设置animFinish为false和animReady为false。- Closed状况:页面封闭完结,无需履行任何操作。
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 -> {}
}
}
运用Box组件及其Modifier
使用offsetAnimatable.value
、巨细改变size
和圆角cornerSize
的动画作用在页面上显示。
if (pageState != CreateNoteState.Closed && animReady) {
Box(
Modifier
.offset { offsetAnimatable.value }
.clip(RoundedCornerShape(cornerSize))
.width(with(LocalDensity.current) { size.width.toDp() })
.height(with(LocalDensity.current) { size.height.toDp() })
) {
...
你的界面
...
}
}
完好作用图
完好代码
转跳按钮
HomeAddButton(
Modifier
.navigationBarsPadding()
.align(Alignment.BottomEnd),
onSizedChanged = {
viewModel.cardSize = it
}
) { offset ->
//点击事情
viewModel.currentCreateNoteState = CreateNoteState.Opening
viewModel.createNoteUIOffset = offset
//震动
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(
......
)
}
}
记载页面的巨细信息
/** 创立笔记 */
var cardSize by mutableStateOf(IntSize(0, 0))
var createNoteUIOffset by mutableStateOf(IntOffset(0, 0))
var currentCreateNoteState by mutableStateOf(CreateNoteState.Closed)
转跳的界面
@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 && 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) // 真实的界面
if (pageState == CreateNoteState.Closing){
Box(Modifier.fillMaxSize()
.alpha(alpha)
.background(background))
}
}
}
完好源码
JIULANG9/WordsFairyNote: 词仙笔记源码 (github.com)