Android 时钟翻页效果

背景

今天逛掘金,发现了一个有意思的web view效果,想着Android能不能实现一下捏。

原文链接:juejin.cn/post/724435...

具体实现分析请看上文原文链接,那我们开始吧!

容器

kt 复制代码
val space = 10f //上下半间隔
val bgBorderR = 10f //背景圆角
//上半部分
val upperHalfBottom = height.toFloat() / 2 - space / 2
canvas.drawRoundRect(
    0f,
    0f,
    width.toFloat(),
    upperHalfBottom,
    bgBorderR,
    bgBorderR,
    bgPaint
)
//下半部分
val lowerHalfTop = height.toFloat() / 2 + space / 2
canvas.drawRoundRect(
    0f,
    lowerHalfTop,
    width.toFloat(),
    height.toFloat(),
    bgBorderR,
    bgBorderR,
    bgPaint
)

绘制数字

我们首先居中绘制数字4

kt 复制代码
val number4 = "4"
textPaint.getTextBounds(number4, 0, number4.length, textBounds)
//居中显示
val x = (width - textBounds.width()) / 2f - textBounds.left
val y = (height + textBounds.height()) / 2f - textBounds.bottom
canvas.drawText(number4, x, y, textPaint)

接下来我们将数字切分为上下两部分,分别绘制。

less 复制代码
val number4 = "4"
textPaint.getTextBounds(number4, 0, number4.length, textBounds)
val x = (width - textBounds.width()) / 2f - textBounds.left
val y = (height + textBounds.height()) / 2f - textBounds.bottom
// 上半部分裁剪
canvas.save()
canvas.clipRect(
    0f,
    0f,
    width.toFloat(),
    upperHalfBottom
)
canvas.drawText(number4, x, y, textPaint)
canvas.restore()
// 下半部分裁剪
canvas.save()
canvas.clipRect(
    0f,
    lowerHalfTop,
    width.toFloat(),
    height.toFloat()
)
canvas.drawText(number4, x, y, textPaint)

翻转卡片

如何实现让其旋转呢? 而且还得是3d的效果了。我们选择Camera来实现。 我们先让数字'4'旋转起来。

准备工作,通过属性动画来改变旋转的角度。

kt 复制代码
private var degree = 0f //翻转角度
private val camera = Camera()
private var flipping = false //是否处于翻转状态
...
//动画
val animator = ValueAnimator.ofFloat(0f, 360f)
animator.addUpdateListener { animation ->
    val animatedValue = animation.animatedValue as Float
    setDegree(animatedValue)
}
animator.doOnStart {
    flipping = true 
}
animator.doOnEnd {
    flipping = false
}
animator.duration = 1000
animator.interpolator = LinearInterpolator()
animator.start()
...

private fun setDegree(degree: Float) {
    this.degree = degree
    invalidate()
}

让数字'4'旋转起来:

kt 复制代码
  override fun onDraw(canvas: Canvas) {
      super.onDraw(canvas)
      // 居中绘制数字4
      val number4 = "4"
      textPaint.getTextBounds(number4, 0, number4.length, textBounds)
      val x = (width - textBounds.width()) / 2f - textBounds.left
      val y = (height + textBounds.height()) / 2f - textBounds.bottom

      if (!flipping) {
          canvas.drawText(number4, x, y, textPaint)
      } else {
          camera.save()
          canvas.translate(width / 2f, height / 2f)
          camera.rotateX(-degree)
          camera.applyToCanvas(canvas)
          canvas.translate(-width / 2f, -height / 2f)
          camera.restore()
          canvas.drawText(number4, x, y, textPaint)
      }
  }

我们再来看一边效果图: 我们希望将卡片旋转180度,并且0度-90度由上半部分完成,90度-180度由下半部分完成。

我们调整一下代码,先处理一下上半部分:

kt 复制代码
...
val animator = ValueAnimator.ofFloat(0f, 180f)
...
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    val space = 10f //上下半间隔
    //上半部分
    val upperHalfBottom = height.toFloat() / 2 - space / 2
    ...
    // 居中绘制数字4
    val number4 = "4"
    textPaint.getTextBounds(number4, 0, number4.length, textBounds)
    val x = (width - textBounds.width()) / 2f - textBounds.left
    val y = (height + textBounds.height()) / 2f - textBounds.bottom
    
    if (!flipping) {
        //上半部分裁剪
        canvas.save()
        canvas.clipRect(
            0f,
            0f,
            width.toFloat(),
            upperHalfBottom
        )
        canvas.drawText(number4, x, y, textPaint)
        canvas.restore()
    } else {
        if (degree < 90) {
            //上半部分裁剪
            canvas.save()
            canvas.clipRect(
                0f,
                0f,
                width.toFloat(),
                upperHalfBottom
            )
            camera.save()
            canvas.translate(width / 2f, height / 2f)
            camera.rotateX(-degree)
            camera.applyToCanvas(canvas)
            canvas.translate(-width / 2f, -height / 2f)
            camera.restore()
            canvas.drawText(number4, x, y, textPaint)
            canvas.restore()
        }
    }
}

