Android使用Canvas和Path自定義繪制動畫

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做動畫。
效果如下:

demo.gif

將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);
        }
    }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末介陶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子色建,更是在濱河造成了極大的恐慌哺呜,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件箕戳,死亡現(xiàn)場離奇詭異某残,居然都是意外死亡,警方通過查閱死者的電腦和手機陵吸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進(jìn)店門玻墅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人走越,你說我怎么就攤上這事椭豫。” “怎么了旨指?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵赏酥,是天一觀的道長。 經(jīng)常有香客問我谆构,道長裸扶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任搬素,我火速辦了婚禮呵晨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘熬尺。我一直安慰自己摸屠,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布粱哼。 她就那樣靜靜地躺著季二,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上胯舷,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天刻蚯,我揣著相機與錄音,去河邊找鬼桑嘶。 笑死炊汹,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的逃顶。 我是一名探鬼主播讨便,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼口蝠!你這毒婦竟也來了器钟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤妙蔗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后疆瑰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體眉反,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年穆役,在試婚紗的時候發(fā)現(xiàn)自己被綠了寸五。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡耿币,死狀恐怖梳杏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情淹接,我是刑警寧澤十性,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站塑悼,受9級特大地震影響劲适,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜厢蒜,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一霞势、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧斑鸦,春花似錦愕贡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至攒庵,卻和暖如春嘴纺,著一層夾襖步出監(jiān)牢的瞬間败晴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工栽渴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留尖坤,地道東北人。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓闲擦,卻偏偏與公主長得像慢味,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子墅冷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,665評論 2 354

推薦閱讀更多精彩內(nèi)容