Android翻页效果原理实现之模拟扭曲

尊重原创转载请注明:From AigeStudio(http://blog.csdn.net/aigestudio)Power by Aige 侵权必究!

炮兵镇楼

上一节我们实现了翻页的曲线效果,但是效果有点小瑕疵不知道大家发现没有:

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

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

19个网格将控件分割为20x20的网格细分线条区域,尔后我们就不需要使用drawBitmap绘制折叠区域了而是改用drawBitmapMesh。之前在讲1/6的时候有盆友多次小窗过我离屏缓冲是个什么意思需要注意什么,这里我权当演示,在绘制扭曲图像的时候使用一个单独的Bitmap并将其装载进一个额外的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的大小就刚好和我们的控件一样大,但是我们实现扭曲的那一部分图像是超出该范围外的:

如上图透明红色的范围是我们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,而事实上扭曲是个拉扯联动的效果,扭曲不仅仅会影响最后一行/列同时也会影响倒数第二、三、四行等,只不过这个影响效力是递减的,这部分就留给大家自己去做了,原理我讲的很清楚了。

这一节到此为止,下一节我们将完善最终效果结束本例所有的Study~

源码下载:传送门

时间: 2024-08-23 23:25:06

Android翻页效果原理实现之模拟扭曲的相关文章

Android翻页效果原理实现之翻页的尝试

尊重原创转载请注明:From AigeStudio(http://blog.csdn.net/aigestudio)Power by Aige 侵权必究! 炮兵镇楼 在<自定义控件其实很简单>系列的前半部分中我们用了整整六节近两万字两百多张配图讲了Android图形的绘制,虽然篇幅很巨大但仍然只是图形绘制的冰山一角,旨在领大家入门,至于修行成果就看各位的了--那么这个些列主要是通过前面学习到的一些方法来尝试完成一个翻页的效果. 对于我个人来说,我是不太建议大家在没自己去尝试前看本文的,因为你看

Android翻页效果原理实现之曲线的实现

尊重原创转载请注明:From AigeStudio(http://blog.csdn.net/aigestudio)Power by Aige 侵权必究! 炮兵镇楼 上一节我们通过引入折线实现了页面的折叠翻转效果,有了前面两节的基础呢其实曲线的实现可以变得非常简单,为什么这么说呢?因为曲线无非就是在折线的基础上对Path加入了曲线的实现,进而只是影响了我们的Region区域,而其他的什么事件啊.滑动计算啊之类的几乎都是不变的对吧,说白了就是对现有的折线View进行update改造,虽然是改造,但

Android 实现书籍翻页效果(转载链接)

Android 实现书籍翻页效果----原理篇 效果图: Android 实现书籍翻页效果----完结篇 效果图: Android 实现书籍翻页效果----升级篇 效果图: Android 实现书籍翻页效果---番外篇之光影效果

android开发教程:android手势翻页效果

听麦子学院android开发老师说要实现手势翻页效果,主要用到ViewFlipper和GestureDetector.  ViewFlipper变化当前显示内容,GestureDetector监听手势.  用于多页的展示非常酷.  现在我就给大家简单说明下,  首先创建工程:TestFlip,创建主Activity:TestFlip.  在res/layout/main.xml中添加flipper信息,如下:  Java代码   1. <?xml version="1.0" en

Android用悬浮按钮实现翻页效果

今天给大家分享下自己用悬浮按钮点击实现翻页效果的例子. 首先,一个按钮要实现悬浮,就要用到系统顶级窗口相关的WindowManager,WindowManager.LayoutParams.那么在AndroidManifest.xml中添加权限: ? 1 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> 然后,我们要对WindowManager,WindowManager.La

Android应用源码之双面翻页效果

Android应用源码之双面翻页效果 在网上看到一个非常不错的源码,想跟大家分享一下.仔细翻了翻源码是实现翻页效果的,有兴趣的朋友可以研究一下! 下载地址:http://www.devstore.cn/code/info/804.html 运行截图:   

android之实现上下左右翻页效果

如果实现上下或者左右翻页效果,我们借助下这个开源项目:https://github.com/openaphid/android-flip Aphid FlipView是一个能够实现Flipboard翻页效果的UI组件. 下载完毕后导入到当前你的项目中,我们来下上下翻页的效果图:           直接贴出代码: 布局文件: <span style="font-size:14px;"><?xml version="1.0" encoding=&qu

前端实现类似于iBooks的图书翻页效果的网络阅读软件(一)

昨天晚上在群里交流各种脑动大开的题目,我顺手也提了一个问题: JS如何做“字符分页“ 原意是源于我4年前公司项目,我负责开发1年的样子,后来各种原因就没有然后了… http://reader.appcarrier.com/     以上图片是手机上的截图,Metro风格当前可是风靡一时,软件本身是类似现在的”追书神器” 通过书名,在网络上搜索到对应的内容,之后保存到本地数据库.在通过JS获取数据再处理 自己装好测了下,貌似下载服务器已经挂了~ 程序采用PhoneGap打包的,数据采集是用底层完成

微信里经常看到的滑动翻页效果,slide

上个星期我们的产品姐姐让我帮她写个微信里经常看到的滑动翻页效果,今天抽空写了3个小demo(只写了webkit需要chrome模拟手机看 开启touch事件), 故此写个随笔. 1.demo1,整个大容器tranlateY(性能应该是最好的,但是如果增删一页的话对css影响很大,如果用sass或less除外) html: <!DOCTYPE html> <html> <head> <title></title> <meta name=&qu