介绍
升级MIUI8之后,小米自带的天气应用晴天的时候动画效果很赞。然后就捣鼓了几下写了个简易的demo。
我们先来看看实现后的效果:
正常模式:img1
View跟随手机摇晃后移动:
img2
分析
动画分解
飞翔的鸟群
树的枝干会倾斜15~30度,并在达到倾斜的最大角度后抛出树叶,等树叶消失后,回到最初位置。
树叶每次抛出角度不固定,并且在下落一定距离后渐渐消失。
左右摇晃手机,背景的山会随着手机移动。
实现
(1) 下载app,然后将其后缀名改为zip解压获取我们需要的资源图片:
img3
(2) 实现鸟群动画
思路:通过解压后得到的素材可以发现鸟扇动翅膀的实现类似于帧动画,鸟群的运动轨迹是曲线,我们可以使用贝塞尔曲线,为了能够实时移动鸟群,我们需要获取贝塞尔曲线上面的点坐标,这里我是使用函数来计算出点坐标,也可以用PathMeasure来实现。综上,用ValueAnimator实现实时计算鸟群移动点坐标,设定一个计数器,用来更新鸟的Bitmap实现帧动画效果。
核心代码
isDrawBirds = true;
BezierEvaluator bezierEvaluator = new BezierEvaluator(newPointF(birdControlPointX, birdControlPointY));
final ValueAnimator anim = ValueAnimator.ofObject(bezierEvaluator,
new PointF(birdStartPointX, birdStartPointY),
new PointF(birdEndPointX, birdEndPointY));
anim.setDuration(8000);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
PointF point = (PointF) valueAnimator.getAnimatedValue();
birdMovePointX = (int) point.x;
birdMovePointY = (int) point.y;
++birdTimes;
if (birdTimes % 8 == 0){
updatebirds(birdTimes);
}
invalidate();
}
});
anim.setInterpolator(new LinearInterpolator());
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animation) {
super.onAnimationRepeat(animation);
birdTimes = 0 ;
}
});
anim.setRepeatMode(ValueAnimator.RESTART);
anim.setRepeatCount(ValueAnimator.INFINITE);
anim.start();
private void updatebirds(int birdTimes){
bird = birdsList.get(birdTimes % 16);
}复制代码
(2) 树动画
思路:看到这素材就知道树是拼凑出来的。树动画的实现,通过观察可以发现是treeBallLeft,treeBallMiddle,treeBallRight,通过绕treeBranch的中心点实现。
核心代码
valueAnimatorTree = new ValueAnimator().ofFloat(0, 15);
valueAnimatorTree.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
rotateValueTree = 360 - (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimatorTree.addPauseListener(new Animator.AnimatorPauseListener() {
@Override
public void onAnimationPause(Animator animation) {
rotateValueTree = 0;
isDrawTree = true;
startAnimLeaf();
}
@Override
public void onAnimationResume(Animator animation) {
}
});
valueAnimatorTree.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animation) {
++timesTree;
if (timesTree % 2 == 1)
valueAnimatorTree.pause();
}
});
valueAnimatorTree.setDuration(2000);
valueAnimatorTree.setRepeatMode(ValueAnimator.REVERSE);
valueAnimatorTree.setRepeatCount(ValueAnimator.INFINITE);
valueAnimatorTree.setInterpolator(new LinearInterpolator());
valueAnimatorTree.start();复制代码
(3) 树叶动画
思路:树叶的初始位置,将树叶藏在treeBallMiddle里面。然后执行路径动画,执行路径动画的同时,根据getAnimatedFraction() 获取动画进行的百分比计算出树叶的旋转角度,动画到运行75%的时候执行透明动画就行了
核心代码
public void startAnimLeaf() {
randomRotate = Math.random() * 90;
BezierEvaluator bezierEvaluator = new BezierEvaluator(new PointF(treeControlPointX, treeControlPointY));
final ValueAnimator anim = ValueAnimator.ofObject(bezierEvaluator,
new PointF(treeStartPointX, treeStartPointY),
new PointF(treeEndPointX, treeEndPointY));
anim.setDuration(4000);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
PointF point = (PointF) valueAnimator.getAnimatedValue();
moveLeafX = point.x;
moveLeafY = point.y;
float fraction =valueAnimator.getAnimatedFraction();
rotateValueLeaf = 90 * fraction;
if (fraction>=0.75 && valueAnimatorAlpha.isRunning()== false){
startAlpha();
}
invalidate();
}
});
anim.setInterpolator(new LinearInterpolator());
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
isDrawTree = false;
valueAnimatorTree.resume();
invalidate();
}
});
anim.start();
}
private void startAlpha(){
valueAnimatorAlpha = ValueAnimator.ofFloat(0,1);
valueAnimatorAlpha.setDuration(1000);
valueAnimatorAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float fraction = (float) valueAnimator.getAnimatedValue();
treeLeafPaint.setAlpha((int) (255 - 255 * fraction));
}
});
valueAnimatorAlpha.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
treeLeafPaint = new Paint();
}
});
valueAnimatorAlpha.setInterpolator(new LinearInterpolator());
valueAnimatorAlpha.start();
}复制代码
(4) 背景
思路:通过BitmapFactory.Option来压缩背景图片的大小,保证View的流畅性。
摇晃手机的同时,背景跟着动的实现:设置背景图片的时候,宽是屏幕宽度+maxoffsetX*2。通过implements SensorEventListener,获取摇摆手机时候晃动的百分比,然后在onDraw中调用
currentOffsetX = maxOffsetX * mProgress;
canvas.translate(currentOffsetX, 0);复制代码