1. 動畫類型
在android
開發(fā)中,完成設(shè)計師設(shè)計的動畫的途徑有很多種格郁。
(1). 屬性動畫(Property Animtion)
Creates an animation by modifying an object's property values over a set period of time with an Animator.
通過使用Animator
類修改object
的屬性來實現(xiàn)動畫展箱。 注意屬性動畫可以為所有的 Object
類來作動畫旨枯。
(2). 視圖動畫(View Animation)
Tween animation: Creates an animation by performing a series of transformations on a single image with an Animation
Frame animation: or creates an animation by showing a sequence of images in order with an AnimationDrawable.
補間動畫:通過使用Animation
類對單個view
作一系列變形來實現(xiàn)動畫。
幀動畫:通過使用AnimationDrawable
來創(chuàng)建動畫來順序展示一連串的圖片混驰。
(3). lottie庫
lottie
庫是Airbnb開源的支持Android
,ios
,ReactNative
的動畫庫攀隔,設(shè)計師利用AE
上插件導(dǎo)出json
文件,研發(fā)人員即可使用栖榨,可以省去研發(fā)人員的大量事件昆汹,但缺點是目前還不能完成所有的動畫。
(4). gif
Fresco 是一個強大的圖片加載組件婴栽。使用它之后满粗,你不需要再去關(guān)心圖片的加載和顯示這些繁瑣的事情! 支持 Android 2.3 及以后的版本愚争。Fresco 支持 GIF 和 WebP 格式的動畫圖片映皆。
String path = ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+ getResources().getResourcePackageName(R.raw.gif_pic) + "/"
+ getResources().getResourceTypeName(R.raw.gif_pic) + "/"
+ getResources().getResourceEntryName(R.raw.gif_pic);
Uri uri = Uri.parse(path);
ControllerListener controllerListener = new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(String id, @Nullable ImageInfo imageInfo, @Nullable Animatable anim) {
if (anim != null) {
anim.start();
}
}
@Override
public void onFailure(String id, Throwable throwable) {
}
};
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setControllerListener(controllerListener)
.setAutoPlayAnimations(false)
.build();
mSimpleDraweeView.setController(controller);
(5). 使用 canvas 自定義繪制
使用canvas繪制動畫的簡單姿勢:
- 提前創(chuàng)建好
Paint
對象 - 重寫
onDraw()
挤聘,控制不同的狀態(tài),繪制不同的代碼劫扒。
今天主要介紹利用canvas
來繪制一個Path
動畫檬洞。
2. 開發(fā)步驟
本節(jié)將以一團(tuán)火圍繞一個愛心旋轉(zhuǎn)為例來說明如何使用path
做動畫。
效果如下:
將video 轉(zhuǎn)成gif沟饥,推薦一個工具:GIF Brewery by Gfycat
2.1 如何畫出「愛心」
android
有很多繪制圖形的API,對于簡單的圖形湾戳,可以直接找到對應(yīng)的API贤旷。具體可以查閱 canvas, 不過對于一些自定義的圖形,可以使用
canvas.drawPath
或者canvas.drawBitmap
,本例中的「愛心」砾脑,就是利用Path
來繪制幼驶。
可以拆成兩個弧和一條線,具體見:
Path mAnimPath = new Path();
mAnimPath.addArc(200, 200, 400, 400, -225, 225);
mAnimPath.arcTo(400, 200, 600, 400, -180, 225, false);
mAnimPath.lineTo(400, 542);
drawArc()
是使用一個橢圓來描述弧形的韧衣。
left
,top
,right
,bottom
描述的是這個弧形所在的橢圓盅藻;
startAngle
是弧形的起始角度(x 軸的正向,即正右的方向畅铭,時針3點的位置是 0 度的位置氏淑;順時針為正角度,逆時針為負(fù)角度)
sweepAngle
是弧形劃過的角度硕噩;
useCenter
表示是否連接到圓心假残,如果不連接到圓心,就是弧形炉擅,如果連接到圓心辉懒,就是扇形。
2.2 如何畫出 「光點」
「光點」的繪制則是利用canvas.drawBitmap()
來完成的谍失。該API 有很多重載方法
drawBitmap(Bitmap bitmap, float left, float top,Paint paint)
drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)
由于我們在繪制過程中要對「光點」進(jìn)行平移和旋轉(zhuǎn)眶俩,所以選擇通過變換Matrix
來繪制變換的bitmap
。
2.3 如何控制「光點」的移動速度快鱼、位置和角度
2.3.1 「光點」的移動速度
我們利用計算「愛心」路徑的長度和合適的時長來計算每次「光點」移動的步長颠印。
「愛心」路徑的長度:
mPathMeasure = new PathMeasure(mAnimPath, false);
mPathLength = mPathMeasure.getLength();
如果在onDraw
函數(shù)中沒有大量的耗時操作,就認(rèn)為16ms執(zhí)行一次攒巍,可以根據(jù)總運行時間嗽仪,來推算要執(zhí)行多少次,從而推算出步長是多少柒莉。
private static final int INVALIDATE_TIMES = 100; //希望執(zhí)行100次
mStep = mPathLength / INVALIDATE_TIMES;
2.3.2 「光點」的位置和角度
我們希望在動畫過程中能知道該「光點」在屏幕的位置和角度闻坚,可以利用API
boolean getPosTan (float distance, float[] pos, float[] tan)
該API 就是獲取在路徑長度為distance
時,該點的位置和角度兢孝。其中pos[0],pos[1]
表示此時的坐標(biāo)位置窿凤,而tan[0], tan[1]
表示計算tan
角度的x
值和y
值
mPos = new float[2];
mTan = new float[2];
mPathMeasure.getPosTan(mDistance, mPos, mTan);
2.3.3 繪制「光點」的位置和角度
通過上一節(jié)仅偎,知道了不同時刻的位置和角度,那么畫在屏幕上雳殊,利用matrix
來做變換橘沥,代碼很簡單,不做其它解釋了夯秃。
mMatrix.postTranslate(-mOffsetX, -mOffsetY); // 移到光點圖片的中間位置座咆。
float degrees = (float) (Math.atan2(mTan[1], mTan[0]) * 180.0 / Math.PI);
mMatrix.postRotate(degrees);
mMatrix.postTranslate(mPos[0], mPos[1]);
canvas.drawBitmap(mBitmap, mMatrix, null);
3. 源碼
最后放出整個效果的源碼,祝大家520快樂仓洼。
package com.taohuahua.pathanimdemo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import java.util.Date;
public class PathAnimView extends View {
private static final int INVALIDATE_TIMES = 100; //總共執(zhí)行100次
private Paint mPaint;
private Bitmap mBitmap;
private int mOffsetX, mOffsetY; // 圖片的中間位置
private Path mAnimPath;
private PathMeasure mPathMeasure;
private float mPathLength;
private double mStep; //distance each step
private float mDistance; //distance moved
private float[] mPos;
private float[] mTan;
private Matrix mMatrix;
public PathAnimView(Context context) {
this(context, null);
}
public PathAnimView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public PathAnimView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPathView();
}
public void start() {
mDistance = 0;
invalidate();
}
public void initPathView() {
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(10);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setAntiAlias(true);
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.day_top_light);
mOffsetX = mBitmap.getWidth() / 2;
mOffsetY = mBitmap.getHeight() / 2;
mAnimPath = new Path();
mAnimPath.addArc(200, 200, 400, 400, -225, 225);
mAnimPath.arcTo(400, 200, 600, 400, -180, 225, false);
mAnimPath.lineTo(400, 542);
mAnimPath.close();
mPathMeasure = new PathMeasure(mAnimPath, false);
mPathLength = mPathMeasure.getLength();
mStep = mPathLength / INVALIDATE_TIMES;
mDistance = mPathLength;
mPos = new float[2];
mTan = new float[2];
mMatrix = new Matrix();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mAnimPath == null || mPaint == null) {
return;
}
canvas.drawPath(mAnimPath, mPaint);
if (mDistance < mPathLength) {
mPathMeasure.getPosTan(mDistance, mPos, mTan);
mMatrix.reset();
mMatrix.postTranslate(-mOffsetX, -mOffsetY);
float degrees = (float) (Math.atan2(mTan[1], mTan[0]) * 180.0 / Math.PI);
mMatrix.postRotate(degrees);
mMatrix.postTranslate(mPos[0], mPos[1]);
canvas.drawBitmap(mBitmap, mMatrix, null);
mDistance += mStep;
invalidate();
} else {
canvas.drawBitmap(mBitmap, mMatrix, null);
}
}
}