Android動畫雜七雜八

目錄介紹

  • 1.動畫的分類
  • 2.補間動畫爲什麼不能真正改變View的位置?而屬性動畫爲什麼能夠?屬性動畫是如何改變View的屬性?
  • 3.屬性動畫插值器和估值器的做用?插值器和估值器分別是如何更改動畫的?
  • 4.爲何屬性動畫最後會改變View的點擊事件位置而View動畫不會?
  • 5.屬性動畫內存泄漏的緣由
  • 6.具體用法

1.動畫的分類

  1. 屬性動畫: 對該類對象進行動畫操做,真正改變了對象的屬性。(ObjectAnimator,ValueAnimator)
  2. 幀動畫:由一幀一幀的圖片構建起來的動畫效果,幀動畫須要注意圖片過大會發生OOM。
  3. 補間動畫(View動畫) :對View進行平移、縮放、旋轉和透明度變化的動畫,不能真正的改變view的位置,限制比較大且種類只有四種 AlphaAnimation(透明度動畫)、RotateAnimation(旋轉動畫)、ScaleAnimation(縮放動畫)、TranslateAnimation(平移動畫)且只能做用於View上。 (應用如Activity切換動畫)

2.補間動畫爲什麼不能真正改變View的位置?而屬性動畫爲什麼能夠?屬性動畫是如何改變View的屬性?

  • View動畫改變的只是View的畫布,而沒有改變View的點擊響應區域;而屬性動畫會經過反射技術來獲取和執行屬性的get、set方法,從而改變了對象位置的屬性值
  • Animation產生的動畫數據實際並非應用在View自己的,而是應用在RenderNode或者Canvas上的(經過畫布的移動實現動畫),這就是爲何Animation不會改變View的屬性的根本所在。咱們能夠理解爲Animation只是操做的View畫布而並非改變View的位置(mLeft,mRight,mTop,mBottom)。

在View的draw()方法中:java

final Animation a = getAnimation();//是否設置了動畫
if (a != null) {
    more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
    concatMatrix = a.willChangeTransformationMatrix();
    if (concatMatrix) {
        mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
    }
    transformToApply = parent.getChildTransformation();
} else {
    if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) != 0) {
        // No longer animating: clear out old animation matrix
        mRenderNode.setAnimationMatrix(null);
        mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
    }
    if (!drawingWithRenderNode
            && (parentFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
        final Transformation t = parent.getChildTransformation();
        final boolean hasTransform = parent.getChildStaticTransformation(this, t);
        if (hasTransform) {
            final int transformType = t.getTransformationType();
            transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null;
            concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
        }
    }
}
......
if (transformToApply != null) {
    if (concatMatrix) {
        if (drawingWithRenderNode) {
            renderNode.setAnimationMatrix(transformToApply.getMatrix());
        } else {
            // Undo the scroll translation, apply the transformation matrix,
            // then redo the scroll translate to get the correct result.
            canvas.translate(-transX, -transY);
            canvas.concat(transformToApply.getMatrix());
            canvas.translate(transX, transY);
        }
        parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
    }
  ......
}

複製代碼

在View的draw方法中咱們能夠看到當咱們設置了動畫以後會生成transformToApply對象,當transformToApply不爲null的時候會進行根據動畫的參數矩陣進行View的從新繪製。重點看到Animation產生的動畫數據實際並非應用在View自己的,而是應用在RenderNode或者Canvas上的,這就是爲何Animation不會改變View的屬性的根本所在。另外一方面,咱們知道Animation僅在View被繪製的時候才能發揮本身的價值,這也是爲何補間動畫被放在Android.view包內。git

3.屬性動畫插值器和估值器的做用?插值器和估值器分別是如何更改動畫的?

  • 插值器(Interpolator):根據時間流逝的百分比計算出當前屬性值改變的百分比。肯定了動畫效果變化的模式,如勻速變化、加速變化等等。View動畫和屬性動畫都可使用。(可理解爲改變更畫的速度曲線,默認使用的是先加速再減速的插值器github

  • 經常使用的系統內置插值器:canvas

    • 線性插值器(LinearInterpolator):勻速動畫
    • 加速減速插值器(AccelerateDecelerateInterpolator):動畫兩頭慢中間快
    • 減速插值器(DecelerateInterpolator):動畫愈來愈慢

自定義插值器實現Interpolator接口。bash

public class LinearInterpolator implements Interpolator {
    public LinearInterpolator() {
    }
    public LinearInterpolator(Context context,AttributeSet attrs) {
    }
    public float getInterpolation(float input) {
        return input;
    }
}
複製代碼
  • 類型估值器(TypeEvaluator):根據當前屬性改變的百分比計算出改變後的屬性值(計算在百分之多少的時候返回什麼值)。針對於屬性動畫,View動畫不須要類型估值器。經常使用的系統內置的估值器:app

    • 整形估值器(IntEvaluator)
    • 浮點型估值器(FloatEvaluator)
    • Color屬性估值器(ArgbEvaluator)

自定義估值器實現TypeEvaluator接口。ide

public class IntEvaluator implements TypeEvaluator<Integer> {
    public Integer evaluate(float fraction,Integer startValue,Integer endValue) {
        int startInt = startValue;      start:1,end:3 那麼在0.2的時候則爲:   1+(3-1)*0.2
        return (int)(startInt + fraction * (endValue -startInt));
    }
}
複製代碼

4.爲何屬性動畫最後會改變View的點擊事件位置而View動畫不會?

如下爲例:動畫

mView.setOnTouchListener(new View.OnTouchListener() {
    int lastX, lastY;
    Toast toast = Toast.makeText(TestActivity.this, "", Toast.LENGTH_SHORT);
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int x = (int) event.getRawX();
        int y = (int) event.getRawY();
        if (event.getAction() == MotionEvent.ACTION_MOVE) {
            //Toolbar和狀態欄的高度
            int toolbarHeight = (getWindow().getDecorView().getHeight() - findViewById(R.id.root_view).getHeight());
            int widthOffset = mView.getWidth() / 2;
            int heightOffset = mView.getHeight() / 2;
            mView.setTranslationX(x - mView.getLeft() - widthOffset);
            mView.setTranslationY(y - mView.getTop() - heightOffset - toolbarHeight);
            toast.setText(String.format("left: %d, top: %d, right: %d, bottom: %d",
                    mView.getLeft(), mView.getTop(), mView.getRight(), mView.getBottom()));
            toast.show();
        }
        lastX = x;
        lastY = y;
        return true;
    }
});
複製代碼

