PathMeasure:路徑動(dòng)畫飛機(jī)轉(zhuǎn)圈的加載動(dòng)畫

PathMeasure這個(gè)東西還是挺神奇的让禀,我們看到的許多酷炫的動(dòng)畫大多要依靠他,他就像一個(gè)計(jì)算器陨界,你給他一個(gè)path巡揍,他還你路徑總長(zhǎng)、指定長(zhǎng)度的終點(diǎn)坐標(biāo)菌瘪,路徑上某一點(diǎn)的tan腮敌、sin阱当、cos值等等。這次我們來看看怎么用它做一個(gè)飛機(jī)轉(zhuǎn)圈的加載動(dòng)畫糜工,效果如下圖:

2.gif

先了解PathMeasure的一些方法:

一弊添、初始化

他的初始化有兩種,第一種直接new空的構(gòu)造方法,得到實(shí)例后利用setPath傳入路徑捌木,如:

PathMeasure p=new PathMeasure ();
p.setPath(path,true);

第二種油坝,直接在構(gòu)造時(shí)候傳入path,

PathMeasure p=new PathMeasure (path,true);

我們看到true這個(gè)參數(shù)多次出現(xiàn)刨裆,他代表的是PathMeasure 是否閉合的參數(shù)澈圈,如果為true,那么不管path有沒有閉合帆啃,PathMeasure 都會(huì)閉合瞬女,但是只會(huì)影響PathMeasure 對(duì)path的計(jì)算,而不會(huì)改變path本身努潘。

二诽偷、getLength

顧名思義就是獲取path在計(jì)算后的長(zhǎng)度。下面我們利用getLength看看上面說的true是怎么影響計(jì)算的疯坤。我們先定義一個(gè)自定義view渤刃,如下:

public class MyView extends View {

    private Paint paint;

    public MyView(Context context) {
        this(context,null);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(5);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(50,50);
        Path path=new Path();
        path.moveTo(0,0);
        path.lineTo(0,100);
        path.lineTo(100,100);
        path.lineTo(100,0);

        PathMeasure pathMeasure1=new PathMeasure(path,false);
        PathMeasure pathMeasure2=new PathMeasure(path,true);
        Log.d("yanjin","pathMeasure1的length="+pathMeasure1.getLength()+"--pathMeasure2的length="+pathMeasure2.getLength());

        canvas.drawPath(path,paint);
    }
}
length.png

展示效果如上圖,我們打印的getLength在設(shè)置為true和false的時(shí)候贴膘,會(huì)有不同的數(shù)值卖子,一個(gè)為300,一個(gè)為400刑峡,多出來的100洋闽,大家應(yīng)該也知道在哪來的吧,哈哈哈哈突梦。

三诫舅、nextContour

我們都知道,一個(gè)path就相當(dāng)于一個(gè)集合宫患,他可以不斷地add很多不連續(xù)的路徑PathMeasure只對(duì)連續(xù)的路徑有效果刊懈,那么假如path里面有A/B/C三個(gè)不連續(xù)的線段,怎么計(jì)算他們的值呢娃闲?這里就用到了nextContour函數(shù)虚汛,簡(jiǎn)單的說他就是跳到下一個(gè)線段的作用。比如如下代碼:

public class MyView2 extends View {
    private Paint paint;

    public MyView2(Context context) {
        this(context,null);
    }

    public MyView2(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(5);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(150,150);
        Path path=new Path();
        path.addRect(-50,-50,50,50,Path.Direction.CW);
        path.addRect(-100,-100,100,100,Path.Direction.CW);
        path.addRect(-120,-120,120,120,Path.Direction.CW);
        canvas.drawPath(path,paint);

        PathMeasure pathMeasure=new PathMeasure(path,false);//已經(jīng)閉合了皇帮,我們可以傳false卷哩。
        do {
            float length = pathMeasure.getLength();
            Log.d("yanjin","len="+length);
        }while (pathMeasure.nextContour());

    }
}

輸出的值為:

2019-02-22 15:48:33.787 7889-7889/com.easy.customeasytablayout.customviews D/yanjin: len=400.0
2019-02-22 15:48:33.787 7889-7889/com.easy.customeasytablayout.customviews D/yanjin: len=800.0
2019-02-22 15:48:33.787 7889-7889/com.easy.customeasytablayout.customviews D/yanjin: len=960.0

我們可以得出以下結(jié)論:
1、nextContour函數(shù)得到的path循序與我們path.add時(shí)順序一樣属拾。
2将谊、getLength針對(duì)的是當(dāng)前線段冷溶,不是整個(gè)path。

四尊浓、getSegment函數(shù)

他的定義如下:

public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)

getSegment是用來截取一段path的逞频,通過startD與stopD設(shè)置起始點(diǎn)。然后將截取的path存到dst中栋齿,startWithMoveTo表示是否使用moveTo虏劲,將路徑的新起點(diǎn)移動(dòng)到結(jié)果path的起點(diǎn),一般為true褒颈。
進(jìn)過上面的介紹,我們可以先寫一個(gè)常見的加載動(dòng)畫了励堡,動(dòng)畫效果如下:


1.gif

這個(gè)很常見吧谷丸,下面來講講他的代碼。
先寫自定義CirclePathAnimView代碼

public class CirclePathAnimView extends View {

    private float mAnimatorValue;
    private PathMeasure mPathMeasure;
    private Path mDevPath;
    private Paint mPaint;
    private ValueAnimator mValueAnimator;

    public CirclePathAnimView(Context context) {
        this(context, null);
    }

    public CirclePathAnimView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CirclePathAnimView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setLayerType(LAYER_TYPE_SOFTWARE, null);//關(guān)閉硬件加速
        //初始化畫筆
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(getResources().getDimension(R.dimen.dp_3));
        mPaint.setColor(getResources().getColor(R.color.colorPrimary));

        //畫真正顯示的path
        mDevPath = new Path();


        //開始動(dòng)畫应结,當(dāng)然當(dāng)前動(dòng)畫你可以單獨(dú)寫成一個(gè)方法
        mValueAnimator = ValueAnimator.ofFloat(0, 1);
        mValueAnimator.setInterpolator(new LinearInterpolator());
        mValueAnimator.setDuration(2000);
        mValueAnimator.setRepeatCount(-1);
        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mAnimatorValue = (float) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
        mValueAnimator.start();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int radius = 0;
        if (width >= height) {
            radius = height / 2 - height / 8;
        } else {
            radius = width / 2 - width / 8;
        }
        //繪制path
        //先畫圓的path刨疼,但是這個(gè)圓只是用來計(jì)算
        Path circlePath = new Path();
        circlePath.addCircle(width / 2, height / 2, radius, Path.Direction.CW);


        //計(jì)算圓的path的長(zhǎng)度
        mPathMeasure = new PathMeasure(circlePath, true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float length = mPathMeasure.getLength();
        float stop = length * mAnimatorValue;
        //在0到0.5以前,起點(diǎn)不變鹅龄,0.5到1揩慕,起點(diǎn)開始向終點(diǎn)靠攏。
        float start = (float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * length));

        mDevPath.reset();
        mPathMeasure.getSegment(start, stop, mDevPath, true);
        canvas.drawPath(mDevPath, mPaint);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mValueAnimator.cancel();
        mValueAnimator = null;
    }
}