效果如下:

接下来我们再来看一下下半部分:

kt 复制代码
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    val space = 10f //上下半间隔
    //下半部分
    val lowerHalfTop = height.toFloat() / 2 + space / 2

    // 居中绘制数字4
    val number4 = "4"
    textPaint.getTextBounds(number4, 0, number4.length, textBounds)
    val x = (width - textBounds.width()) / 2f - textBounds.left
    val y = (height + textBounds.height()) / 2f - textBounds.bottom

    if (!flipping) {
        // 下半部分裁剪
        canvas.save()
        canvas.clipRect(
            0f,
            lowerHalfTop,
            width.toFloat(),
            height.toFloat()
        )
        canvas.drawText(number4, x, y, textPaint)
        canvas.restore()
    } else {
        if (degree > 90) {
            canvas.save()
            canvas.clipRect(
                0f,
                lowerHalfTop,
                width.toFloat(),
                height.toFloat()
            )
            camera.save()
            canvas.translate(width / 2f, height / 2f)
            val bottomDegree = 180 - degree
            camera.rotateX(bottomDegree)
            camera.applyToCanvas(canvas)
            canvas.translate(-width / 2f, -height / 2f)
            camera.restore()
            canvas.drawText(number4, x, y, textPaint)
            canvas.restore()
        }
    }
}

那我们将上下部分结合起来,效果如下:

数字变化

好!我们完成了翻转部分,现在需要在翻转的过程中将数字改变:

我们还是举例说明:数字由'4'变为'5'的情况。我们思考个问题,什么时候需要改变数字?

上半部分在翻转开始的时候,上半部分底部显示的数字就应该由'4'变为'5',但是旋转的部分还是应该为'4', 下半部分开始旋转的时候底部显示的数字还是应该为'4',而旋转的部分该为'5'。

kt 复制代码
canvas.save()
canvas.clipRect(
    0f,
    0f,
    width.toFloat(),
    upperHalfBottom
)
canvas.drawText(number5, x, y, textPaint)
canvas.restore()
// 下半部分裁剪
canvas.save()
canvas.clipRect(
    0f,
    lowerHalfTop,
    width.toFloat(),
    height.toFloat()
)
canvas.drawText(number4, x, y, textPaint)
canvas.restore()
//=====⬆️=====上述的代码显示的上下底部显示的内容,即上半部分地步显示5,下半部分显示4
if (degree < 90) {
    //上半部分裁剪
    canvas.save()
    canvas.clipRect(
        0f,
        0f,
        width.toFloat(),
        upperHalfBottom
    )
    camera.save()
    canvas.translate(width / 2f, height / 2f)
    camera.rotateX(-degree)
    camera.applyToCanvas(canvas)
    canvas.translate(-width / 2f, -height / 2f)
    camera.restore()
    canvas.drawText(number4, x, y, textPaint)
    canvas.restore()
    //=====⬆️=====上述的代码表示上半部分旋转显示的内容,即数字4
} else {
    canvas.save()
    canvas.clipRect(
        0f,
        lowerHalfTop,
        width.toFloat(),
        height.toFloat()
    )
    camera.save()
    canvas.translate(width / 2f, height / 2f)
    val bottomDegree = 180 - degree
    camera.rotateX(bottomDegree)
    camera.applyToCanvas(canvas)
    canvas.translate(-width / 2f, -height / 2f)
    camera.restore()
    canvas.drawText(number5, x, y, textPaint)
    canvas.restore()
    //=====⬆️=====上述的代码表示下半部分旋转显示的内容,即数字5
}

效果图如下:大伙可以在去理一下上面数字的变化的逻辑。

最后我们加上背景再看一下效果:

小结

上述代码仅仅提供个思路,仅为测试code,正式代码可不能这么写哦 >..<

相关推荐
CYRUS_STUDIO4 小时前
Frida 检测与对抗实战:进程、maps、线程、符号全特征清除
android·逆向
csj505 小时前
安卓基础之《(28)—Service组件》
android
lhbian7 小时前
PHP、C++和C语言对比:哪个更适合你?
android·数据库·spring boot·mysql·kafka
catoop8 小时前
Android 最佳实践、分层架构与全流程解析(2025)
android
ZHANG13HAO9 小时前
Android 13 特权应用(Android Studio 开发)调用 AOSP 隐藏 API 完整教程
android·ide·android studio
田梓燊9 小时前
leetcode 142
android·java·leetcode
angerdream10 小时前
Android手把手编写儿童手机远程监控App之JAVA基础
android
菠萝地亚狂想曲10 小时前
Zephyr_01, environment
android·java·javascript
sTone8737510 小时前
跨端框架通信机制全解析:从 URL Schema 到 JSI 到 Platform Channel
android·前端
sTone8737510 小时前
Java 注解完全指南:从 "这是什么" 到 "自己写一个"
android·前端