當咱們調用了setTranslationX或setRotation等方法後其實改變的並非View的真正位置,只是對View畫布的改變。而改變View真正座標只能使用view.layout()方法,那麼爲何屬性動畫能夠經過setTranslationX等方法改變View的點擊事件區域呢?ui

由於在事件分發中當咱們去判斷View是否在手指點擊區域內的時候會去判斷View是否調用了setTranslation,setRotation,setScale這些方法,若是調用的話會調用matrix.mapPoints這個方法將View的初始座標值和通過動畫改變的座標值進行一個融合計算從而獲得最終的View座標值,以此值去判斷View是否再點擊區域內,而補間動畫並無將矩陣設置給View,那麼最終作座標融合的時候天然不會以融合改變後的座標去判斷View是否在手指點擊區域內,因此View動畫不會改變點擊區域而屬性動畫能夠。this

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
......
    Transformation transformToApply = null;
    final Animation a = getAnimation();
    if (a != null) {
        more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
        transformToApply = parent.getChildTransformation();
    }
    if (transformToApply != null) {
        if (drawingWithRenderNode) {   //屬性動畫會設置矩陣
            renderNode.setAnimationMatrix(transformToApply.getMatrix());
        } else {
            canvas.translate(-transX, -transY); //View動畫不會設置到矩陣中
            canvas.concat(transformToApply.getMatrix());
            canvas.translate(transX, transY);
        }
    }
}
複製代碼

當咱們設置的動畫播放補間動畫的時候,咱們所看到的變化,都只是臨時的。而屬性動畫呢,它所改變的東西,卻會更新到這個View所對應的矩陣中,因此當ViewGroup分派事件的時候,會正確的將當前觸摸座標,轉換成矩陣變化後的座標,這就是爲何播放補間動畫不會改變觸摸區域的緣由了。

5.屬性動畫內存泄漏的緣由

public AnimationHandler getAnimationHandler() {    return AnimationHandler.getInstance();}

private void addAnimationCallback(long delay) {
    if (!mSelfPulse) {
        return;
    }
    getAnimationHandler().addAnimationFrameCallback(this, delay);this就是ValueAnimator自身
}
複製代碼

如上當咱們新建ValueAnimator的時候會建立AnimationHandler這個靜態類,同時它會持有ValueAnimator,當進入Activity界面後若是有一些和控件綁定在一塊兒的屬性動畫在運行同時設置成了無限循環模式,退出的時候要記得cancel掉這些動畫不然會形成內存泄漏。

引用鏈關係:Activity->View->ValueAnimator->AnimationHandler(靜態類,GCROOT)

public void startAnimation(Animation animation) {
        animation.setStartTime(Animation.START_ON_FIRST_FRAME);
        setAnimation(animation);
        invalidateParentCaches();
        invalidate(true);
    }
複製代碼

6.具體用法

參考:github.com/lvzishen/LV…

裏邊包括各類類型的動畫使用,關鍵幀等的使用實例。 下邊給一個屬性動畫的使用實例

ValueAnimator mFirstPhaseAnimator;

if (mFirstPhaseAnimator == null) {
    mFirstPhaseAnimator = ValueAnimator.ofInt(0, 100);
    mFirstPhaseAnimator.setDuration(2000);
    mFirstPhaseAnimator.setInterpolator(new DecelerateInterpolator());
    mFirstPhaseAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            if (!mIsAnimationSetFinished) {
                mCurrentVal = (int) valueAnimator.getAnimatedValue(); 獲取當前的值
                if (mProcessor != null) {
                    String text = mProcessor.getText(getContext(), mCurrentVal);
                    if (TextUtils.isEmpty(text)) {
                        setValText(text);
                    }
                } else {
                    setValText(String.valueOf(mCurrentVal));
                }
            }
        }
    });
    mFirstPhaseAnimator.start();//開啓動畫
}
複製代碼
相關文章
相關標籤/搜索