我們可以先看構(gòu)造方法扮休,我們先設(shè)置mPaint 迎卤,然后開啟動(dòng)畫,其實(shí)這個(gè)動(dòng)畫可以另寫一個(gè)方法玷坠,手動(dòng)掉一下蜗搔,這里為了方便就寫在這了,可以看到動(dòng)畫的更新監(jiān)聽里面我們獲取動(dòng)畫值之后八堡,調(diào)用invalidate刷新界面樟凄,這樣會(huì)重走onDraw方法,這里講onDraw之前兄渺,先看看onMeasure缝龄。
onMeasure里面我們主要拿到控件自己的寬高,設(shè)置了一個(gè)圓形Path--》circlePath 挂谍,但是這個(gè)circlePath 并沒有被畫出來叔壤,他只是用來被截取的,mPathMeasure 存入這個(gè)circlePath 口叙。
然后動(dòng)畫中每調(diào)用invalidate進(jìn)入onDraw的時(shí)候百新,拿動(dòng)畫值mAnimatorValue*path總長(zhǎng)得到當(dāng)前終點(diǎn),起點(diǎn)的話庐扫,我們采取在0到0.5以前饭望,起點(diǎn)不變仗哨,0.5到1,起點(diǎn)開始向終點(diǎn)靠攏的算法獲得起點(diǎn)铅辞。這樣我們就能調(diào)用截取方法了

mPathMeasure.getSegment(start, stop, mDevPath, true);

截取后原本為空的mDevPath就有數(shù)據(jù)了厌漂,我們就可以把它畫下來了。為了讓他看起來更有意思斟珊,我們?cè)贏ctivity中對(duì)他整個(gè)空間進(jìn)行旋轉(zhuǎn)苇倡,

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main29);
        CirclePathAnimView circlePathAnimView = findViewById(R.id.view);

        ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(circlePathAnimView,"rotation",0,360);
        objectAnimator.setRepeatCount(-1);
        objectAnimator.setInterpolator(new LinearInterpolator());
        objectAnimator.setDuration(2500);
        objectAnimator.start();
    }

就能看到上面的效果了。
說了半天囤踩,答應(yīng)的飛機(jī)呢旨椒?這樣,我先上代碼堵漱。還是那個(gè)自定義View综慎,我只是改了一點(diǎn)點(diǎn)代碼。

public class CirclePathAnimView extends View {

    private float mAnimatorValue;
    private PathMeasure mPathMeasure;
    private Path mDevPath;
    private Paint mPaint;
    private ValueAnimator mValueAnimator;
    private Bitmap airplayBitmap;

    public CirclePathAnimView(Context context) {
        this(context, null);
    }

    public CirclePathAnimView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CirclePathAnimView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setLayerType(LAYER_TYPE_SOFTWARE, null);//關(guān)閉硬件加速
        //初始化畫筆
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(getResources().getDimension(R.dimen.dp_2));
        mPaint.setColor(getResources().getColor(R.color.colorPrimary));

        //飛機(jī)圖片
        airplayBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.airplay);

        //畫真正顯示的path
        mDevPath = new Path();


        //開始動(dòng)畫勤庐,當(dāng)然當(dāng)前動(dòng)畫你可以單獨(dú)寫成一個(gè)方法
        mValueAnimator = ValueAnimator.ofFloat(0, 1);
        mValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        mValueAnimator.setDuration(3000);
        mValueAnimator.setRepeatCount(-1);
        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mAnimatorValue = (float) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
        mValueAnimator.start();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int radius = 0;
        if (width >= height) {
            radius = height / 2 - height / 8;
        } else {
            radius = width / 2 - width / 8;
        }
        //繪制path
        //先畫圓的path示惊,但是這個(gè)圓只是用來計(jì)算
        Path circlePath = new Path();
        circlePath.addCircle(width / 2, height / 2, radius, Path.Direction.CW);


        //計(jì)算圓的path的長(zhǎng)度
        mPathMeasure = new PathMeasure(circlePath, true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float length = mPathMeasure.getLength();
        float stop = length * mAnimatorValue;
        //在0到0.5以前,起點(diǎn)不變愉镰,0.5到1米罚,起點(diǎn)開始向終點(diǎn)靠攏。
        float start = (float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * length));

        mDevPath.reset();
        mPathMeasure.getSegment(start, stop, mDevPath, true);
        canvas.drawPath(mDevPath, mPaint);

        Matrix matrix=new Matrix();
        mPathMeasure.getMatrix(stop,matrix,PathMeasure.POSITION_MATRIX_FLAG|PathMeasure.TANGENT_MATRIX_FLAG);
        matrix.preTranslate(-airplayBitmap.getWidth()/2,-airplayBitmap.getHeight()/2);

        canvas.drawBitmap(airplayBitmap,matrix,mPaint);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mValueAnimator.cancel();
        mValueAnimator = null;
    }
}

