【一步教学,一步到位】花里胡哨的3D翻页卡片,隔壁产品都馋哭了

好文推荐
原文链接:https://juejin.im/post/6883357912489132045
作者:彭也

阅读完本文约需8分钟。

废不说,看图,有图有

带有立体纵深的卡片翻页效果,稍加组合和颜色变化就可以搭配出多种不同的风格,如:

比赛比分牌

卡片翻页时钟

一、设计思路

如何使得数字的变化更为灵动?3D纵深效果是个值得思考的方案。在现实生活中的一些特定场景,数字的变化就是通过翻页实现的,比如日历、比分牌、数学教具等等。

灵活替换翻页卡片的颜色和数字图片,即可展现多种不同风格

二、实现方案

2.1 3D纵深效果实现方案参考

为了在Android上实现3D纵深效果,可以考虑使用OpenGL进行,这样实现的效果是细节最丰富、最拟物的,但成本也巨大,为了一个控件使用OpenGL实在是用核弹打苍蝇——可以但没必要。

这里考虑采用Matrix+Camera结合的方案。

2.2 Matrix简介

android中的matrix是一个3*3的矩阵,如下图

scale控制缩放,skew控制错切,trans控制位移,persp控制透视,值得注意的是,旋转操作是通过scale和skew共同作用完成的。

android中matix的封装非常完善,使用者无需再去重拾体育老师教授的数学知识,直接调用。

2.3 Camera简介

这里指的是android.graphics包下的Camera,用于计算3D变换。封装得同样十分完善,使用者无需再用屁股思考控件坐标计算过程,直接调用,同时搭配好对应的matrix。

需要注意的是,Camera中摄像头的位置是对准画布左上角的。

坐标系如下,是左手坐标系:

常用方法如下:

2.4 UI拆解

2.4.1 形状分析

从形状上观察并不复杂,算上正在翻转的卡片,3个圆角矩形即可搞定。数字部分采用图片绘制,可以灵活替换,最后在指定卡片区域绘制图片即可。

2.4.2 模型设计

卡片翻转过程中,最多同一时刻出现3个卡片,故只需要按位置关系定义上中下三个卡片,中间的卡片负责翻转。

2.5 纵深效果实现

2.5.1 卡片绘制

绘制上中下卡片,中间卡片为活动页,需要最后绘制

override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
setLayerType(LAYER_TYPE_SOFTWARE, null)
//判断状态,不同状态绘制不同内容
judgeState(curState)
canvas?.let {
drawUpCard(it)
drawDownCard(it)
// 中间活动card最后绘制
drawMidCard(it)
}
}

2.5.2 实现卡片翻页

中间卡片翻页效果通过Camera的roate方法实现,绕x轴进行旋转

private fun drawMidCard(canvas: Canvas) {
if (!isNeedDrawMidCard) return
with(canvas) {
save()
mMatrix.reset()
mCamera.save()
mCamera.translate(0F, 0F, depthZ)
mCamera.rotateX(rotateX)
mCamera.rotateY(rotateY)
mCamera.getMatrix(mMatrix)
mCamera.restore()
val scale = resources.displayMetrics.density
val mValues = FloatArray(9)
mMatrix.getValues(mValues)
mValues[6] = mValues[6] / scale
mValues[7] = mValues[7] / scale
mMatrix.setValues(mValues)
mMatrix.preTranslate(-width / 2F, -height / 2F)
mMatrix.postTranslate(width / 2F, height / 2F)
concat(mMatrix)
mPaint.color = Color.WHITE
mPaint.setShadowLayer(cardShadowSize, 0F, cardShadowDistance, Color.GRAY)
val rectF = RectF(paddingSize, paddingSize + cardHeight, paddingSize + cardWidth, paddingSize + cardHeight * 2)
drawRoundRect(
rectF,
20F,
20F,
mPaint
)
//todo: 绘制数字图片
restore()
}
}
image

结合我在《今日头条loading控件,隔壁产品都馋哭了》文章中提到过的坐标计算框架,用户手指在屏幕上移动的距离与中间卡片旋转角度存在某种函数关系,通过IFunc进行保存和计算。

代码如下:

/**

  • Card翻转函数
    /
    var cardRotateFunc: IFunc? = null
    /
    *
  • 阴影大小变化函数
    /
    var cardShadowSizeFunc: IFunc? = null
    /
    *
  • 阴影距离变化函数
    /
    var cardShadowDistanceFunc: IFunc? = null
    /
    *
  • 配置各个函数
    */
    private fun configFunc() {
    cardRotateFunc = CardRotateFunc()
    with(cardRotateFunc!!) {
    inParamMin = 0F
    inParamMax = cardHeight * 2
    outParamMin = 0F
    outParamMax = 180F
    initValue = 45F
    }
    cardShadowSizeFunc = CardShadowSizeFunc()
    with(cardShadowSizeFunc!!) {
    inParamMin = 0F
    inParamMax = 180F
    outParamMax = 50F
    outParamMin = 0F
    initValue = 10F
    }
    cardShadowDistanceFunc = CardShadowDistanceFunc()
    with(cardShadowDistanceFunc!!) {
    inParamMin = 0F
    inParamMax = 180F
    outParamMax = 50F
    outParamMin = 0F
    initValue = 10F
    }
    }
    复制代码

2.5.3 阴影变化

为了更好地模拟3D效果,卡片阴影也存在微小的变化

/**

  • 根据旋转角度计算阴影大小、距离
    */
    private fun executeShadowFunc(rotate: Float) {
    cardShadowSizeFunc?.let {
    cardShadowSize = it.execute(rotate)
    }
    cardShadowDistanceFunc?.let {
    cardShadowDistance = it.execute(rotate)
    }
    }
    复制代码

2.5.4 数字图片绘制

数字的绘制就是图片的绘制,需要注意的是中间活动卡片在上翻或下翻转超过90度时,绘制的数字需要改变,涉及到图片的水平镜像翻转,调用matrix.postScale(-1F, 1F)实现。以下翻为例,代码如下:

if (curState == STATE_DOWN_ING) {
//往下翻
if (abs(cardRotateFunc!!.initValue - rotateX) >= 90F) {
//绘制前一个数字
if (curShowNum - 1 >= 0) {
tempBm = Bitmap.createBitmap(numBms[curShowNum - 1], 0, 0, curNumBm.width, curNumBm.height, matrix, false)
} else {
tempBm = Bitmap.createBitmap(numBms[0], 0, 0, curNumBm.width, curNumBm.height, matrix, false)
}
} else {
tempBm = Bitmap.createBitmap(numBms[curShowNum], 0, 0, curNumBm.width, curNumBm.height, matrix, false)
}
}
tempBm?.let {
drawBitmap(it, Rect(0, it.height / 2, it.width, it.height), rectF, mPaint)
}
复制代码

2.6 实现交互

2.6.1 上下翻转

逻辑主要在onTouchEvent方法中,通过中间卡片初始角度和当前的翻转角度判断是上翻还是下翻

写在最后

本次我的分享也接近尾声了,感谢你们在百忙中花上一下午来这里聆听我的宣讲,希望在接下来的日子,我们共同成长,一起进步!!!

最后放上一个大概的Android学习方向及思路(详细的内容太多了~),提供给大家:

对于程序员来说,要学习的知识内容、技术有太多太多,这里就先放上一部分,其他的内容有机会在后面的文章向大家呈现出来,不过我自己所有的学习资料都整理成了一个文档,一直在不断学习,希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!

Android架构师之路很漫长,一起共勉吧!

如果你觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言,一定会认真查询,修正不足,谢谢。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

vGou7Q-1714537135959)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 14
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值