深刻了解一些Android動畫

1、PropertyValuesHolder

閱讀本文須要上一文Android屬性動畫的基礎,這樣才能夠明白接下來要講什麼。前端

1.理解和使用

PropertyValuesHolder 是ObjectAnimation相似的一個方法,只是少了一個target,就是要執行的控件。看看正常的使用方法:會同時執行所有的Holderjava

public void  doPropertyValuesHolder(){
        //定義一個旋轉Holder
        PropertyValuesHolder rotationHolder=
                PropertyValuesHolder.ofFloat(
                        "rotation",
                        60f,40f,100f,-60f,40f,88f,77f);

        //定義一個透明Holder
        PropertyValuesHolder alphaHolder=
                PropertyValuesHolder.ofFloat(
                        "alpha",
                        0.01f,0.5f,1.0f,0.8f,0.2f,0.0f);
        
    	//加載進ObjectAnimator
        ObjectAnimator objectAnimator=ObjectAnimator.ofPropertyValuesHolder(ballImageView,rotationHolder,alphaHolder);
        objectAnimator.setDuration(3000);
        objectAnimator.start();
    }

2.方法和參數

能夠看看這個方法的參數:android

ObjectAnimator ofPropertyValuesHolder(Object target,PropertyValuesHolder... values)
PropertyValuesHolder ofFloat(String propertyName, float... values)

Object target 是要顯示動畫的控件編程

PropertyValuesHolder... values 裝載多個PropertyValuesHoldercanvas

String propertyName 表明要反射的參數,跟ObjectAnimation的參數是同樣的ide

float... values 表明是可變長參數
這樣的方法還有如下圖片這些:函數

圖片.png

其中ofObject()方法 ,也是跟ObjectAnimation的類似,也是要自定義TypeEvaluator。
圖片.png佈局

2、Keyframe

1.理解和使用

看名字,就是理解爲關鍵幀的意思,在動畫中,在某幀作一些操做,從而實現對比效果比較明顯的效果。
關鍵幀表示是某個物體在哪一個時間點應該在哪一個位置上。
具體使用:post

public void  doPropertyValuesHolderKeyFrame(){

        //頭keyframe1,從進度0.6開始,在進度60%的時候,數值是0.1f
        Keyframe keyframe1=Keyframe.ofFloat(0.6f,0.1f);

        //中間keyframe2
        Keyframe keyframe2=Keyframe.ofFloat(0.1f,0.8f);

        //尾部keyframe3,以50%進度做爲結束,這時候的數值爲0.2f
        Keyframe keyframe3=Keyframe.ofFloat(0.5f,0.2f);

        //裝載到Holder中,並設置要反射的方法,這是反射的是setAlpha()方法,控制透明度
        PropertyValuesHolder alphaHolder=PropertyValuesHolder.ofKeyframe("alpha",keyframe1,keyframe2,keyframe3);

        //把裝載到Holder中裝載到ObjectAnimator或者ValueAnimation
        ObjectAnimator objectAnimator=ObjectAnimator.ofPropertyValuesHolder(ballImageView,alphaHolder);
        objectAnimator.setDuration(3000);
        objectAnimator.start();
    }

2.方法和參數

Keyframe ofFloat(float fraction, float value)

float fraction 表示進度動畫

float value  表示在這個進度下的數值

PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values)

String propertyName 要反射的set方法

Keyframe... values 傳入Keyframe

Keyframe的方法,也是和其餘的相似的。
圖片.png

Keyframe的set方法,設置進度,插值器,數值。
沒有設置插值器的時候,默認是線性插值器

keyframe1.setInterpolator(new LinearInterpolator()); //默認線性插值器

圖片.png

3.幀的操做

直接寫結論:

  • 若是去掉0幀,則以第一個關鍵幀爲起始位置
  • 若是去掉結束幀(進度爲1),則以最後一個關鍵幀爲結束位置
  • 使用keyframe來構建動畫,至少須要2幀