在構(gòu)造方法中丈探,我們先獲取圖片


airplay.png
airplayBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.airplay);

在onDraw中录择,我們把飛機(jī)畫上去。

        Matrix matrix=new Matrix();
        mPathMeasure.getMatrix(stop,matrix,PathMeasure.POSITION_MATRIX_FLAG|PathMeasure.TANGENT_MATRIX_FLAG);
        matrix.preTranslate(-airplayBitmap.getWidth()/2,-airplayBitmap.getHeight()/2);
        canvas.drawBitmap(airplayBitmap,matrix,mPaint);

就這么簡(jiǎn)單碗降。
看是這么簡(jiǎn)單糊肠,但是里面有個(gè)getMatrix函數(shù)我們必須要講一講。

五遗锣、getMatrix函數(shù)

getMatrix函數(shù)可以獲得某一長(zhǎng)度終點(diǎn)的坐標(biāo)以及該坐標(biāo)的正切值的矩陣货裹。

public boolean getMatrix(float distance, Matrix matrix, int flags)

distance指的是path長(zhǎng)度,
matrix指的是容器精偿,計(jì)算后會(huì)把結(jié)果存進(jìn)來弧圆。
flags指的是要存入哪些內(nèi)容,POSITION_MATRIX_FLAG是位置信息笔咽,TANGENT_MATRIX_FLAG是切邊信息搔预。


微信圖片_20190222165911.png

圖片中箭頭代表飛機(jī),飛機(jī)沒飛一點(diǎn)叶组,就要調(diào)整角度拯田,他的方向基本要與切線一樣,那么根據(jù)圖中所示甩十,角a+角b=90度船庇,角a=角c吭产,所以飛機(jī)頭要掉角c這么多度數(shù),而getMatrix就是能獲取這些正切值鸭轮。結(jié)合畫圖也有傳Matrix的方式臣淤,剛剛好。

有時(shí)間再更新個(gè)支付寶支付成功的動(dòng)畫窃爷,嘻嘻邑蒋。
對(duì)了,不喜勿噴哦按厘!医吊,我的心臟很弱小的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末逮京,一起剝皮案震驚了整個(gè)濱河市卿堂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌造虏,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件麦箍,死亡現(xiàn)場(chǎng)離奇詭異漓藕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)挟裂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門享钞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诀蓉,你說我怎么就攤上這事栗竖。” “怎么了渠啤?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵狐肢,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我沥曹,道長(zhǎng)份名,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任妓美,我火速辦了婚禮僵腺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘壶栋。我一直安慰自己辰如,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布贵试。 她就那樣靜靜地躺著琉兜,像睡著了一般凯正。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上呕童,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天漆际,我揣著相機(jī)與錄音,去河邊找鬼夺饲。 笑死奸汇,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的往声。 我是一名探鬼主播擂找,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼浩销!你這毒婦竟也來了贯涎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤慢洋,失蹤者是張志新(化名)和其女友劉穎塘雳,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體普筹,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡败明,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了太防。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妻顶。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蜒车,靈堂內(nèi)的尸體忽然破棺而出讳嘱,到底是詐尸還是另有隱情,我是刑警寧澤酿愧,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布沥潭,位于F島的核電站,受9級(jí)特大地震影響嬉挡,放射性物質(zhì)發(fā)生泄漏叛氨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一棘伴、第九天 我趴在偏房一處隱蔽的房頂上張望寞埠。 院中可真熱鬧,春花似錦焊夸、人聲如沸仁连。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饭冬。三九已至使鹅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間昌抠,已是汗流浹背患朱。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炊苫,地道東北人裁厅。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像侨艾,于是被迫代替她去往敵國(guó)和親执虹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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