介紹
升級MIUI8之後,小米自帶的天氣應用晴天的時候動畫效果很贊。然後就搗鼓了幾下寫了個簡易的demo。
我們先來看看實現後的效果:
正常模式:
View跟隨手機搖晃後移動:
分析
動畫分解
- 飛翔的鳥群
- 樹的枝幹會傾斜15~30度,並在達到傾斜的最大角度後丟擲樹葉,等樹葉消失後,回到最初位置。
- 樹葉每次丟擲角度不固定,並且在下落一定距離後漸漸消失。
- 左右搖晃手機,背景的山會隨著手機移動。
實現
(1) 下載app,然後將其字尾名改為zip解壓獲取我們需要的資源圖片:
(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);複製程式碼
搖晃手機獲取差值參考Github:PanoramaImageView