3、ViewPropertyAnimator

1.理解和使用

能夠經過串行的形式,快速定義動畫,省去一些定義,在每次界面繪製的時候,啓動動畫,比其餘的更節省消耗。
好比:

ballImageView.animate().alpha(0.5f).rotation(360f).scaleX(1.5f).translationX(100f);

2.參數和方法

能夠看到這些方法的返回值,基本都是ViewPropertyAnimator
圖片.png
圖片.png
再引用一張表格:

函數 含義
alpha(float value) 設置透明度
scaleY(float value) 設置 Y軸方向的縮放大小
scaleX(float value) 設置X軸方向的縮放大小
translationY(float value) 設置Y軸方向的移動值
translationX(float value) 設置X軸方向的移動值
rotation(float value) 設置繞Z軸旋轉度數
rotationX(float value) 設置繞x軸旋轉度數
rotationY(float value) 設置繞 Y 軸旋轉度數
x(float value) 相對於父容器的左上角座標在 X軸方向的最終位置
y(float value) 相對於父容器的左上角座標在Y軸方向的最終位置
alphaBy(float value) 設置透明度增量
rotationBy(float value) 設置繞Z軸旋轉增量
rotationXBy(float value) 設置繞 X 油旋轉增量
rotationYBy(float value) 設置統Y軸旋轉增量
translationXBy(float value) 設置X軸方向的移動值增量
translationYBy(float value) 設置Y軸方向的移動值增量
scaleXBy(float value) 設置X軸方向的縮放大小增量
scaleYBy(float value) 設置 Y軸方向的縮放大小增量
xBy(float value) 相對於父容器的左上角座標在 X軸方向的位置增量
yBy(float value) 相對於父容器的左上角座標在 Y軸方向的位置增量
setlnterpolator(Timelnterpolator interpolator) 設置插值器
setStartDelay(long startDelay) 設置開始延時
setDuration(long duration) 設置動畫時長

4、animateLayoutChanges

 android:animateLayoutChanges="true"
在Layout加入控件,或者移除控件的時候,添加動畫,可是隻能使用默認動畫。

<LinearLayout
            android:animateLayoutChanges="true"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"/>

5、LayoutTransition

LayoutTransition能夠控制ViewGroup的動畫,可使用自定義的動畫。
具體使用:

public void doLayoutTransition(){

        LinearLayout linearLayout=new LinearLayout(this);

        //1.建立實例
        LayoutTransition transition=new LayoutTransition();

        //2.建立動畫
        ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(null,"rotation",0f,90f,0f);

        //3.動畫出現形式進行設置
        transition.setAnimator(LayoutTransition.DISAPPEARING,objectAnimator);

        //4.將LayoutTransition設置到ViewGroup中
        linearLayout.setLayoutTransition(transition);
     
      	//5.開源動畫庫 NineOldAndroids
     
    }

setAnimator(int transitionType, Animator animator)

這個方法中,transitionType有五個選項

image.png

CHANGE_APPEARING  因爲容器中要顯示一個新的元素,其餘須要變化的元素所應用的動畫(問題多,不經常使用)

_CHANGE_DISAPPEARING_  當個容器中某個元素要消失時,其餘須要變化的元素所應用的動畫(問題多,不經常使用)

_CHANGING_  容器中正在更改的元素的動畫變化

_APPEARING_  元素在容器中出現時所定義的動畫

_DISAPPEARING_  元素在容器中消失時所定義的動畫

6、PathMeasure

PathMeasure相似一個計算器,能夠計算出目標path的座標,長度等

1.初始化

public void doPathMeasure(){
        Path path=new Path();

        //初始化方法1
        PathMeasure pathMeasure1=new PathMeasure();
        pathMeasure1.setPath(path,true);

        //初始化方法2
        PathMeasure pathMeasure2=new PathMeasure(path,false);
    }

setPath(Path path, boolean forceClosed)
path    就是表明要計算的目標Path。
forceClosed    是否閉合,true會計算閉合狀態下的Path,false會按照Path原來狀況來計算。

