一、吹牛逼的代价👍
之前三篇写了Compose UI相关的文章。后来在群里看到有人发了一些链接,上面都是一些花里胡哨的导航栏设计,当然了相当的花里胡哨了。然后自己觉得都能写,吹了一句"两小时我能搞定"
后来证实确实有点吹牛逼,最后被大佬们点名了,让我8小时写出来。当时觉得很简单,就动手了,事实是一顿分析猛如虎,两个小时原地杵。大佬们都指名我写了,那么接下来我们进行分析和堆代码。
前三章
Jetpack-Compose基本布局
JetPack-Compose - 自定义绘制
JetPack-Compose - Flutter 动态UI?
模仿的UI效果
二、回顾和分析👍👍
记得前三章我们进行编写了如下效果如下:
在上两节我们学习了Compose的自定义,贴了很多自定义的文章,如果你需要提升自己自定义技能我建议你动手动脑下功夫,没有捷径可走。这篇文章我们来挑战一下下面的动画。
1、分解
刚开始看着动画可能比较生猛,一时半会反应不过来,当然GIF动画就是一帧帧的静态图片顺序播放,所以用Mac就可以右键用选择系统自带的图片预览工具打开,这样你就可以分析动画的每个阶段的特效了。
分析如下
1. 默认进入界面
2. 过渡部分
3. 水滴分离Bootom
4. 恢复原位
上面4个步骤中或者你仔细走过每一个帧就会有自己的想法。当然这里不同人有不同的思路,只要效果对那就是对的。在我看来分为两部分绘制一个是运动的水滴💧
另一个是有凸起且有高低起伏动效的底部
。
2、运动的水滴
向上运动的水滴,且逐渐变圆,又有弹性。
1、水滴绘制
绘制正是由于贝塞尔曲线才突破了天花板。什么东西没有它搞不定的,画人物,山水,鸟兽....都离不开曲线。上面分析的水滴我们当然也可以用贝塞尔曲线来解决。
圆也是可以通过贝塞尔曲线绘制,用贝塞尔画圆需要因子.因子决定了圆的程度,圆的因子0.551915024494f
,我直接用的0.5也很圆,当然了我是为了计算方便而已,P1,P2,P4,P5,P7,P8,P10,P11都是半径的中点,P如下图控制点分别为p1->p2->p3段,p4->p5->p6段,p7->p8->p9段,p10->p11->p0段。
代码来一波
绘制圆
1.将坐标系移动到屏幕中心,变换坐标系圆点为屏幕中心。
2.求P1->P11之间的所有坐标点。
3.绘制Path即可
val paint = Paint()
paint.color = Color.RED
paint.style = Paint.Style.FILL
paint.strokeWidth = 10f
paint.isAntiAlias = true
val r = 200.0f
canvas.drawCircle(100f,100f,100f,paint)
canvas.scale(1f, -1f)
canvas.translate(width / 2f, -height / 2f)
//圆的坐标和中心点的坐标计算
//1.首先 原点为(0f,0f)且半径r=100f--->那么p6(0f,r),p5=(r/2,r),p4(r,r/2),p3(r,0f)
//2.第二象限里面 p2(r,-r/2),p1(r/2,-r),p0(0f,r)
//3.第三象限里面 p11(-r/2,-r),p10(-r,-r/2),p9(-r,0f)
//4.第四象限里面 p8(-r,r/2),p7(r/2,r),p6(0f,r)
val P0 = PointF(0f, -r)
val P1 = PointF(r / 2, -r)
val P2 = PointF(r, -r / 2)
val P3 = PointF(r, 0f)
val P4 = PointF(r, r / 2 )
val P5 = PointF(r / 2, r)
val P6 = PointF(0f, r )
val P7 = PointF(-r / 2, r )
val P8 = PointF(-r, r / 2)
val P9 = PointF(-r, 0f)
val P10 = PointF(-r, -r / 2)
val P11 = PointF(-r / 2, -r)
val path = Path()
path.moveTo(P0.x, P0.y )
//p1->p2->p3
path.cubicTo(P1.x, P1.y, P2.x, P2.y , P3.x, P3.y)
//p4->p5->p6
path.cubicTo(P4.x, P4.y, P5.x, P5.y, P6.x, P6.y)
//p7->p8->p9
path.cubicTo(P7.x, P7.y, P8.x, P8.y, P9.x, P9.y)
//p10->p11->p0
path.cubicTo(
P10.x,
P10.y ,
P11.x,
P11.y ,
P0.x,
P0.y
)
path.close()
canvas.drawPath(path, paint)
}
如下图效果:
绘制水滴
水滴如下图片,下面有弧度我们可以控制P10,P11,P0,P1,P2控制下面的弧度。下面三个图结合起来看应该比较清楚不清楚看看之前写的Android自定义-曲线渐变填充
val paint = Paint()
paint.color = Color.RED
paint.style = Paint.Style.FILL
paint.strokeWidth = 10f
paint.isAntiAlias = true
val r = 200.0f
canvas.drawCircle(100f,100f,100f,paint)
canvas.scale(1f, -1f)
canvas.translate(width / 2f, -height / 2f)
//圆的坐标和中心点的坐标计算
//1.首先 原点为(0f,0f)且半径r=100f--->那么p6(0f,r),p5=(r/2,r),p4(r,r/2),p3(r,0f)
//2.第二象限里面 p2(r,-r/2),p1(r/2,-r),p0(0f,r)
//3.第三象限里面 p11(-r/2,-r),p10(-r,-r/2),p9(-r,0f)
//4.第四象限里面 p8(-r,r/2),p7(r/2,r),p6(0f,r)
val P0 = PointF(0f, -r)
val P1 = PointF(r / 2, -r)
val P2 = PointF(r, -r / 2)
val P3 = PointF(r, 0f)
val P4 = PointF(r, r / 2 )
val P5 = PointF(r / 2, r)
val P6 = PointF(0f, r )
val P7 = PointF(-r / 2, r )
val P8 = PointF(-r, r / 2)
val P9 = PointF(-r, 0f)
val P10 = PointF(-r, -r / 2)
val P11 = PointF(-r / 2, -r)
val path = Path()
path.moveTo(P0.x, P0.y-60 )
//p1->p2->p3
path.cubicTo(P1.x, P1.y-30, P2.x, P2.y-30 , P3.x, P3.y)
//p4->p5->p6
path.cubicTo(P4.x, P4.y, P5.x, P5.y, P6.x, P6.y)
//p7->p8->p9
path.cubicTo(P7.x, P7.y, P8.x, P8.y, P9.x, P9.y)
//p10->p11->p0
path.cubicTo(
P10.x,
P10.y-30 ,
P11.x,
P11.y-30 ,
P0.x,
P0.y-60
)
path.close()
canvas.drawPath(path, paint)
}
水滴变换为圆、水滴从下网上运动
上面我们已经绘制了园和水滴,区别就是控制点P10,P11,P0,P1,P2进行了垂直方向变化,水滴上下运动也无非y值的不断增大。代码如下:
package com.example.android_draw.view.worter
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
@SuppressLint("WrongConstant")
class LHC_WaterDrop_View @JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var mCurAnimValue: Float = 1f
private var mCurAnimValueY: Float = 1f
private var animator: ValueAnimator = ValueAnimator.ofFloat(1f, 0f)
private var animatorY: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
init {
animator.duration = 1500
animator.interpolator = AccelerateDecelerateInterpolator()
animator.addUpdateListener { animation ->
mCurAnimValue = animation.animatedValue as Float
invalidate()
}
animator.repeatMode = ValueAnimator.INFINITE
animator.start()
animatorY.duration = 1500
animatorY.interpolator = AccelerateDecelerateInterpolator()
animatorY.addUpdateListener { animation ->
mCurAnimValueY = animation.animatedValue as Float
invalidate()
}
animatorY.repeatMode = ValueAnimator.INFINITE
animatorY.start()
}
@SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val paint = Paint()
paint.color = Color.RED
paint.style = Paint.Style.FILL
paint.strokeWidth = 10f
paint.isAntiAlias = true
val r = 100.0f-60f*(1-mCurAnimValue)
canvas.drawCircle(100f,100f,100f,paint)
canvas.scale(1f, -1f)
canvas.translate(width / 2f, -height / 2f)
//圆的坐标和中心点的坐标计算
//1.首先 原点为(0f,0f)且半径r=100f--->那么p6(0f,r),p5=(r/2,r),p4(r,r/2),p3(r,0f)
//2.第二象限里面 p2(r,-r/2),p1(r/2,-r),p0(0f,r)
//3.第三象限里面 p11(-r/2,-r),p10(-r,-r/2),p9(-r,0f)
//4.第四象限里面 p8(-r,r/2),p7(r/2,r),p6(0f,r)
val P0 = PointF(0f, -r + mCurAnimValueY * 160)
val P1 = PointF(r / 2, -r + mCurAnimValueY * 160)
val P2 = PointF(r, -r / 2 + mCurAnimValueY * 160)
val P3 = PointF(r, 0f + mCurAnimValueY * 160)
val P4 = PointF(r, r / 2 + mCurAnimValueY * 160)
val P5 = PointF(r / 2, r + mCurAnimValueY * 160)
val P6 = PointF(0f, r + mCurAnimValueY * 160)
val P7 = PointF(-r / 2, r + mCurAnimValueY * 160)
val P8 = PointF(-r, r / 2 + mCurAnimValueY * 160)
val P9 = PointF(-r, 0f + mCurAnimValueY * 160)
val P10 = PointF(-r, -r / 2 + mCurAnimValueY * 160)
val P11 = PointF(-r / 2, -r + mCurAnimValueY * 160)
val path = Path()
path.moveTo(P0.x, P0.y - 60 * mCurAnimValue)
//p1->p2->p3
path.cubicTo(P1.x, P1.y - 30 * mCurAnimValue, P2.x, P2.y - 30 * mCurAnimValue, P3.x, P3.y)
//p4->p5->p6
path.cubicTo(P4.x, P4.y, P5.x, P5.y, P6.x, P6.y)
//p7->p8->p9
path.cubicTo(P7.x, P7.y, P8.x, P8.y, P9.x, P9.y)
//p10->p11->p0
path.cubicTo(
P10.x,
P10.y - 30 * mCurAnimValue,
P11.x,
P11.y - 30 * mCurAnimValue,
P0.x,
P0.y - 60 * mCurAnimValue
)
path.close()
canvas.drawPath(path, paint)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_DOWN -> {
animator.start()
animatorY.start()
}
}
return true
}
}
三、Compose代码编写👍👍👍
1、升起的水滴💧
首先我们将宽度分为三等分,点击第一个底部导航按钮时候坐标圆点变换到如下黑色圆心处,就和上面的部分吻合了。
//每次点击底部导航栏圆心需要变换到每一等分中间位置
val centerHdX = size.width / 3 / 2 + size.width / 3*clickIndex
//这里坐标系位置圆点就为上边线中点
canvas.translate(centerHdX, height)
绘制水滴
同样我们绘制好每个控制点即可。然后将复杂的数字简单化设置曲线即可。
Box(
modifier = Modifier
.fillMaxWidth()
.height(100.dp)
.clickable() {}
) {
androidx.compose.foundation.Canvas(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
) {
drawIntoCanvas { canvas ->
//绘制底部曲线到底部
canvas.translate(0f, size.height)
canvas.scale(1f, -1f)
//--------------------------------------------------------------------------
canvas.save()
//中间凸起部分
val centerHdX = size.width / 3 / 2 + size.width / 3 * homeViewModel.position.value!!
//这里坐标系位置圆点就为上边线中点
canvas.translate(centerHdX, height)
//--------------------------------------------------------------------------
//绘制弹性圆球
//假设点击的是index=0一共三个底部按钮
canvas.save()
//1,2,3
//将坐标系移动到点击部位()这样写起来比较爽好理解。将点击部位作为我们的坐标系园点
val centerX =
size.width / 3 / 2 + size.width / 3 * homeViewModel.position.value!!
Log.e("圆点", "LoginPage: $centerX")
canvas.translate(centerX, height * 2 / 3.2f)
//canvas.drawCircle(Offset(0f, 0f), 100f, paint)
//这里我们清楚坐标圆点之后我们进行绘制我们的圆
val r = 100f - 50 * (1 - mCurAnimValue)
//圆的坐标和中心点的坐标计算
//1.首先 原点为(0f,0f)且半径r=100f--->那么p6(0f,r),p5=(r/2,r),p4(r,r/2),p3(r,0f)
//2.第二象限里面 p2(r,-r/2),p1(r/2,-r),p0(0f,r)
//3.第三象限里面 p11(-r/2,-r),p10(-r,-r/2),p9(-r,0f)
//4.第四象限里面 p8(-r,r/2),p7(r/2,r),p6(0f,r)
Log.e("mCurAnimValueY", "LoginPage=: $mCurAnimValueY")
val moveTopHeight = mCurAnimValueY * 250f
val P0 = Offset(0f, -r + moveTopHeight + animalScaleCanvasHeightValue)
val P1 = Offset(r / 2, -r + moveTopHeight + animalScaleCanvasHeightValue)
val P2 = Offset(r, -r / 2 + moveTopHeight + animalScaleCanvasHeightValue)
val P3 = Offset(r, 0f + moveTopHeight + animalScaleCanvasHeightValue)
val P4 = Offset(r, r / 2 + moveTopHeight + animalScaleCanvasHeightValue)
val P5 = Offset(r / 2, r + moveTopHeight + animalScaleCanvasHeightValue)
val P6 = Offset(0f, r + moveTopHeight + animalScaleCanvasHeightValue)
val P7 = Offset(-r / 2, r + moveTopHeight + animalScaleCanvasHeightValue)
val P8 = Offset(-r, r / 2 + moveTopHeight + animalScaleCanvasHeightValue)
val P9 = Offset(-r, 0f + moveTopHeight + animalScaleCanvasHeightValue)
val P10 = Offset(-r, -r / 2 + moveTopHeight + animalScaleCanvasHeightValue)
val P11 = Offset(-r / 2, -r + moveTopHeight + animalScaleCanvasHeightValue)
val heightController = 180f
val pathReult = Path()
pathReult.moveTo(P0.x, P0.y - heightController * mCurAnimValue)
//p1->p2->p3
pathReult.cubicTo(
P1.x,
P1.y - 30 * mCurAnimValue,
P2.x,
P2.y - 30 * mCurAnimValue,
P3.x,
P3.y
)
//p4->p5->p6
pathReult.cubicTo(P4.x, P4.y, P5.x, P5.y, P6.x, P6.y)
//p7->p8->p9
pathReult.cubicTo(P7.x, P7.y, P8.x, P8.y, P9.x, P9.y)
//p10->p11->p0
pathReult.cubicTo(
P10.x,
P10.y - 30 * mCurAnimValue,
P11.x,
P11.y - 30 * mCurAnimValue,
P0.x,
P0.y - heightController * mCurAnimValue
)
pathReult.close()
//
paint.color = Color(245, 215, 254, mCurAnimValueColor.value.toInt() * 255)
canvas.drawPath(pathReult, paint)
}
}
}
2、底部弧度变换
我们看到底部弧度向上运动,当水滴向上一部分时候底部弧度逐渐降低且半径缩小。同样这部分我们寻找到控制点即可底部整体的缩放也一样在X方向进行水平缩放,y轴方向进行竖直缩放即可。
//绘制底部曲线到底部
//绘制底部曲线到底部
canvas.translate(0f, size.height)
canvas.scale(1f, -1f)
val paint = androidx.compose.ui.graphics.Paint()
paint.strokeWidth = 2f
paint.style = PaintingStyle.Fill
paint.color = Color(245, 215, 254, 255)
val height = 276f
val cicleHeight = height / 3
val ScaleHeight = animalScaleCanvasHeightValue
val ScaleWidth = animalScaleCanvasWidthValue
//控制脖子左边,一直在变化
val path = Path()
path.moveTo(0f + ScaleWidth, 0f)
path.lineTo(0f + ScaleWidth, height - cicleHeight + ScaleHeight)
path.quadraticBezierTo(
0f + ScaleWidth,
height + ScaleHeight,
cicleHeight,
height + ScaleHeight
)
//第一个左弧度
path.lineTo(size.width - cicleHeight - ScaleWidth, height + ScaleHeight)
path.quadraticBezierTo(
size.width - ScaleWidth,
height + ScaleHeight,
size.width - ScaleWidth,
height - cicleHeight + ScaleHeight
)
path.lineTo(size.width - ScaleWidth, 0f)
path.close()
canvas.drawPath(path, paint)
//--------------------------------------------------------------------------
canvas.save()
//中间凸起部分
val centerHdX =
size.width / 3 / 2 + size.width / 3 * homeViewModel.position.value!!
//这里坐标系位置圆点就为上边线中点
canvas.translate(centerHdX, height)
val R = 30f
//0-50是变大部分
val RH = mCurAnimalHeight
//50到-50是变为平
val p0 = Offset(-R, 0f + RH + animalScaleCanvasHeightValue)
val p1 = Offset(-R, R + RH + animalScaleCanvasHeightValue)
val p3 = Offset(0f, 2 * R - 30f + RH + animalScaleCanvasHeightValue)
val p5 = Offset(R, R + RH + animalScaleCanvasHeightValue)
val p6 = Offset(R, 0f + RH + animalScaleCanvasHeightValue)
val p7 = Offset(100f, -10f + animalScaleCanvasHeightValue)
val pathCub = Path()
pathCub.moveTo(-100f, 0f + animalScaleCanvasHeightValue)
pathCub.cubicTo(p0.x, p0.y, p1.x, p1.y, p3.x, p3.y)
pathCub.cubicTo(p5.x, p5.y, p6.x, p6.y, p7.x, p7.y)
canvas.drawPath(pathCub, paint)
//中间凸起部分落下
canvas.restore()
四、最终代码👍👍👍👍
class HomeViewModel: ViewModel() {
//首页选中项的索引
private val _position = MutableLiveData(-1)
//动画状态
val animalBoolean = mutableStateOf(true)
var position:LiveData<Int> = _position
//选中索引数据刷新
var bootomType=true
fun positionChanged(selectedIndex: Int){
_position.value=selectedIndex
}
}
@InternalComposeApi
@Composable
fun BottomNavigationTwo(homeViewModel:HomeViewModel){
val applyContext = currentComposer.applyCoroutineContext
val clickTrue = remember { mutableStateOf(false) }
val mCurAnimValueColor = remember { Animatable(1f) }
val animalBooleanState: Float by animateFloatAsState(
if (homeViewModel.animalBoolean.value) {
0f
} else {
1f
}, animationSpec = TweenSpec(durationMillis = 600),
finishedListener = {
if (it>=0.9f&&clickTrue.value){
homeViewModel.animalBoolean.value = !homeViewModel.animalBoolean.value
}
}
)
val stiffness = 100f
val animalScaleCanvasWidthValue: Float by animateFloatAsState(
if (!clickTrue.value) {
0f
} else {
30f
},
animationSpec = SpringSpec(stiffness = stiffness),
)
val animalScaleCanvasHeightValue: Float by animateFloatAsState(
if (!clickTrue.value) {
0f
} else {
30f
},
animationSpec = SpringSpec(stiffness = stiffness),
)
val mCurAnimalHeight: Float by animateFloatAsState(
if (!clickTrue.value) {
-30f
} else {
30f
},
animationSpec = SpringSpec(stiffness = stiffness),
)
val mCurAnimValueY: Float by animateFloatAsState(
if (!clickTrue.value) {
0f
} else {
1f
}, animationSpec = SpringSpec(stiffness = stiffness),
finishedListener = {
if (it >= 0.9f && clickTrue.value) {
CoroutineScope(applyContext).launch {
mCurAnimValueColor.animateTo(
0f,
animationSpec = SpringSpec(stiffness = stiffness)
)
}
}
if (it <= 0.01f && !clickTrue.value) {
CoroutineScope(applyContext).launch {
mCurAnimValueColor.animateTo(
1f,
animationSpec = SpringSpec(stiffness = stiffness)
)
}
}
//动画结束->回归原来位置
if (it > 0.9f && clickTrue.value) {
clickTrue.value = !clickTrue.value
}
}
//TweenSpec(durationMillis = 1600)
// DurationBasedAnimationSpec, FloatSpringSpec, FloatTweenSpec, KeyframesSpec, RepeatableSpec, SnapSpec, SpringSpec, TweenSpec
)
//半径的决定动画
val mCurAnimValue: Float by animateFloatAsState(
if (clickTrue.value) {
0f
} else {
1f
}, animationSpec = SpringSpec(dampingRatio = 1f, stiffness = 30f)
)
Box(contentAlignment = Alignment.BottomCenter,
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(100.dp)
.clickable() {}
) {
androidx.compose.foundation.Canvas(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
) {
drawIntoCanvas { canvas ->
//绘制底部曲线到底部
canvas.translate(0f, size.height)
canvas.scale(1f, -1f)
val paint = androidx.compose.ui.graphics.Paint()
paint.strokeWidth = 2f
paint.style = PaintingStyle.Fill
paint.color = Color(245, 215, 254, 255)
val height = 276f
val cicleHeight = height / 3
val ScaleHeight = animalScaleCanvasHeightValue
val ScaleWidth = animalScaleCanvasWidthValue
//控制脖子左边,一直在变化
val path = Path()
path.moveTo(0f + ScaleWidth, 0f)
path.lineTo(0f + ScaleWidth, height - cicleHeight + ScaleHeight)
path.quadraticBezierTo(
0f + ScaleWidth,
height + ScaleHeight,
cicleHeight,
height + ScaleHeight
)
//第一个左弧度
path.lineTo(size.width - cicleHeight - ScaleWidth, height + ScaleHeight)
path.quadraticBezierTo(
size.width - ScaleWidth,
height + ScaleHeight,
size.width - ScaleWidth,
height - cicleHeight + ScaleHeight
)
path.lineTo(size.width - ScaleWidth, 0f)
path.close()
canvas.drawPath(path, paint)
//--------------------------------------------------------------------------
canvas.save()
//中间凸起部分
val centerHdX =
size.width / 3 / 2 + size.width / 3 * homeViewModel.position.value!!
//这里坐标系位置圆点就为上边线中点
canvas.translate(centerHdX, height)
val R = 30f
//0-50是变大部分
val RH = mCurAnimalHeight
//50到-50是变为平
val p0 = Offset(-R, 0f + RH + animalScaleCanvasHeightValue)
val p1 = Offset(-R, R + RH + animalScaleCanvasHeightValue)
val p3 = Offset(0f, 2 * R - 30f + RH + animalScaleCanvasHeightValue)
val p5 = Offset(R, R + RH + animalScaleCanvasHeightValue)
val p6 = Offset(R, 0f + RH + animalScaleCanvasHeightValue)
val p7 = Offset(100f, -10f + animalScaleCanvasHeightValue)
val pathCub = Path()
pathCub.moveTo(-100f, 0f + animalScaleCanvasHeightValue)
pathCub.cubicTo(p0.x, p0.y, p1.x, p1.y, p3.x, p3.y)
pathCub.cubicTo(p5.x, p5.y, p6.x, p6.y, p7.x, p7.y)
canvas.drawPath(pathCub, paint)
//中间凸起部分落下
canvas.restore()
//--------------------------------------------------------------------------
//绘制弹性圆球
//假设点击的是index=0一共三个底部按钮
canvas.save()
//1,2,3
//将坐标系移动到点击部位()这样写起来比较爽好理解。将点击部位作为我们的坐标系园点
val centerX =
size.width / 3 / 2 + size.width / 3 * homeViewModel.position.value!!
Log.e("圆点", "LoginPage: $centerX")
canvas.translate(centerX, height * 2 / 3.2f)
//canvas.drawCircle(Offset(0f, 0f), 100f, paint)
//这里我们清楚坐标圆点之后我们进行绘制我们的圆
val r = 100f - 50 * (1 - mCurAnimValue)
//圆的坐标和中心点的坐标计算
//1.首先 原点为(0f,0f)且半径r=100f--->那么p6(0f,r),p5=(r/2,r),p4(r,r/2),p3(r,0f)
//2.第二象限里面 p2(r,-r/2),p1(r/2,-r),p0(0f,r)
//3.第三象限里面 p11(-r/2,-r),p10(-r,-r/2),p9(-r,0f)
//4.第四象限里面 p8(-r,r/2),p7(r/2,r),p6(0f,r)
Log.e("mCurAnimValueY", "LoginPage=: $mCurAnimValueY")
val moveTopHeight = mCurAnimValueY * 250f
val P0 = Offset(0f, -r + moveTopHeight + animalScaleCanvasHeightValue)
val P1 = Offset(r / 2, -r + moveTopHeight + animalScaleCanvasHeightValue)
val P2 = Offset(r, -r / 2 + moveTopHeight + animalScaleCanvasHeightValue)
val P3 = Offset(r, 0f + moveTopHeight + animalScaleCanvasHeightValue)
val P4 = Offset(r, r / 2 + moveTopHeight + animalScaleCanvasHeightValue)
val P5 = Offset(r / 2, r + moveTopHeight + animalScaleCanvasHeightValue)
val P6 = Offset(0f, r + moveTopHeight + animalScaleCanvasHeightValue)
val P7 = Offset(-r / 2, r + moveTopHeight + animalScaleCanvasHeightValue)
val P8 = Offset(-r, r / 2 + moveTopHeight + animalScaleCanvasHeightValue)
val P9 = Offset(-r, 0f + moveTopHeight + animalScaleCanvasHeightValue)
val P10 = Offset(-r, -r / 2 + moveTopHeight + animalScaleCanvasHeightValue)
val P11 = Offset(-r / 2, -r + moveTopHeight + animalScaleCanvasHeightValue)
val heightController = 180f
val pathReult = Path()
pathReult.moveTo(P0.x, P0.y - heightController * mCurAnimValue)
//p1->p2->p3
pathReult.cubicTo(
P1.x,
P1.y - 30 * mCurAnimValue,
P2.x,
P2.y - 30 * mCurAnimValue,
P3.x,
P3.y
)
//p4->p5->p6
pathReult.cubicTo(P4.x, P4.y, P5.x, P5.y, P6.x, P6.y)
//p7->p8->p9
pathReult.cubicTo(P7.x, P7.y, P8.x, P8.y, P9.x, P9.y)
//p10->p11->p0
pathReult.cubicTo(
P10.x,
P10.y - 30 * mCurAnimValue,
P11.x,
P11.y - 30 * mCurAnimValue,
P0.x,
P0.y - heightController * mCurAnimValue
)
pathReult.close()
//
paint.color = Color(245, 215, 254, mCurAnimValueColor.value.toInt() * 255)
//canvas.drawPath(pathReult, paint)
}
}
}
Row(
modifier = Modifier.fillMaxWidth().height(200.dp), horizontalArrangement = Arrangement.SpaceAround,verticalAlignment = Alignment.Bottom
) {
Image(
bitmap = getBitmap(resource = R.drawable.home),
contentDescription = "1",
modifier = Modifier
.modifiers(homeViewModel.position.value, 0, animalBooleanState)
.clickable {
homeViewModel.positionChanged(0)
clickTrue.value = !clickTrue.value
homeViewModel.animalBoolean.value = !homeViewModel.animalBoolean.value
}
)
Image(
bitmap = getBitmap(resource = R.drawable.center),
contentDescription = "1",
modifier = Modifier
.modifiers(homeViewModel.position.value, 1, animalBooleanState)
.clickable {
homeViewModel.positionChanged(1)
clickTrue.value = !clickTrue.value
homeViewModel.animalBoolean.value = !homeViewModel.animalBoolean.value
}
)
Image(
bitmap = getBitmap(resource = R.drawable.min),
contentDescription = "1",
modifier = Modifier
.modifiers(homeViewModel.position.value, 2, animalBooleanState)
.clickable {
homeViewModel.positionChanged(2)
clickTrue.value = !clickTrue.value
homeViewModel.animalBoolean.value = !homeViewModel.animalBoolean.value
}
)
}
}
}
fun Modifier.modifiers(
animalCenterIndex: Int?,
i: Int,
animalBooleanState: Float
): Modifier {
Log.e("currentValue=", "modifiers: "+animalCenterIndex.toString()+"=="+i )
return if (animalCenterIndex == i) {
Modifier
.padding(bottom = 35.dp + (animalBooleanState * 100).dp)
.width(25.dp)
.height(25.dp)
} else {
return Modifier
.padding(bottom = 35.dp - (animalBooleanState * 10).dp)
.width(25.dp)
.height(25.dp)
}
}
五、总结
炫酷的动画不一定适用与平时的应用,性能反而降低或者交互上更加烦琐不便,但是技术无错,我相信只要能通过软件设计出来的2D动画效果或UI,我们一定能通过同样的原理来实现效果。通过这几篇文章我们从基本的UI到各种各种花里胡哨的实现,UI应该敢于挑战,敢于尝试。这篇文章由于时间问题,动画的部分没有仔细的调整,如果用心调整animationSpec动画规则
可以做到更接近原动画的粘性效果,当然动画部分会另起章节,如果我的文章能带给你帮助或感受到丁点儿的炫酷那就留下大佬们的赞
👍👍👍👍👍👍👍👍👍👍👍👍和宝贵的意见
。
有时间我会补上下面的炫酷设计。