炮兵镇楼
上一节我们实现了翻页的曲线效果,但是效果有点小瑕疵不知道大家发现没有:
如图,我们发现折叠区域怪怪的,并没有实现我们之前的“弯曲”效果,为什么呢?是计算错了么?其实不是的,我们之前测试的时候使用的将canvas填色,但是这里我们用到的是一张位图,虽然我们的Path是曲线、Region有曲线区域,但是我们的Bitmap是个规规矩矩的矩形啊,怎么弯曲~怎么办呢?说起扭曲,我们首先想到的是drawBitmapMesh方法,它是我们现在了解的也是唯一的一个能对图像进行扭曲的API,而使用drawBitmapMesh方法呢我们也可以有多种思路,最简单的就是最大化恒定细分值,将图像分割成一定的网格区域,然后判断离曲线起点和顶点最近的细分线获取该区域内的细分线交点按指定方向百分比递减移动起点和顶点的距离值即可,这种方法简单粗暴,但扭曲不是很精确,正确地说精确度取决于细分,细分也大越精确当然也越耗性能,而第二种方法呢是根据曲线的起点和顶点动态生成细分值,我们可以确保在起点和顶点处都有一条细分线,这样就可以很准确地计算扭曲范围,但是我们就需要动态地去不断计算细分值相当麻烦,用哪种呢?这里鉴于时间关系还是尝试用第一种去做,首先定义宽高的细分值:
- private static final int SUB_WIDTH = 19, SUB_HEIGHT = 19;// 细分值横竖各19个网格
- private Canvas mCanvasFoldCache;// 执行绘制离屏缓冲的Canvas
- private Bitmap mBitmapFoldCache;// 存储绘制离屏缓冲数据的Bitmap
- /*
- * 实例化Canvas
- */
- mCanvasFoldCache = new Canvas();
- /*
- * 生成缓冲位图并注入Canvas
- */
- mBitmapFoldCache = Bitmap.createBitmap(mViewWidth + 100, mViewHeight + 100, Bitmap.Config.ARGB_8888);
- mCanvasFoldCache.setBitmap(mBitmapFoldCache);
如上图透明红色的范围是我们mBitmapFoldCache的大小,但是底部和右侧的扭曲没有被包含进来,为了弥补这部分的损失我将mBitmapFoldCache的宽高各+100,当然你也可以计算出具体的值,这里只做演示。
而在绘制时,我们先将所有的数据绘制到mBitmapFoldCache上再将该Bitmap绘制到我们的canvas中:
- mCanvasFoldCache.drawBitmapMesh(mBitmaps.get(end - 1), SUB_WIDTH, SUB_HEIGHT, mVerts, 0, null, 0, null);
- canvas.drawBitmap(mBitmapFoldCache, 0, 0, null);
- mCanvasFoldCache.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
- mCanvasFoldCache.drawBitmapMesh(mBitmaps.get(end - 1), SUB_WIDTH, SUB_HEIGHT, mVerts, 0, null, 0, null);
- canvas.drawBitmap(mBitmapFoldCache, 0, 0, null);
- canvas.drawBitmapMesh(mBitmaps.get(end - 1), SUB_WIDTH, SUB_HEIGHT, mVerts, 0, null, 0, null);
- // 实例化数组并初始化默认数组数据
- mVerts = new float[(SUB_WIDTH + 1) * (SUB_HEIGHT + 1) * 2];
- if (sizeLong > mViewHeight) {
- // 省略大量代码……
- } else {
- // 省略巨量代码……
- /*
- * 生成折叠区域的扭曲坐标
- */
- int index = 0;
- for (int y = 0; y <= SUB_HEIGHT; y++) {
- float fy = mViewHeight * y / SUB_HEIGHT;
- for (int x = 0; x <= SUB_WIDTH; x++) {
- float fx = mViewWidth * x / SUB_WIDTH;
- mVerts[index * 2 + 0] = fx;
- mVerts[index * 2 + 1] = fy;
- index += 1;
- }
- }
- }
我们的方法其实很简单,只需要把从短边长度减短边长度乘以1/4的位置开始到短边长度位置的点按递增向下拽即可对吧:
如上图所示的两个蓝点分别代表短边长度减短边长度乘以1/4的位置和短边长度位置,因为我们的网格是不变的,但是位置在不断改变,我们应当获取离当前位置最近的网格点,比如上图中的两个蓝点此时我们应该获取到网格中的对应位置是:
如图中绿色的蓝点,考虑到更好的容差值,我们令起点往后挪一个点而终点往前挪一个点,最终我们的取舍点如下:
同样,我们右侧的也一样:
那在代码中的实现也很简单:
- // 计算底部扭曲的起始细分下标
- mSubWidthStart = Math.round((btmX / mSubMinWidth)) - 1;
- mSubWidthEnd = Math.round(((btmX + CURVATURE * sizeShort) / mSubMinWidth)) + 1;
- // 计算右侧扭曲的起始细分下标
- mSubHeightStart = (int) (leftY / mSubMinHeight) - 1;
- mSubHeightEnd = (int) (leftY + CURVATURE * sizeLong / mSubMinHeight) + 1;
每一个点的偏移值相对于上一个点来说是倍增的,倍增多少呢?是基于最大的偏移值来说的,这里为了简化一定的问题,我就不去计算了,而是给定一个固定的起始值和倍增率:
- // 长边偏移
- float offsetLong = CURVATURE / 2F * sizeLong;
- // 长边偏移倍增
- float mulOffsetLong = 1.0F;
- // 短边偏移
- float offsetShort = CURVATURE / 2F * sizeShort;
- // 短边偏移倍增
- float mulOffsetShort = 1.0F;
- // 计算底部扭曲的起始细分下标
- mSubWidthStart = Math.round((btmX / mSubMinWidth)) - 1;
- mSubWidthEnd = Math.round(((btmX + CURVATURE * sizeShort) / mSubMinWidth)) + 1;
- // 计算右侧扭曲的起始细分下标
- mSubHeightStart = (int) (leftY / mSubMinHeight) - 1;
- mSubHeightEnd = (int) (leftY + CURVATURE * sizeLong / mSubMinHeight) + 1;
- /*
- * 生成折叠区域的扭曲坐标
- */
- int index = 0;
- // 长边偏移
- float offsetLong = CURVATURE / 2F * sizeLong;
- // 长边偏移倍增
- float mulOffsetLong = 1.0F;
- // 短边偏移
- float offsetShort = CURVATURE / 2F * sizeShort;
- // 短边偏移倍增
- float mulOffsetShort = 1.0F;
- for (int y = 0; y <= SUB_HEIGHT; y++) {
- float fy = mViewHeight * y / SUB_HEIGHT;
- for (int x = 0; x <= SUB_WIDTH; x++) {
- float fx = mViewWidth * x / SUB_WIDTH;
- /*
- * 右侧扭曲
- */
- if (x == SUB_WIDTH) {
- if (y >= mSubHeightStart && y <= mSubHeightEnd) {
- fx = mViewWidth * x / SUB_WIDTH + offsetLong * mulOffsetLong;
- mulOffsetLong = mulOffsetLong / 1.5F;
- }
- }
- /*
- * 底部扭曲
- */
- if (y == SUB_HEIGHT) {
- if (x >= mSubWidthStart && x <= mSubWidthEnd) {
- fy = mViewHeight * y / SUB_HEIGHT + offsetShort * mulOffsetShort;
- mulOffsetShort = mulOffsetShort / 1.5F;
- }
- }
- mVerts[index * 2 + 0] = fx;
- mVerts[index * 2 + 1] = fy;
- index += 1;
- }
- }
上面的图因为上传大小的限制我压缩过可能大家看不清楚,如果大家DL我想项目运行可以看到在我们翻动的过程中扭曲的部分会有一点小跳动,原因很简单,我们的扭曲只针对了底部最后的一行点y == SUB_HEIGHT和右侧最右的一列点x == SUB_WIDTH,而事实上扭曲是个拉扯联动的效果,扭曲不仅仅会影响最后一行/列同时也会影响倒数第二、三、四行等,只不过这个影响效力是递减的,这部分就留给大家自己去做了,原理我讲的很清楚了。
这一节到此为止,下一节我们将完善最终效果结束本例所有的Study~
本文转自 http://blog.csdn.net/aigestudio/article/details/42741781