2.函數調用

自定義一個view

public class PathView extends View {
    Path mPath;
    Paint mPaint;
    PathMeasure mPathMeasure;

    public PathView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPath=new Path();
        mPaint=new Paint();
        mPathMeasure=new PathMeasure();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.translate(250,250); //畫布移動
        mPaint.setColor(Color.BLUE); //畫筆顏色
        mPaint.setStrokeWidth(5); //畫筆粗細
        mPaint.setStyle(Paint.Style.STROKE); //畫筆風格

        mPath.moveTo(0,0);
        mPath.lineTo(0,100);
        mPath.lineTo(100,100);
        mPath.lineTo(100,0);

        mPathMeasure.setPath(mPath,true);
        Log.v("showLog",
                "getLength()=="+mPathMeasure.getLength()
                        +"  isClosed()=="+ mPathMeasure.isClosed()); //結果400.0  true

        mPathMeasure.setPath(mPath,false);
        Log.v("showLog",
                "getLength()=="+mPathMeasure.getLength()
                        +"  isClosed()=="+ mPathMeasure.isClosed()); //結果300.0  false

        canvas.drawPath(mPath,mPaint); //繪製路徑
    }
}

繪製效果:

image.png

2.1 PathMeasure.getLength()

PathMeasure.getLength() 函數用於測量路徑的長度

2.2 PathMeasure.isClosed()

PathMeasure.isClosed() 函數用於返回是否測量閉合狀態

2.3 PathMeasure.nextContour()

mPath.addRect(-50, -50, 50, 50, Path.Direction.CW);
        canvas.drawPath(mPath, mPaint);

        mPath.addRect(-100, -100, 100, 100, Path.Direction.CW);
        canvas.drawPath(mPath, mPaint);

        mPath.addRect(-120, -120, 120, 120, Path.Direction.CW);
        canvas.drawPath(mPath, mPaint);

        mPathMeasure.setPath(mPath, false);

        do {
            float len = mPathMeasure.getLength();
            Log.v("showLog", "len=" + len);
        } while (mPathMeasure.nextContour());

效果:

image.png

打印結果:

len=400.0
len=800.0
len=960.0

PathMeasure.nextContour()獲得的順序與添加的Path的順序相同

PathMeasure.getLength()只是獲得當前path的長度,不是所有的長度

2.3 getSegment()

使用getSegment函數須要禁用硬件加速 在構造方法中加入
setLayerType(LAYER_TYPE_SOFTWARE,null);

mPath.addRect(-50, -50, 50, 50, Path.Direction.CW);
        mPathMeasure.setPath(mPath,false); //計算的path
        mPathMeasure.getSegment(0,150,mDstPath,true); //截取並添加到mDstPath,是添加,不是其餘
        canvas.drawPath(mPath, mPaint); //繪製原來的path

        canvas.translate(200,0); //畫布移動
        mPaint.setColor(Color.RED);
        canvas.drawPath(mDstPath, mPaint); //繪製添加後的mDstPath
boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)

startD path開始截取的點,截取的起始點,是以左上角的點開始的

stopD 截取中止的點

dst 截取後添加到的path

startWithMoveTo  是否保存原狀,true保存原樣,false則會鏈接初始點和終點,和原來的不必定相同形狀
以上代碼的效果: 截圖的方向,與原來的path的生成方向有關

image.png

2.4 動態畫圓的例子

代碼:

public class PathView extends View {
    Path mPath, mDstPath;
    Paint mPaint;
    PathMeasure mPathMeasure;
    float mCurAnimValue;

    public PathView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        mPath = new Path();
        mDstPath = new Path();
        mPaint = new Paint();
        mPathMeasure = new PathMeasure();

        mPaint.setColor(Color.BLUE); //畫筆顏色
        mPaint.setStrokeWidth(5); //畫筆粗細
        mPaint.setStyle(Paint.Style.STROKE); //畫筆風格

