Android App开发进阶与项目实战
上QQ阅读APP看书,第一时间看更新

4.2.3 实现卷曲翻书动画

上一小节介绍的平滑翻书固然实现了层叠翻页,可是该方式依旧无法模拟现实生活的翻书动作。现实当中每翻过一页,这页纸都会卷起来,再绕着装订线往前翻,并非平直地滑过去。就像图4-16所示的那样,手指捏住书页的右下角,然后轻轻地往左上方掀。

仔细观察图4-16,可发现翻书的效果映射到平面上可以划分为三块区域,如图4-17所示。其中,A区域为正在翻的当前页,B区域为当前页的背面,C区域为露出来的下一页。关键在于如何确定三块区域之间的界线,特别是部分界线还是曲线,因而加大了勾勒线条的难度。

图4-16 卷曲翻页的界面效果

图4-17 翻书界面的三块区域

鉴于贝塞尔曲线的柔韧特性,可将其应用于翻书时的卷曲线条,为此需要把图4-17所示的区域界线划分为直线与曲线,其中直线通过首尾两个端点连接而成,曲线采取贝塞尔曲线的公式来描绘。单凭肉眼观察,先标出相关的划分点,如图4-18所示。

由图4-18可见,三块区域的界线从左往右依次描述如下:

(1)CDB三点组成一条曲线线段,其中D点位于书页背面的边缘。

(2)BA两点组成一条直线线段,其中A点原本是当前页右下角的端点。

(3)AK两点组成一条直线线段。

(4)KIJ三点组成一条曲线线段,其中I点位于书页背面的边缘。

(5)DI两点组成一条直线线段。

如此看来,区域界线总共分成两条曲线线段再加三条直线线段。同时E点像是贝塞尔曲线CDB的控制点,H点像是贝塞尔曲线KIJ的控制点。那么这些坐标点的位置又是怎样计算得到的呢?

首先能确定的是F点,该点固定位于书页的右下角;其次是A点,手指在触摸翻书的时候,指尖挪到哪里,A点就跟到哪里。基于A点和F点的坐标位置,再来计算剩余坐标点的位置。为方便讲解,给出标记相关连线的画面效果,如图4-19所示。

图4-18 三块区域的分界端点

图4-19 各点坐标的计算连线

接着继续介绍其余点的计算方法:

(1)连接AF两点,找到线段AF的中点,该点取名为G。

(2)过G点做线段AF的垂线,该垂线分别与书页的下边缘与右边缘相交,其中垂线与书页下边缘的交点为E,垂线与书页右边缘的交点为H。

(3)把线段EF向左边延长二分之一至C点,也就是线段CE的长度为线段EF长度的一半。

(4)把线段HF向上方延长二分之一至J点,也就是线段JH的长度为线段HF长度的一半。

(5)依次连接线段AE、AH、CJ,注意线段AE和线段CJ相交于B点,线段AH和线段CJ相交于K点。

(6)以C点作为起点、B点作为终点、E点作为控制点,计算贝塞尔曲线的中间位置(在D点);以J点为起点、K点为终点、H点为控制点,计算贝塞尔曲线的中间位置(在I点)。

至此,除了A、F两点,其他坐标点都通过各种连线确定了方位。把上述的坐标算法转换成程序实现,具体的示例代码如下:

(完整代码见ebook\src\main\java\com\example\ebook\widget\CurveView.java)

算出了区域界线的重要划分点,接下来描绘当前页、书页背面、下一页就好办多了。其中,当前页的翻卷边缘由CDBAKIJ诸点的曲线和直线线段连接而成,书页背面的边缘则由ABDIK之间的直线或曲线线段界定,剩下的区域部分便是下一页了。唯一的难点在于:矩形的书页视图先去掉当前页部分,再去掉背面页部分,剩下的才是下一页,但下一页的边缘明显不规则,该如何绘制下一页的内容呢?

其实当前页的边缘路径可由前面计算的各点坐标连接得到,背面页的边缘路径同理可得,既然这两个页面的边缘路径都能算出,那么把整张画布的路径依次减去二者的路径,岂不是就得到下一页的边缘路径了呢?Android正好支持路径区域的加减,此时用到了路径工具的op方法,该方法的第一个参数为参与计算的目标路径,第二个参数表示计算规则(比如加法还是减法,具体取值说明见表4-1)。

表4-1 路径计算规则的取值说明

从表4-1可知,翻书效果需要的路径规则正是Path.Op.DIFFERENCE,那么下一页画面的绘制便水到渠成了,绘制过程的具体示例代码如下:

(完整代码见ebook\src\main\java\com\example\ebook\widget\CurveView.java)

至此,翻书效果还剩下两个功能点有待实现,说明如下:

(1)在手指触摸的过程中,要实时计算各坐标点的位置,并调整书页的画面绘制。

(2)手指松开之后,要判断接下来是往前翻页,还是往后缩回去,并在前翻与后缩的过程中展示翻书动画。

关于以上两个功能点,第二点可借助滚动器(Scroller)来实现,第一点则需重写onTouchEvent方法,分别处理手指按下、移动、松开三种情况的视图变迁。下面是实现第一点功能的示例代码的片段:

上面的代码调用了rollFront和rollBack两个方法,其中rollFront表示滚动到上一页,rollBack表示滚回当前页。同时它们在方法末尾都得调用滚动器对象的startScroll,命令滚动器按照规定完成后续的自动滚动行为。这个自动滚动正是前述的第二点功能要求,下面是实现该点功能的示例代码的片段:

完成所有的翻书功能点之后,在布局文件中添加CurveView节点,并在对应的活动页面设置该视图的布局参数及其图片列表。运行并测试该App,可分别观察两种情况下的翻书效果:第一种情况尚未翻过半页(见图4-20),此时松开手指发现书页往右缩了回来,如图4-21所示;第二种情况已经翻过半页(见图4-22),此时松开手指发现书页往左翻了过去,如图4-23所示。

图4-20 尚未翻过半页的画面

图4-21 书页回缩过程的画面

图4-22 已经翻过半页的画面

图4-23 书页前翻过程的画面