阅读 63

关于Android中动画探究(二)属性动画

属性动画

上一节我们看过了Animation视图动画的原理浅析,了解到Animation的原理是通过ViewRootImpl来监听下一个屏幕刷新信号,然后DecorView对View树重新绘制并且顺带将动画绘制出来。

关于属性动画有两个常用类ValueAnimator和它的子类ObjectAnimator,还有view.animate()会得到一个ViewPropertyAnimator对象,需要自己重写其updateListener做动画效果。

基本使用

ValueAnimator本身是没有动画效果的,需要用户自定义listener,获取到计算好的animatedValue进行赋值给view。

val animator1 = ValueAnimator.ofFloat(0f, 1.5f)
animator1.duration = 1000
animator1.repeatCount = 3
animator1.repeatMode = ValueAnimator.REVERSE
animator1.addUpdateListener {
    val data = it.animatedValue as Float
    testAnimal.scaleX = data
    testAnimal.scaleY = data
}
复制代码

ObjectAnimator继承于ValueAnimator,提供了对改变View属性的动画支持。官网解释为需要适合的get/set属性方法去设置这个值,也就是这个属性在View类是存在的,如果不存在也可以自己去写属性的get/set方法。需要注意的是ObjectAnimator也有ofFloat、ofInt、ofArgb等方法,需要确定属性的返回值是否一致,而且属性名要骆驼命名法。

// translationY、scaleX、scaleY、alpha等是View类的基本属性
val animator1 = ObjectAnimator.ofFloat(testAnimal, "translationY", 0f, -120f)
val animator2 = ObjectAnimator.ofFloat(testAnimal, "scaleX", 0f, -120f)

class View {
    @ViewDebug.ExportedProperty(category = "drawing")
    public float getTranslationY() {
        return mRenderNode.getTranslationY();
    }
    public void setTranslationY(float translationY) {
        //...
    }
    //scaleX同上
}
复制代码

进阶用法

(1)自定义属性动画

View中width和height是没有set方法的,但是动画需要改变View的宽度(scale是拉伸View),这时候需要包装属性的set方法,或者使用ValueAnimator重写动画的updateListener。

// 第一种 包装属性
val animator1 = ObjectAnimator.ofInt(ViewWrapper(btn_start2), "width", 120, 420)
animator1.duration = 1000
private inner class ViewWrapper(val target: View) {
        fun getWidth(): Int {
            return target.layoutParams.width
        }
        fun setWidth(width: Int) {
            target.layoutParams.width = width
            target.requestLayout()
        }
}

// 第二种 ValueAnimator重写动画
// 可以使用估值器计算值的百分比,在之后的动画原理会讲到TypeEvaluator
val animator1 = ValueAnimator.ofInt(120, 420)
animator1.duration = 1000
animator1.addUpdateListener {
    val data = it.animatedValue as Int
    testAnimal.layoutParams.width = data
    testAnimal.requestLayout()
}
复制代码
(2)AnimatorSet组合动画

Animator同样会有组合动画,并且可对多个View进行动画,设置动画的执行顺序,更能操控动画的执行过程。

// Node包装了动画的节点,对动画进行分类成父动画、子动画、兄弟动画
animatorSet = AnimatorSet()
val animator1 = ObjectAnimator.ofInt(ViewWrapper(view2), "width", 120, 420)
val animator3 = ValueAnimator.ofFloat(0f, 1.5f)
animator3.addUpdateListener {
    val data = it.animatedValue as Float
    view1.scaleX = data
    view1.scaleY = data
}
animatorSet.duration = 1000
animatorSet.play(animator1).after(animator3)


/** class AnimatorSet */
// 同时执行的动画
public Builder with(Animator anim) {
    Node node = getNodeForAnimation(anim);
    mCurrentNode.addSibling(node);
    return this;
}

// 之前执行的动画
public Builder before(Animator anim) {
    Node node = getNodeForAnimation(anim);
    mCurrentNode.addChild(node);
    return this;
}

// 之后执行的动画
public Builder after(Animator anim) {
    Node node = getNodeForAnimation(anim);
    mCurrentNode.addParent(node);
    return this;
}
复制代码

Animator动画原理解析

了解到ObjectAnimator继承于ValueAnimator,我们直接从ValueAnimator看。

private void start(boolean playBackwards) {
    if (Looper.myLooper() == null) { // 保证线程与Looper关联
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }
    mReversing = playBackwards; //playBackwards传入参数为true或false,表示是否动画反向播放
    mSelfPulse = !mSuppressSelfPulseRequested;
    if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
        if (mRepeatCount == INFINITE) {
            float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
            mSeekFraction = 1 - fraction;
        } else {
            mSeekFraction = 1 + mRepeatCount - mSeekFraction;
        }
    }
    mStarted = true;
    mPaused = false;
    mRunning = false;
    mAnimationEndRequested = false;
       
    mLastFrameTime = -1;
    mFirstFrameTime = -1;
    mStartTime = -1;
    // 添加Animation回调
    addAnimationCallback(0);

    if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
        startAnimation(); // 做一些动画的初始化工作
        if (mSeekFraction == -1) {
            setCurrentPlayTime(0); 
        } else {
            setCurrentFraction(mSeekFraction);
        }
    }
}

