如何实现Android模拟扭曲翻页的效果教程

    作者:课课家教育更新于: 2016-05-17 15:49:01

      在我们使用智能手机的时候,有没有注意到桌面翻页这个效果呢?一定有吧,因为我们总喜欢各种方式的方式来显示自己的个性,那么本文就介绍一下安卓上如何利用翻页原理来实现模拟扭曲效果。

      在介绍之前,我们先介绍一下翻页的基本内容,翻页是查看分页文件的上一个页面、下一个页面或任意存在的非当前页面,也可以将实体书或者移动web窗体中的内容进行改变,这样就能观看不同内容,而在互联网上运用时该机制还呈现可用于浏览到其他页的用户界面元素。

      在之前我们有实现翻页的曲线效果,但有没有发现有点怪怪的,看下面图:

    使用Android翻页效果原理如何来实现模拟扭曲效果教程_Android翻页_安卓_android系统_课课家

      看上图的折叠区域,我们发现折叠区域怪怪的,图上并没有实现我们之前的“弯曲”效果,这是为什么呢?在我们之前测试的时候是使用的将canvas填色,但是在这里我们使用到的是一张位图,虽然我们的Path是曲线、Region有曲线区域,但是我们的Bitmap是个规规矩矩的矩形啊,那怎么弯曲呢?说起扭曲,我们首先要想到的是drawBitmapMesh方法,它是我们现在了解的也是唯一的一个能对图像进行扭曲的API,而使用drawBitmapMesh方法呢我们也可以有多种思路,最简单的就是最大化恒定细分值,将图像分割成一定的网格区域,然后判断离曲线起点和顶点最近的细分线获取该区域内的细分线交点按指定方向百分比递减移动起点和顶点的距离值即可,这种方法简单粗暴,但扭曲不是很精确,正确地说精确度取决于细分,细分也大越精确当然也越耗性能,而第二种方法呢是根据曲线的起点和顶点动态生成细分值,我们可以确保在起点和顶点处都有一条细分线,这样就可以很准确地计算扭曲范围,但是我们就需要动态地去不断计算细分值相当麻烦,用哪种呢?这里鉴于时间关系还是尝试用第一种去做,首先定义宽高的细分值:

    private static final int SUB_WIDTH = 19, SUB_HEIGHT = 19;// 细分值横竖各19个网格

      将上面19个网格将控件分割成为20×20的网格细分线条区域,然后我们就不需要再使用drawBitmap绘制折叠区域了,而是改用drawBitmapMesh。在之前讲1/6的时候有些朋友多次提问离屏缓冲是个什么意思?这个需要注意什么?在这里我权当演示,在绘制扭曲图像的时候使用一个单独的Bitmap并将其装载进一个额外的Canvas中:

    private Canvas mCanvasFoldCache;// 执行绘制离屏缓冲的Canvas
    private Bitmap mBitmapFoldCache;// 存储绘制离屏缓冲数据的Bitmap
    

      在构造方法中,我们实例化mCanvasFoldCache:

    mCanvasFoldCache = new Canvas();//实例化Canvas
    

      在onSizeChanged中,我们生成mBitmapFoldCache:

    /*
     * 生成缓冲位图并注入Canvas
     */
    mBitmapFoldCache = Bitmap.createBitmap(mViewWidth + 100, mViewHeight + 100,Bitmap.Config.ARGB_8888);
    mCanvasFoldCache.setBitmap(mBitmapFoldCache);
    

      在这里+100的实际目的是让Bitmap可以有多余的空间绘制扭曲的那部分图像,之前我们说过Canvas的大小实际是取决于内部装载的Bitmap,如果在这里我们不+100,那么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);
    

      这里我们需要注意的是,我们的mBitmapFoldCache在onSizeChanged方法中生成,每次我们绘制的时候都不再重新生成,那么也就是说,我们每次绘制的数据其实都是叠加在上一次的绘制数据上,这么说这个就会给我们带来一个问题,虽然显示结果有可能不会出错但是每次绘制都要不断计算前面的像素次数一多必定会大大影响性能,这时候我们考虑在绘制每一次结果前清空掉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;
    		}
    	}
    }
    

      虽然在上面我们生成了坐标数组,但是并没有扭曲图像,那么我们在进行下一步操作前,我们先来分析一下这个该如何进行扭曲呢,当我们在折叠区域以drawBitmapMesh的方式绘制Bitmap时,这时候的图像实质上是被网格分割了的

      如下面图展示:

    折叠区域

      其实我们的方法很简单,只需要把从短边长度减短边长度乘以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;
    

      我们只需要将mSubWidthStart到mSubWidthEnd之间的点往下“拽”,mSubHeightStart到mSubHeightEnd的点往右“拽”即可实现初步的“扭曲”效果对吧,但是这个拽是有讲究的,首先,拽的距离是倍增的,如图:

    初步扭曲效果

      每一个点的偏移值相对于上一个点来说是倍增的,那么能倍增多少呢?这是基于最大的偏移值来说的,这里为了简化一定的问题,我就不去计算了,而是给定一个固定的起始值和倍增率:

    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,然而事实上扭曲是个拉扯联动的效果,扭曲不仅仅会影响最后一行/列同时也会影响倒数第二、三、四行等,只不过这个影响效力是递减的,大家可以依据我上面所讲的原理做起来。

      到这里,利用Android翻页效果原理来实现模拟扭曲效果的步骤和方法就到这里了,主要的内容看回上文详细分析讲解,如果你看到自己安卓手机上的翻页也想动手玩玩的话,可以关注本站的一些视频教程和一些相关资料。

课课家教育

未登录

1