本文正在参与「金石计划」

前言

上上星期的时分,和大伙分享了双仿真页,最近又完善了一些,作用愈加丝滑。

由于自己是一个拍摄爱好者,所以简略做了一个相册的Demo,话不多说,看最终作用:

这么炫酷的双仿真,原理了解一下?

图片中的作用来自三星的网页模拟器。

一、基础知识

这一块儿的基础知识已经在《如何写一个炫酷的大屏仿真页》和咱们说过:

  1. 贝塞尔曲线
  2. Canvas相关的Api
  3. Matrix

如果你对贝塞尔曲线仍是不了解,能够阅览我之前的文章:《从阅览仿真页看贝塞尔曲线》

简略的把握了这些,咱们就能够很方便的制作出仿真页。

二、架构 & 全体流程

看一下全体的结构:

这么炫酷的双仿真,原理了解一下?

全体的结构仍是比较简略的:

  • 外层的 DoubleFlipView 担任处理接触事件
  • 中心的 DoubleRealFlipView 担任动画和制作整个流程的把握
  • BaseDirectDrawAction 和下面的 LeftxxxRightxxx 都是抽象类,制作的详细施行方是它们,依据不同的方向又能够分为下面的四种,分别是左下页、左上页、右上页和右下页滑动。

简略的了解一下我的整个制作流程,从我的代码中也能够看出:

  1. 制作非翻转页
  2. 制作翻转页的基本内容
  3. 制作两页之间的暗影
  4. 制作翻转页下一层页显露的内容和暗影
  5. 制作翻转页的两侧暗影
  6. 翻转背部的内容

简略的用图片标示一下,进程对应图中的数字:

这么炫酷的双仿真,原理了解一下?

三、View转Bitmap

xml 文件转 Bitmap,看代码:

private fun createBitmapTwo(index: Int): Bitmap {
    val root: View = LayoutInflater.from(this).inflate(R.layout.view_album_style_two, null, false)
    //... 省略
    root.measure(measureWidth, measureHeight)
    root.layout(0, 0, root.measuredWidth, root.measuredHeight)
    val bitmap = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    canvas.drawColor(Color.WHITE)
    root.draw(canvas)
    return bitmap
}

需求先对 View 进行 measure 和 layout 的进程,最终运用 View#draw 办法将 View 的内容制作在 Canvas 上面,最终返回咱们的 Bitmap 即可。

四、翻页机制

整个翻页的中心机制是这样的:

  1. 左右滑动决议翻页的进度
  2. 上下滑动决议翻页的视点

不太清楚?没关系,看图!

左右滑动:

这么炫酷的双仿真,原理了解一下?

纵向滑动:

这么炫酷的双仿真,原理了解一下?

纵向滑动的时分,实质上便是环绕滑动点坐圆,求到这个极值就能够,极值怎样算:

这么炫酷的双仿真,原理了解一下?

仔细分析一下:

C点 -> A点 直线滚动
DC = DA
c点已知,D点已知,B点X坐标已知,B点很轻松就能够求出来。

写双仿真的难点,很大一部分就来自于将这些动作转化成实际的数学公式,头快秃了~

五、制作基本内容

总算到咱们的制作环节了,制作的中心便是运用贝塞尔曲线构建Path,一切区域的选择都是基于这个Path。

1. 创立基础的Path

Path路径: A – E – EG曲线 – G – B – H – HF曲线 – F – A

关于 Path 中的每个点,我在之前的文章都讲过,咱们能够检查之前的文章:《从阅览仿真页看贝塞尔曲线》

这么炫酷的双仿真,原理了解一下?

尽管图片是单仿真,但是单仿真页和双仿真在贝塞尔这块的原理其实都共同。

2. 制作非翻转页

仍是用的上图:

这么炫酷的双仿真,原理了解一下?

进程1对应的页面就对错翻转页,化繁为简,整个制作区域不做处理,制作整个Bitmap。

3. 制作翻转页基础部分

其实便是制作进程2,对应图片中的部分蓝色三角形部分(标示的有点粗糙,见谅)。

进程便是:将左页原来的矩形制作区域,扣除之前的贝塞尔曲线,得到图中进程2对应的蓝色三角形。

选取一点代码:

override fun drawFlipPageContent(
    canvas: Canvas,
    reUsePath: Path,
    flipPath: Path,
    r: Int
) {
    canvas.save()
    reUsePath.reset()
    reUsePath.moveTo(mLeftPageRBPoint.x - r, mLeftPageLTPoint.y)
    reUsePath.arcTo( mLeftPageRBPoint.x - 2 * r, mLeftPageLTPoint.y, mLeftPageRBPoint.x, mLeftPageLTPoint.y + 2 * r, -90f, 90f, false)
    reUsePath.lineTo(mLeftPageRBPoint.x, mLeftPageRBPoint.y - r)
    reUsePath.arcTo(mLeftPageRBPoint.x - 2 * r, mLeftPageRBPoint.y - 2 * r,mLeftPageRBPoint.x, mLeftPageRBPoint.y, 0f, 90f, false)
    reUsePath.lineTo(mLeftPageLTPoint.x, mLeftPageRBPoint.y)
    reUsePath.lineTo(mLeftPageLTPoint.x, mLeftPageLTPoint.y)
    reUsePath.close()
    canvas.clipPath(reUsePath)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        canvas.clipOutPath(flipPath)
    } else {
        canvas.clipPath(flipPath, Region.Op.DIFFERENCE)
    }
    mLeftTopBitmap?.let {
        canvas.drawBitmap(it, mLeftPageLTPoint.x, mLeftPageLTPoint.y, null)
    }
    canvas.restore()
}

中心区域的静态暗影没什么好说的,直接跳过。

4. 制作底层显露的内容

底层内容对应单仿真页中的 KLB 中的三角形,咱们能够:

  1. 先在 Canvas 抠出对应的贝塞尔区域
  2. 进一步抠出 KLB 三角形对应的区域

然后将下一层内容独自制作在这一块儿区域,依然是 Canvas 结合 Path,制作 Bitmap,没什么好说的。

5. 制作页边暗影

制作页边的暗影也不是件很简单的事,首先,它是分为两个图层画的,上下的暗影为一个图层,左右的暗影为一个图层,所以你能够看见,Canvassaverestore 办法我调用了两遍。

另外一个麻烦的事便是视点的核算,由于有四个方向的翻页,每种方向翻页的时分视点核算都有点不同,所以你去视点核算的时分或许会有点头疼~

6. 制作背部内容

总算来到最终一步了。

抠涂层的过程也是一样:

  1. 先抠贝塞尔Path
  2. 去除 KLB 三角形对应的区域

之后便是制作内容,运用 Matrix

之前看单仿真,看代码先用了对称,然后旋转 + 平移,在写双仿真的时分,惊奇的发现,只用旋转和平移的方法就够了。

看图,我特意将第三张图和第五张图换成一样的:

这么炫酷的双仿真,原理了解一下?

我相信你能够很轻松的了解。完了,运用 Matrix 能够轻松的完结这些东西。

总结

总的来说,双仿真看着不是特别难,但是你去看代码的时分,或许不是一件特别简单的事,特别是运用各种数学公式的时分。

限制于时间的关系,后半部分文章没有贴更多的代码,主要我觉得贴代码也不太利于去了解,感兴趣的同学能够自己去看代码,周六一天时间都花在收拾 Demo 和写文章了。

Demo地址:github.com/mCyp/Double…

这段时间先学习 Opengles 了,想写个双仿真试试。