        mPath.addCircle(100, 100, 50, Path.Direction.CW); //一個完整的圓
        mPathMeasure.setPath(mPath, true); //要計算的path

        ValueAnimator animator = ValueAnimator.ofFloat(0, 1); //進度 0~1
        animator.setRepeatCount(ValueAnimator.INFINITE); //無限循環
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurAnimValue = (Float) animation.getAnimatedValue(); //獲得當前的進度
                invalidate();//重繪,從新執行onDraw()方法
            }
        });
        animator.setDuration(5000);
        animator.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(100, 100); //畫布移動

        float stop=mPathMeasure.getLength()*mCurAnimValue; //一個進度肯定一個截取點
        mDstPath.reset();
        mPathMeasure.getSegment(0,stop,mDstPath,true); //一點點添加

        canvas.drawPath(mDstPath,mPaint); //每次有進度更新,就繪製一小段截取
    }
}

效果:
動態畫圓.gif

2.5 getPosTan()

先看看函數的定義:

boolean getPosTan(float distance, float pos[], float tan[])

float distance 距離path的其實長度

float pos[]  該點的座標值。x和y pos[0]=x,pos[1]=y

float tan[] 該點的正切值。x和y pos[0]=x,pos[1]=y  tan<a=y/x

2.6 箭頭畫圓的例子

代碼:

public class PathView extends View {
    Path mPath, mDstPath;
    Paint mPaint;
    PathMeasure mPathMeasure;
    float mCurAnimValue;
    Bitmap mArrowBmp;
    float[] mPos;
    float[] mTan;
    int mCenterX,mCenterY;
    float mRadius;

    public PathView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        mPath = new Path();
        mDstPath = new Path();
        mPaint = new Paint();
        mPathMeasure = new PathMeasure();
        mPos=new float[2];
        mTan=new float[2];

        //加載箭頭圖片
        mArrowBmp= BitmapFactory.decodeResource(getResources(), R.drawable.arrow);

        mPaint.setColor(Color.BLUE); //畫筆顏色
        mPaint.setStrokeWidth(5); //畫筆粗細
        mPaint.setStyle(Paint.Style.STROKE); //畫筆風格

        mPath.addCircle(540, 972, 486, Path.Direction.CW); //一個完整的圓
        mPathMeasure.setPath(mPath, true); //要計算的path

        ValueAnimator animator = ValueAnimator.ofFloat(0, 1); //進度 0~1
        animator.setRepeatCount(ValueAnimator.INFINITE); //無限循環
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurAnimValue = (Float) animation.getAnimatedValue(); //獲得當前的進度
                invalidate();//重繪,從新執行onDraw()方法
            }
        });
        animator.setDuration(5000);
        animator.start();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        /*
         * 獲得h,w的最小的那個值;
         * >> 1 移位 跟 /2 相同;
         * 乘以0.9f,表示佔佈局的90%
         * */
        mRadius = (Math.min(h, w) >> 1) * 0.9f;

        // 中心座標
        mCenterX = w / 2;
        mCenterY = h / 2;
        Log.v("showLog",mCenterX+"  "+mCenterY+"  "+mRadius);
        postInvalidate();
        super.onSizeChanged(w, h, oldw, oldh);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float stop=mPathMeasure.getLength()*mCurAnimValue; //一個進度肯定一個截取點

        mDstPath.reset();
        mPathMeasure.getSegment(0,stop,mDstPath,true); //一點點添加

        canvas.drawPath(mDstPath,mPaint); //每次有進度更新,就繪製一小段截取

        mPathMeasure.getPosTan(stop,mPos,mTan); //得到每點的正切值和座標

        /**
         *    Math.atan2(mTan[1],mTan[0])得到tan的弧度值
         *    *180.0/Math.PI將轉化爲角度值
         * */
        float degrees=(float)(Math.atan2(mTan[1],mTan[0])*180.0/Math.PI);

        Matrix matrix=new Matrix();

        /**
         * 將圖片圍繞中心點旋轉指定角度
         * postRotate(float degrees, float px, float py)
         * degrees是角度  (px,py)是圖片中心點
         * */
        matrix.postRotate(degrees,mArrowBmp.getWidth()/2,mArrowBmp.getHeight()/2);

        /**
         * 將圖片從默認的(0,0)點移動到路徑的最前端
         * */
        matrix.postTranslate(mPos[0]-mArrowBmp.getWidth()/2,mPos[1]-mArrowBmp.getHeight()/2);

        //繪製圖片
        canvas.drawBitmap(mArrowBmp,matrix,mPaint);

    }
}

