项目GitHub地址:https://github.com/danielzeller/Depth-LIB-Android-
#Fragment转场动画TransitionHelper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56static ObjectAnimator introAnimate(final DepthLayout target, final float moveY, final float customElevation, long delay, int subtractDelay) {
//设置动画中心点x,y坐标
target.setPivotY(getDistanceToCenter(target));
target.setPivotX(getDistanceToCenterX(target));
//设置摄像机距离(z轴),由于view全屏,摄像机需拉高,
target.setCameraDistance(10000 * target.getResources().getDisplayMetrics().density);
//沿Y轴移动View,从底部进入;-moveY * target.getResources().getDisplayMetrics().density而不是0,可以达到分层效果
ObjectAnimator translationY2 = ObjectAnimator.ofFloat(target, View.TRANSLATION_Y, target.getResources().getDisplayMetrics().heightPixels, -moveY * target.getResources().getDisplayMetrics().density).setDuration(800);
translationY2.setInterpolator(new ExpoOut());
translationY2.setStartDelay(700 + subtractDelay);
translationY2.start();
target.setTranslationY(target.getResources().getDisplayMetrics().heightPixels);
//沿X轴移动View,从左进入
ObjectAnimator translationX2 = ObjectAnimator.ofFloat(target, View.TRANSLATION_X, -target.getResources().getDisplayMetrics().widthPixels, 0).setDuration(800);
translationX2.setInterpolator(new ExpoOut());
translationX2.setStartDelay(700 + subtractDelay);
translationX2.start();
target.setTranslationX(-target.getResources().getDisplayMetrics().widthPixels);
//矫正-moveY * target.getResources().getDisplayMetrics().density距离
ObjectAnimator translationY = ObjectAnimator.ofFloat(target, View.TRANSLATION_Y, 0).setDuration(700);
translationY.setInterpolator(new BackOut());
translationY.setStartDelay(700 + 800);
translationY.start();
//绕X轴旋转,方向:顺时针(与x轴正向方向相对,即Y轴朝Z轴旋转)。X轴 Y轴旋转都为3D层变换
ObjectAnimator rotationX = ObjectAnimator.ofFloat(target, View.ROTATION_X, TARGET_ROTATION_X, 0).setDuration(1000);
rotationX.setInterpolator(new QuintInOut());
rotationX.setStartDelay(700 + FISRTDELAY + subtractDelay);
rotationX.start();
target.setRotationX(TARGET_ROTATION_X);
//自定义View实现shadow
ObjectAnimator elevation = ObjectAnimator.ofFloat(target, "CustomShadowElevation", customElevation * target.getResources().getDisplayMetrics().density, target.getCustomShadowElevation()).setDuration(1000);
elevation.setInterpolator(new QuintInOut());
elevation.setStartDelay(700 + FISRTDELAY + subtractDelay * 2);
elevation.start();
target.setCustomShadowElevation(customElevation * target.getResources().getDisplayMetrics().density);
//缩放动画,0.5f放大到原来1f倍大小。
ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, View.SCALE_X, TARGET_SCALE, target.getScaleX()).setDuration(1000);
scaleX.setInterpolator(new CircInOut());
scaleX.setStartDelay(700 + FISRTDELAY + subtractDelay);
scaleX.start();
target.setScaleX(TARGET_SCALE);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, View.SCALE_Y, TARGET_SCALE, target.getScaleY()).setDuration(1000);
scaleY.setInterpolator(new CircInOut());
scaleY.setStartDelay(700 + FISRTDELAY + subtractDelay);
scaleY.start();
target.setScaleY(TARGET_SCALE);
//Z轴不变,X轴与Y轴组成的2D面内旋转
ObjectAnimator rotation = ObjectAnimator.ofFloat(target, View.ROTATION, TARGET_ROTATION, 0).setDuration(1400);
rotation.setInterpolator(new QuadInOut());
rotation.setStartDelay(FISRTDELAY + subtractDelay);
rotation.start();
target.setRotation(TARGET_ROTATION);
rotation.addListener(getShowStatusBarListener(target));
return scaleY;
}
#WaterFragment水流动画
WaterFragment中是WaterSceneView呈现了水流的View,WaterSceneView继承View,通过重写onDraw方法,将水流画上去的。1
2
3
4
5
6
7
8
9
10
11WaterSceneView.java
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
...
for (Renderable renderable : renderables) {
renderable.draw(canvas);
renderable.update(deltaTime, 0);
}
...
}
Renderable.java内的方法很简单,是渲染的基类,抽象了通用坐标等属性和draw方法。我们看到renderables
是一个数组,它在init()
时初始化:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15WaterSceneView.java
private void init() {
renderables = new Renderable[4];
...
water = new Water(waterBitmap, foam, getYCoordByPercent(0.65f), getYCoordByPercent(1f), getXCoordByPercent(1f), 6);
renderables[0] = water;
...
renderables[1] = new Renderable(aura, getXCoordByPercent(0.5f), getYCoordByPercent(0.35f));
...
noiseScratchEffect = new NoiseEffect(noiseScratch, 100, 2f);
renderables[2] = noiseScratchEffect;
noise = new NoiseEffect(noiseReg, 30, 1.5f);
renderables[3] = noise;
...
}
Water
对象即水流对象,继承了渲染基类:Renderable
,实现了draw
方法:1
2
3
4
5
6Water.java
@Override
public void draw(Canvas canvas) {
water.draw(canvas);
...
}
water
为PathBitmapMesh
类型,PathBitmapMesh
实现了draw方法:1
2
3
4PathbitmapMesh.java
public void draw(Canvas canvas) {
canvas.drawBitmapMesh(bitmap, HORIZONTAL_SLICES, VERTICAL_SLICES, drawingVerts, 0, null, 0, paint);
}
终于看到了实际绘制的方法。至此基本的绘制流程就走通了:View(WaterSceneView.java) -> onDraw() -> Water.java ->draw() -> PathBitmapMesh.java -> draw() -> canvas.drawBitmapMesh(…)
##核心绘制类:PathBitmapMesh.java。
水流位图(49*201):
那waterSceneView是怎么铺满X轴呢?来看看Canvas提供的drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)
- 工作原理
Draw the bitmap through the mesh, where mesh vertices are evenly distributed across the bitmap. There are meshWidth+1 vertices across, and meshHeight+1 vertices down. The verts array is accessed in row-major order, so that the first meshWidth+1 vertices are distributed across the top of the bitmap from left to right
通过将图片平方成若干网格的顶点坐标画到屏幕上(注意,顶点坐标不是顶点在位图上的坐标)。所以这里有(meshWidth+1)*(meshHeight+1)个顶点。verts数组是一个有序的一维数组,从左上角开始所有的顶点坐标都被放入verts数组中,偶数为x坐标,奇数为y坐标。
- 具体实现
图片网格对应顶点坐标,canvas会扭曲图片到对应坐标,自动计算周围的扭曲曲线(该demo看不出扭曲的效果,替换纯色water图片可以看出为横向拉伸效果)。这样就达到了填铺的效果。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27PathbitmapMesh.java
private static final int HORIZONTAL_SLICES = 6;
private static final int VERTICAL_SLICES = 1;
public void matchVertsToPath(Path path, float bottomCoord, float extraOffset) {
PathMeasure pm = new PathMeasure(path, false);
for (int i = 0; i < staticVerts.length / 2; i++) {
float yIndexValue = staticVerts[i * 2 + 1];
float xIndexValue = staticVerts[i * 2];
float percentOffsetX = (0.000001f + xIndexValue) / bitmap.getWidth();
float percentOffsetX2 = (0.000001f + xIndexValue) / (bitmap.getWidth() + extraOffset);
percentOffsetX2 += pathOffsetPercent;
pm.getPosTan(pm.getLength() * (1f - percentOffsetX), coords, null);
pm.getPosTan(pm.getLength() * (1f - percentOffsetX2), coords2, null);
if (yIndexValue == 0) {
setXY(drawingVerts, i, coords[0], coords2[1]);
} else {
float desiredYCoord = bottomCoord;
setXY(drawingVerts, i, coords[0], desiredYCoord);
}
}
}
public void draw(Canvas canvas) {
canvas.drawBitmapMesh(bitmap, HORIZONTAL_SLICES, VERTICAL_SLICES, drawingVerts, 0, null, 0, paint);
}
drawingVerts
顶点坐标数组,matchVertsToPath(Path path, float bottomCoord, float extraOffset)
为计算坐标的方法。matchVertsToPath()
通过指定path和PathMeasure来计算对应点坐标,PathMeasure几个方法:
- float getLength() 返回path的长度
- boolean getPosTan(float distance, float[] pos, float[] tan) 传入一个距离distance,计算该距离的点坐标并填入pos,切线数据放入tan
这个path就相当于变化的海浪线,沿这这条线,传入一个距离就能知道对应的坐标,非常方便的就能绘画出对应的海浪。staticVerts
在位图中顶点坐标数组(0-6 0-1) = 7 2 = 14个顶点。得到各顶点百分比float percentOffsetX = (0.000001f + xIndexValue) / bitmap.getWidth()
,通过这个百分比pm.getPosTan(pm.getLength() * (1f - percentOffsetX), coords, null);
计算得到各顶点对应的坐标,这里用(1-percentOffseX)使得图片反转(注:纯色无法看出)。当yIndexValue == 0
的时候为网格上部分高顶点(Y轴方向只有一格),随时间变化取不同高度,形成海面波动的效果,同时引入第二个percentOffsetX2
取X轴错位的Y坐标(这里取的是-0.06个百分比,因为这段时间是呈线性递减变化直到24%左右,变换平滑),让波动更生动不生硬。而yIndexValue !=0
的时候底部直接设置view底部的坐标即可。
##Foam泡沫效果Foam.java
也是继承PathBitmapMesh
,能够实现海平面被光照射的反光效果,使水面更有层次感,看下具体实现:1
2
3
4
5
6
7
8
9
10
11
12Foam.java
public void matchVertsToPath(Path path, float extraOffset) {
...
if (yIndexValue == 0) {
setXY(drawingVerts, i, coords[0], coords2[1] + verticalOffset);
} else {
float desiredYCoord = Math.max(coords2[1], coords2[1] + easedFoamCoords[Math.min(easedFoamCoords.length - 1, index)]);
setXY(drawingVerts, i, coords[0], desiredYCoord + verticalOffset);
index += 1;
}
}
}
与Water
不同的地方在于每条白光的底部坐标是变化的,Water
中第二行所有顶点的Y坐标都是固定的,这里需要设计一个随机的变化来填充白光第二行的Y坐标,这里不理解原作者的意图,做法如下:1
2
3
4
5
6Foam.java
void update(float deltaTime) {
for (int i = 0; i < foamCoords.length; i++) {
easedFoamCoords[i] += ((foamCoords[i] - easedFoamCoords[i])) * deltaTime;
}
}
#WindFragment树林-熊-风动画
##树林动画
和WaterFragment一样,WindFragment里也有个主要View:BearSceneView。BearSceneView.java
中包含了RenderableThree
,ParticleSystem flames
,ParticleSystem sparks
,Smoke
,AuraDrawable
RenderableThree.java
(作者失误,树应为tree)与water实现基本一致,不同之处在于树木是随风左右弯曲,所以他的path包含leftPath和rightPath,然后根据leftPath计算每棵树的第一列XY坐标,rightPath计算第二列XY坐标。这里不做具体解释AuraDrawable.java
光晕实现类,没什么内容,交替两帧图片画到View上即可。ParticleSystem.java
粒子系统类,实现了flames火焰动画,sparks火星动画。里面有两个方法:
- draw(Canvas canvas)负责绘制矩形颗粒
- update(float deltaTime,float wind)更新下一次绘制的颗粒XY坐标的计算,值根据增量时间,和风力大小来生成,增量时间越大Y值越大,风力越大X值越大,反之亦然。