private void addAnimationCallback(long delay) {
    if (!mSelfPulse) {
        return;
    }
    // 注意这里传入的是this,意味着会在ValueAnimator中回调
    getAnimationHandler().addAnimationFrameCallback(this, delay);
}
复制代码

AnimationHandler用于确保动画使用计算后的同一值,保证动画的同步执行

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            //记住这里哈  等下还要回来的
            doAnimationFrame(getProvider().getFrameTime());
            if (mAnimationCallbacks.size() > 0) {
                getProvider().postFrameCallback(this);
            }
        }
    };

/** 注册callBack以获取下一帧延时回调
*/
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
    // 当前的mAnimationCallbacks为空的话(第一次执行动画)
    if (mAnimationCallbacks.size() == 0) {
        getProvider().postFrameCallback(mFrameCallback);
    }
    if (!mAnimationCallbacks.contains(callback)) {
        mAnimationCallbacks.add(callback);
    }
    if (delay > 0) {
        mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
    }
}

private AnimationFrameCallbackProvider getProvider() {
    if (mProvider == null) {
        mProvider = new MyFrameCallbackProvider();
    }
    return mProvider;
}

/** 
* 从这里可以看到Choreographer类,这个类我们在Animation和View的绘制过程中都可以看到。
* 这个类做了什么?
* Choregrapher类主要来控制同步处理输入、动画、绘制三个UI操作,也就是当屏幕的每一次刷新信号到来时要去处理某
* 些事
*    mFrameInfo.markInputHandlingStart();
*    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
*    mFrameInfo.markAnimationsStart();
*    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
*    mFrameInfo.markPerformTraversalsStart();
*    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
*    // 遍历上述事件完成,提交操作,用于修正动画的启动时间
*    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
*/
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {

    final Choreographer mChoreographer = Choreographer.getInstance();

    @Override
    public void postFrameCallback(Choreographer.FrameCallback callback) {
        // 发送祯回调通知,以在下一祯时回调
        mChoreographer.postFrameCallback(callback);
    }
    // ...
}

/** 回到上面说的地方,下一帧通知过来了,正式去处理动画内容
*/
private void doAnimationFrame(long frameTime) {
    long currentTime = SystemClock.uptimeMillis();
    final int size = mAnimationCallbacks.size();
    for (int i = 0; i < size; i++) {
        // 这里就是上面所说ValueAnimator添加callback,传入this
        final AnimationFrameCallback callback = mAnimationCallbacks.get(i); 
        if (callback == null) {
            continue;
        }
        if (isCallbackDue(callback, currentTime)) { // 判断动画是否过期
            callback.doAnimationFrame(frameTime); // 调用ValueAnimator的doAnimationFrame回调 
            if (mCommitCallbacks.contains(callback)) {
                getProvider().postCommitCallback(new Runnable() {
                    @Override
                    public void run() {
                        commitAnimationFrame(callback, getProvider().getFrameTime());
                    }
                });
            }
        }
    }
    cleanUpList(); // 动画执行话,清除callBack列表
}

复制代码

ValueAnimator类中真正执行动画的过程,与Animation一致,都是通过估值器、插值器等计算出百分比值等,在动画的Update中去绘制。

 public final boolean doAnimationFrame(long frameTime) {
	 // ..省略代码,概括为处理第一帧的时间	
     //动画中第一帧时间可能会早于当前时间,这里主要修正当前时间,避免以负值作为帧的显示
     final long currentTime = Math.max(frameTime, mStartTime);
     boolean finished = animateBasedOnTime(currentTime); // 计算,显示下一帧动画
    
     if (finished) {
         endAnimation();
     }
     return finished;
 }

 /** 为动画处理单个帧操作,并且返回值标识动画是否结束
 */ 
 boolean animateBasedOnTime(long currentTime) {
        boolean done = false;
        if (mRunning) {
            //省略代码  计算动画进度,保证动画进度处于0~1之间,如果有设置repeatCount属性,则这个值是会超过1的,所以需要经过计算转换为0~1
            animateValue(currentIterationFraction); // 对应Animation的applyTransformation()方法
        }
        return done;
    }

 /** 清除Animation的回调,回复lastFramTime、mStarted、mRunning等属性值
 */
 private void endAnimation() {
     if (mAnimationEndRequested) {
         return;
     }
     removeAnimationCallback();
     // 省略大法
 }

void animateValue(float fraction) {
    fraction = mInterpolator.getInterpolation(fraction); // 插值器使用在这里,计算出真实百分比进度 
    mCurrentFraction = fraction;
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        mValues[i].calculateValue(fraction); // 设置View属性的地方
    }
    if (mUpdateListeners != null) { // 如果Animator有添加updateListener方法,则操作地方在这里
        int numListeners = mUpdateListeners.size();
        for (int i = 0; i < numListeners; ++i) {
            mUpdateListeners.get(i).onAnimationUpdate(this);
        }
    }
}
复制代码

关于mValues[i].calculateValue(fraction)的代码,等有时间再去深究其中代码,这里已交代完了Animator的执行流程,下图是Animator的调用流程图(手动盗图)

属性动画的优点

  1. 动画的执行整体可控制,可以添加listener进行控制动画过程
  2. 动画不限于自有属性,可自定义属性进行动画实现
  3. 可以改变View的属性,不仅限于只绘制其位置
关注下面的标签,发现更多相似文章
评论