Apr 8

Android简单实现界面扭曲与简单的物理效果实现

Lrdcq , 2016/04/08 18:17 , 程序 , 閱讀(5662) , Via 本站原創
界面扭曲,听起来就是高大上酷炫屌的的动效,说起界面扭曲特效,常见的系统中,iOS和OSX的动效中会稍有出现,印象比较深的当年ubuntu就有很多3d特效之类的涉及到界面扭曲,微软平台上有WindowsFX软件可以做到类似的事情。而回到安卓,大概系统动效里面完全没有扭曲效果吧。

扭曲效果往往被扣上好看不实用的标签,但是在一些比较注重界面交互与动效的app上,还是有存在的必要性的。那么扭曲效果与其它界面变化有什么不同呢?相对于移动旋转缩放,包括看起来高大上的3d旋转,它们最大的特点是,可以用矩阵来表示这一变化,在显示层也就是把矩阵叉乘到原本参数上的事儿。而扭曲,就是一个远比这个复杂的工作了。

在实际使用中,常见的app使用场景包括:图像弯曲贴图(静态),精确的大块图/界面回弹动画,翻页效果等。

1.实现原理

任何一个界面动效考虑实现原理,我们都会考虑平台CPU/GPU与具体实现性能(fps太低影响视觉效果)。所以排出canvas不可以完成的特效,摆在我们面前的方案就只有:canvas绘制与gl绘制。而实现界面扭曲,最方便的方式,也是canvas实现的唯一方式,就是方法:drawBitmapMesh。从实际效果上来看,这个方法性能可以满足1/2绘fps的需求。

2.drawBitmapMesh

这个api是从api1就引入,这么多年来并没有变过,但它在复杂的交互动效制作中的重要性是显而易见的。它最重要的东西,当然是mesh咯。接触过图形学或者3d建模的同学当然知道mesh是什么,mesh就是它的字面意思,网格。在3d建模中,再复杂的模型也是由无数的三角形构成的网格拼装起来的,而贴图就像一层布一样,以一顶投影方式紧贴在网格上(uv贴图)。显然,这个api就是让我们把一个位图绘制在一个网格上。那么看看参数:
void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
            @NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
            @Nullable Paint paint)

它们分别是:
bitmap:当然,输入的位图源咯。
meshWidth,meshHeight:横纵坐标分段的段数,比如下面的图例一开始都是分的4段。
verts:3d民工的常见概念,点的数组咯。奇数位是x坐标偶数位是y坐标,所以这个数组的总长度应该大于等于(meshWidth+1) * (meshHeight+1) * 2才行。
vertOffset:坐标错位,如果讲动画中所有坐标都计算完成储存在一个verts里,动画是只需要移动offset就可以了。
colors:叠加颜色,可以没有。如果有,长度至少为 (meshWidth+1) * (meshHeight+1)也就是点的个数。
colorOffset:用法与意义和vertOffset彼此彼此。
paint:绘图api的日常。

那么,最简单的将一张图以4为分段的方式贴上去就是这样了:
int count = 4;
int[] c = new int[(count + 1) * (count + 1)];
float[] v = new float[(count + 1) * (count + 1) * 2];
int k = 0;
for (int i = 0; i <= count; i++) {
  for (int j = 0; j <= count; j++, k += 2) {
    v[k] = j * 400 + 100;
    v[k + 1] = i * 400 + 100;
  }
}
canvas.drawBitmapMesh(BitmapFactory.decodeResource(getResources(), R.drawable.a), count, count, v, 0, null, 0, null);

點擊在新視窗中瀏覽此圖片

当然,上面的红线是用其它代码绘制的。要注意到的是网格每一个四边形的切线都是左斜的,这是固定不变的且对后面的效果实现有非常巨大的影响。然后,既然网格的点是我们自己生成的,那么我们就可以改变其中的点的位置对图像进行拉伸了。比如我对右上角和右下角的点进行拉伸,如下图。

點擊在新視窗中瀏覽此圖片

可以清晰的看到图像的拉伸,而且收到三角切线方向的影响,两个四边形内部拉伸出来的效果并不相同。不过我都把切线打出来了大家也都应该理解一下了。大家看到既然可以往图像外拉伸,那么往里拉伸是不是也可以呢,是不是可以覆盖到其它网格上实现3d效果呢(虽然数据中并没有z)?稍微测试了一下,如下图:

點擊在新視窗中瀏覽此圖片

可以看到,依然受到切线影响,拉伸效果并不一致,右上角被拉到第二行网格的部分被第二行图像挡住了,最后一个点并没有被谁挡住,但是三角形两部分之间会互相遮挡。经过多次测试后,总结出规律:所有的四边形无论怎么拉伸怎么折叠,从左到右从上到下一次z轴上升,并且每一次四边形内右侧的三角高于左侧。话说这也可以猜出这个api的实现原理,并没有通过gl或者软件渲染中真正的3d部分去生成mesh去绘制,只是单纯的用mesh把图像拆开一块一块去画而已。

当然,本身,拉伸一个角或者一个点也不是这个api正确的用法,我们是要用一顶算法生成vert并用来扭曲图像的。比如下列图:

點擊在新視窗中瀏覽此圖片點擊在新視窗中瀏覽此圖片點擊在新視窗中瀏覽此圖片

也许4分段的时候,图像边上一段一段的还可以看得很清楚,但是当分段增加到8段,16段的时候,已经就基本干净不出来了,要不是我绘制了红色的网格线的话,看起来就和曲面一样了。这就是传说中的曲面细分(才不是),就是3d建模中所谓的面越多,模型越精细,就是这个道理。通过不同大小的图调整不同的细分度,我们就可以调整出非常合适毫无违和感的曲面了。

然后再看看颜色,简单的做了两个颜色的实例:第一个分别从0xFF000000到0xFFFFFFFF到0x00FFFFFF;第二个是在第一,第三和第八个点设置了红色。

點擊在新視窗中瀏覽此圖片點擊在新視窗中瀏覽此圖片

可以看到,第一幅图中黑色区域变黑了,但是白色区域并没有改变,这个效果类似于ps中叠加方式变暗,另外alpha通道的叠加方式看起来和一般的叠加方式不同,会直接将颜色的alpha值设置为图像的alpha通道值。另外,仔细看第二幅图中红色往外渐变的方式,可以注意到颜色的渐变方式依然受到网格切线的影响,站在切线方向的颜色渐变会影响到整个四边形,而另一个方向的颜色渐变会只占领当前三角形。考虑到网格的实际密度的允许范围,事实上这个api的颜色上色效果并不是很好,这也是上色功能(看起来设计之初是为了做阴影和立体效果的)并没有什么实际使用。

3.扭曲运算

当然,扭曲图像只是我们的第一步,一个静态的扭曲画面并没有什么好看的,往往出现扭曲的场景都是动效。当然,不同的动效实现的方法。本demo的目标是实现界面拖动是像旗帜一样的扭曲,在ubuntu和windowsfx中比较常见,效果如下图:

點擊在新視窗中瀏覽此圖片

可以想想,这个动效的实现原理,是拖动的点是完全跟随手指一动到目标位置,而周围的点是随着第一个点的牵引带动,以一定的简谐运动的拉伸方式才开始运动——似乎计算很麻烦的样子,再简化一下,周围的点的运行,比牵引点慢,而且距离越远,起步速度越慢。

那么对于每一个点的计算算法,我们这样设定,每一个点拥有(x,y)的坐标与(vx,vy)的速度,速度初始值为0,那么对于每一帧在手指的牵引下一动(mx,my),速度的变化为v = v * (1-rate) + m * rate; pos += v;其中rate是一个(0,1]的常数,可以想想如果rate是1的时候,速度每次直接变化为了手指移动的变化,因此每次移动就直接跟随手指移动过去了——这就是中心点,而rate在0到1之间的话,移动会受到之前的速度影响,因此移动反应会慢很多;同理如果rate真为0的话,由于初始速度为0,那么速度永远为0,那这个点也就被固定下来了。

另外,当每次移动的m为0,即当手指停下来之后,每次v将随着rate立刻或者缓慢递减,直到趋近于0。可以方便的证明每次移动从V1向量到Vn向量。
點擊在新視窗中瀏覽此圖片
如果直接移动过去的话,显然,将这些向量累加就可以了:
點擊在新視窗中瀏覽此圖片
然而如果考虑rate,递推公式是这样的:
點擊在新視窗中瀏覽此圖片
显然,当R=1的时候,就完全和上一公式相等了,当R=0的时候,这个合就为0。且可以证明得到,当R!=0时,Va与Vb就是相等的。至少直觉是正确的就可以了。

4.其它

那么通过这种计算方法在每一帧的移动后对每一个点进行运算,这个demo中,我们就可以模拟出比较逼真的物理效果了。当然,除了点的运算,对界面进行扭曲的动画考虑和优化的点还包括:
1.界面截取:是否要动态截取呢,这关乎到动画的性能。
2.动画帧率:动画帧率太大,运算时间少容易卡顿和跳帧得不偿失,帧率太小,当然动画就不流畅了,需要根据实际情况进行取舍。
3.mesh细分:到底细分多少段才可以保证动画中图像弯曲得流畅,要不要动态运算段数。
4.延迟:是否在移动停止后动画继续播放呢(毕竟可以到正无穷的运算),这关系到用户体验的取舍。
5.弹性交互:最好的交互动效莫过于带弹性反馈的东西了,那么在物理运算的过程中,是否考虑一下真实的简谐运动反馈效果呢?

同样,界面扭曲制作其他动效的同时,也要考虑类似的设计,还包括:
6.动画的点是否可以事前运算好直接用offset使用,还是事实运算。
7.mesh界面移动的过程中是否要加入参数z进行模拟3d运算来实现更逼真的3d效果。
8.实现太复杂drawBitmapMesh是否能满足需求与性能。
logo
mn
2018/04/13 10:09
你好我是要动效拉伸的效果,能看下demo源码么,像是图 4、图5、图6那种效果
一颗金星
2018/01/16 14:14
你好,有demo源码吗?我现在遇到四个角控制拉伸扭曲图片这个需求,也想到用drawBitmapMesh方法实现,但是想不到怎么确定网格交点坐标,希望能看下你的demo,谢谢