Jetpack Compose 将动画完结的门槛降低了,不过Compose现在还不支持同享元素过渡。

(上篇文章Jetpack Compose开发的本地笔记本)的动画作用的完结

转跳前的准备工作

界说State枚举类来表明页面的三种状况:
Closing(封闭状况)
Closed(封闭完结状况)
Opening(打开状况)\

enum class CreateNoteState {
    Closing, Closed, Opening
}

Jetpack Compose中的mutableStateOf()函数来创立可变状况,并别离初始化了三个变量cardSizecreateNoteUIOffsetcurrentCreateNoteState
cardSize是一个IntSize类型的可变状况,用来表明页面的尺寸,初始值为(0, 0)createNoteUIOffset是一个IntOffset类型的可变状况,用来表明创立笔记界面的偏移量,初始值为(0, 0)currentCreateNoteState是一个枚举类型CreateNoteState的可变状况,用来表明创立笔记界面的当时状况,初始值为State.Closed,即封闭状况。这个枚举类型可能包含ClosingClosedOpening等状况。

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状况:调用offsetAnimatableanimateTo()函数完结打开动画,将页面偏移从cardOffset改变到fullOffset;设置animFinish为true。- Closing状况:调用offsetAnimatableanimateTo()函数完结封闭动画,将页面偏移从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() })
    ) {
       ...
       你的界面
       ...
    }
}

完好作用图

Jetpack Compose 实现了一个丝滑流畅的页面展开和关闭的效果动画

完好代码

转跳按钮

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)