开篇
几个月前,晚上梦到在研究翻页动画,次日上班居然想起了昨天晚上的梦。search了相关信息,大概要用到数学计算、canvas绘图等相关内容。对我来说手动绘图这方面的实践还是0,想想短时间内应该搞不定,便将此事记在了待做清单内。(公司电脑一个单子,自己Mac一个单子,增加行数的速度远比删除的速度快。)
十一长假前夕手头没什么事情,看着清单顶部的第一项(时间排序),便照着例文开始绘图。画到后面,想想就一张图片多无聊。算了!加点功能做个带翻页动画的阅读器吧!
preview1 preview2 preview3项目地址:GitHub
觉得好的话star一下
文章顺序:
- 翻页
- 内容
- 工具栏
翻页
主要是按照
Android自定义View——从零开始实现书籍翻页效果
的逻辑,自己手敲了一遍完成的翻页动画,并对代码进行了调整。
期间也学习了
各个击破搞明白PorterDuff.Mode
一起看画布Android Canvas
对代码理解和调整起到了很大帮助。
这里就不赘述以上文章的内容,直接说明调整后的代码逻辑。
自定义控件继承自View,通过重写
- onMeasure 来获取控件的宽高,取得纸张各点的初始值
- onDraw 用来绘制纸张、阴影、内容
- onTouchEvent 获取用户的手势事件,重计算各点并绘制,达到翻页的动效
- computeScroll 用到了Scroller,来执行手势事件后的翻页、恢复效果
初始化
因为视图是通过Canvas绘制出来的,因此控件初始化时要将
- Point 书页运动的关键点,用于完成翻页动画
- Paint 内容画笔
- Path A、B、C区域的路径,用于翻页时路径的裁剪
- Gradient 阴影
初始化,并打开setClickable(true),用以接收手势事件
测量
按View的生命周期,到了onMeasure
设置BookPageView的宽高后,便有了关键点的初始值。通过宽高值,初始化A、B两个不同区域的Bitmap。
绘制
具备了Point、Paint、Path、Gradient、Bitmap,便可以开始绘制Canvas。
将准备好的纸图片,绘制到Canvas当全局背景,用以填充裁剪所漏出的区域。
具体的绘制过程需要根据翻页的方向进行区分,因为翻页的方向不同,导致各关键点的计算公式、阴影位置不同。
触点
区域划分ACTION_DOWN时判断翻页的位置,预先加载前一页or后一页的内容到Bitmap上,并计算各点坐标。
正常状况下滑动不予翻页,避免突兀到翻页效果。
翻页结束,根据预设条件判断翻页还是恢复,并调用Scroller.startScroll。
内容
由于翻页相关的内容主体逻辑已经在引用中有了详细的解释,所以没必要用篇幅再去讲解一遍。
但内容相关的代码会详细介绍,涉及到字体大小所影响的页面显示、TXT内容的读取策略等
根据最少知识原则设计内部类ContentController,用以读取文件、缓存内容、获取当前内容。
根据TXT小说的大体估计,平均一行约500汉字,共占1000字节约1K。
每页约1~3行,由每行字数多少决定。
缓存策略为缓存50页:当前页的前10页、包括当前页的后40页。
获取指定页内容(待优化)
从缓存中取,若指定页为阀值,则更新缓存。
待优化点:
- 未预先缓存数据,现在显示正常是因为onMeasure的多次执行,导致initBitmapA多次执行,从而代替了预先缓存。如果内容不变的话initBitmapA不应该多次执行。
更新指定页前后的内容(待优化)
根据当前页计算,需缓存的前后页码。
待优化点:
- 重复内容并没有重用,(现缓存1~50,需缓存37~87,则37~50内容重复)。但限于读取的方式,暂没想到好的解决办法,见下。
获取指定范围的内容(重点)
初始数组,获取输入流
标记第一页,根据字体大小计算每页及每行可容纳的字数。
标准IO操作,读取每行内容,计算当前行长度+当前页内容长度,是否超过当页容量,超过则放到下一页。当这页已满,stringBuffer.toString()放入result数组,页数加一。
由于每次读取都是从第1页开始,若缓存30~80页,则先读取前30页的内容并抛弃,然后才存到数组缓存。
所以要判断
待优化点:
- 现读取的是raw中的文件,可优化选择文件功能
- 每次读文件都是从第一行开始读,读字节的话又会造成,前后页之间的文字乱码。(暂未想到解决办法)
- 当一页的第一行只有1个或很少的字数并换行,且接下来的段落每行字数都很满,则会造成最后一行绘出屏幕
工具栏
一个完整的书页,需要有工具栏来设置字体、字号、背景色等多种功能。这里只提供了工具栏,至于具体的工具待填充。
由于没有工具,所以
这里完全可以如上所说代码布局,我省个事!!
在onFinishInflate,获取对应控件。不必担心回调优先执行导致控件空指针,此处控件已初始化完成,并setText,在onMeasure和onLayout中也只会对控件进行拉伸显示
对控件进行测量,切记注意EXACTLY、AT_MOST与实际xml中填写项的对应。
这里布局子View,需要注意layout时,不要只在乎左上角的点,右下角同样重要,否则控件会被拉伸。
动画效果采用了Animator,属性动画,通过改变自定义属性,不停的layout达到效果
网友评论