Android翻页效果原理实现之模拟扭曲
来源:程序员人生 发布时间:2015-03-02 07:59:38 阅读次数:2880次
上1节我们实现了翻页的曲线效果,但是效果有点小瑕疵不知道大家发现没有:
如图,我们发现折叠区域怪怪的,并没有实现我们之前的“曲折”效果,为何呢?是计算错了么?其实不是的,我们之前测试的时候使用的将canvas填色,但是这里我们用到的是1张位图,虽然我们的Path是曲线、Region有曲线区域,但是我们的Bitmap是个规规矩矩的矩形啊,怎样曲折~怎样办呢?说起扭曲,我们首先想到的是drawBitmapMesh方法,它是我们现在了解的也是唯1的1个能对图象进行扭曲的API,而使用drawBitmapMesh方法呢我们也能够有多种思路,最简单的就是最大化恒定细分值,将图象分割成1定的网格区域,然后判断离曲线出发点和顶点最近的细分线获得该区域内的细分线交点按指定方向百分比递减移动出发点和顶点的距离值便可,这类方法简单粗鲁,但扭曲不是很精确,正确地说精确度取决于细分,细分也大越精确固然也越耗性能,而第2种方法呢是根据曲线的出发点和顶点动态生成细分值,我们可以确保在出发点和顶点处都有1条细分线,这样就能够很准确地计算扭曲范围,但是我们就需要动态地去不断计算细分值相当麻烦,用哪一种呢?这里鉴于时间关系还是尝试用第1种去做,首先定义宽高的细分值:
private static final int SUB_WIDTH = 19, SUB_HEIGHT = 19;// 细分值横竖各19个网格
19个网格将控件分割为20x20的网格细分线条区域,尔后我们就不需要使用drawBitmap绘制折叠区域了而是改用drawBitmapMesh。之前在讲1/6的时候有盆友屡次小窗过我离屏缓冲是个甚么意思需要注意甚么,这里我权当演示,在绘制扭曲图象的时候使用1个单独的Bitmap并将其装载进1个额外的Canvas中:
private Canvas mCanvasFoldCache;// 履行绘制离屏缓冲的Canvas private Bitmap mBitmapFoldCache;// 存储绘制离屏缓冲数据的Bitmap
在构造方法中我们实例化mCanvasFoldCache:
/* * 实例化Canvas */ mCanvasFoldCache = new Canvas();
在onSizeChanged中我们生成mBitmapFoldCache:
/* * 生成缓冲位图并注入Canvas */ mBitmapFoldCache = Bitmap.createBitmap(mViewWidth + 100, mViewHeight + 100, Bitmap.Config.ARGB_8888); mCanvasFoldCache.setBitmap(mBitmapFoldCache);
这里+100的目的是让Bitmap有过剩的空间绘制扭曲的那部份图象,我们之前说过Canvas的大小实际取决于内部装载的Bitmap,如果这里我们不+100,那末mBitmapFoldCache的大小就恰好和我们的控件1样大,但是我们实现扭曲的那1部份图象是超越该范围外的:
如上图透明红色的范围是我们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);
这里要注意的是,我们的mBitmapFoldCache在onSizeChanged方法中生成,每次我们绘制的时候都不再重新生成,也就是说,每次绘制其实都是叠加在上1次的绘制数据上,那末这就会给我们带来1个问题,虽然显示结果有可能不会出错但是每次绘制都要不断计算前面的像素次数1多一定会大大影响性能,这时候候我们斟酌在绘制每次结果前清空掉mBitmapFoldCache中的内容:
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);
题外话到此为止,实际上我们不需要缓冲绘制,直接使用drawBitmapMesh便可:
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步操作前我们先来分析1下如何进行扭曲呢,当我们在折叠区域以drawBitmapMesh的方式绘制Bitmap时这时候候的图象实质上是被网格分割了的:
我们的方法其实很简单,只需要把从短边长度减短边长度乘以1/4的位置开始到短边长度位置的点按递增向下拽便可对吧:
如上图所示的两个蓝点分别代表短边长度减短边长度乘以1/4的位置和短边长度位置,由于我们的网格是不变的,但是位置在不断改变,我们应当获得离当前位置最近的网格点,比如上图中的两个蓝点此时我们应当获得到网格中的对应位置是:
如图中绿色的蓝点,斟酌到更好的容差值,我们令出发点往后挪1个点而终点往前挪1个点,终究我们的取舍点以下:
一样,我们右边的也1样:
那在代码中的实现也很简单:
// 计算底部扭曲的起始细分下标 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;
我们只需要将mSubWidthStart到mSubWidthEnd之间的点往下“拽”,mSubHeightStart到mSubHeightEnd的点往右“拽”便可实现初步的“扭曲”效果对吧,但是这个拽是有讲求的,首先,拽的距离是倍增的,如图:
每个点的偏移值相对上1个点来讲是倍增的,倍增多少呢?是基于最大的偏移值来讲的,这里为了简化1定的问题,我就不去计算了,而是给定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我想项目运行可以看到在我们翻动的进程中扭曲的部份会有1点小跳动,缘由很简单,我们的扭曲只针对了底部最后的1行点y == SUB_HEIGHT和右边最右的1列点x == SUB_WIDTH,而事实上扭曲是个拉扯联动的效果,扭曲不单单会影响最后1行/列同时也会影响倒数第2、3、4行等,只不过这个影响效率是递减的,这部份就留给大家自己去做了,原理我讲的很清楚了。
这1节到此为止,下1节我们将完善终究效果结束本例所有的Study~
源码下载:传送门
生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