效果:

箭頭動態畫圓.gif

2.7 getMatrix()

參數類型:

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

使用方法:

//計算方位角
        Matrix matrix = new Matrix();

		//獲取位置信息
        mPathMeasure.getMatrix(stop,matrix,PathMeasure.POSITION_MATRIX_FLAG);

		//獲取切邊信息
        mPathMeasure.getMatrix(stop,matrix,PathMeasure.TANGENT_MATRIX_FLAG);

2.8 支付成功例子

public class TickView extends View {
    Path mPath, mDstPath;
    Paint mPaint;
    PathMeasure mPathMeasure;
    float mCurAnimValue;
    int mCenterX, mCenterY;
    float mRadius;

    public TickView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        mPath = new Path();
        mDstPath = new Path();
        mPaint = new Paint();
        mPathMeasure = new PathMeasure();

        mPaint.setColor(Color.BLUE); //畫筆顏色
        mPaint.setStrokeWidth(5); //畫筆粗細
        mPaint.setStyle(Paint.Style.STROKE); //畫筆風格

        mCenterX = 540;
        mCenterY = 972;
        mRadius = 486 / 2;

        /**
         * 圓
         * */
        mPath.addCircle(mCenterX, mCenterY, mRadius, Path.Direction.CW);

        /**
         * 對勾
         * */
        mPath.moveTo(mCenterX - mRadius / 2, mCenterY);
        mPath.lineTo(mCenterX, mCenterY + mRadius / 2);
        mPath.lineTo(mCenterX + mRadius / 2, mCenterY - mRadius / 3);

        mPathMeasure.setPath(mPath, false); //要計算的path

        ValueAnimator animator = ValueAnimator.ofFloat(0, 2); //進度 0~1 是圓,1~2是對勾
        animator.setRepeatCount(ValueAnimator.RESTART);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurAnimValue = (Float) animation.getAnimatedValue(); //獲得當前的進度
                invalidate();//重繪,從新執行onDraw()方法
            }
        });
        animator.setDuration(5000);
        animator.start();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        /*
         * 獲得h,w的最小的那個值;
         * >> 1 移位 跟 /2 相同;
         * 乘以0.9f,表示佔佈局的90%
         * */
        mRadius = (Math.min(h, w) >> 1) * 0.9f;

        // 中心座標
        mCenterX = w / 2;
        mCenterY = h / 2;
        Log.v("showLog", mCenterX + "  " + mCenterY + "  " + mRadius);
        postInvalidate();
        super.onSizeChanged(w, h, oldw, oldh);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mCurAnimValue < 1) {
            float stop = mPathMeasure.getLength() * mCurAnimValue;
            mPathMeasure.getSegment(0, stop, mDstPath, true);
        } else if (mCurAnimValue == 1) {
            mPathMeasure.getSegment(0, mPathMeasure.getLength(), mDstPath, true);
            mPathMeasure.nextContour();
        } else {
            float stop = mPathMeasure.getLength() * (mCurAnimValue - 1);
            mPathMeasure.getSegment(0, stop, mDstPath, true);
        }

        canvas.drawPath(mDstPath, mPaint);
    }
}

效果:
對勾動畫.gif

編程中咱們會遇到多少挫折?表放棄,沙漠盡頭必是綠洲。

相關文章
相關標籤/搜索