android property 属性动画介绍和使用以及原理

Android 中有三种动画,分别是补间动画,帧动画,属性动画,优缺点如下: 

补间动画用法简单,支持4种动画效果,不用担心内存泄漏,OOM等问题。缺点就是动画效果较少,有时不能满足需求,而且补间动画是对view的影像做动画,并没有真正改变view的状态,原来的view位置大小状态都是保持不变的。

帧动画用法简单,多张图片一帧一帧的播放组成动画。缺点是图片数量过多过大,容易出现OOM。

属性动画优化了补间动画的缺点,可以实现多样式的动画,以及改变view的状态。缺点android3.0以下会有兼容问题,无限循环的动画需要在Activity退出时及时停止,避免出现内存泄漏。

上面简单的介绍了Android中的动画,下面就来介绍以下属性动画的使用

属性动画可以对任何对象做动画,也可以没有对象,同时动画效果也得到加强。属性动画中有ValueAnimator、ObjectAnimator、AnimatorSet等概念。

ObjectAnimator的使用

需求:将Button x坐标在2秒内向右移动100 px,然后右回到原点。

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mButton,"translationX",0f,100f,0f);
objectAnimator.setDuration(2000);
objectAnimator.start();

AnimatorSet的使用

需求1:Button在移动时,同时进行缩小还原,旋转,渐变还原的过程。

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(
        ObjectAnimator.ofFloat(btnSet,"translationX",0f,300,0f),
        ObjectAnimator.ofFloat(btnSet,"rotationX",0f,360f),
        ObjectAnimator.ofFloat(btnSet,"scaleX",1,0.5f,1f),
        ObjectAnimator.ofFloat(btnSet,"alpha",1,0.5f,1f)
);
animatorSet.setDuration(4000).start();

从源码得知,AnimatorSet 部分api调用使用了Builder设计模式,如果想设置动画先后执行的顺序,可以先调用play,然后根据需求调用下面的方法。

  • after(Animator anim) :将现有动画插入到传入的动画之后执行
  • after(long delay) :将现有动画延迟指定毫秒后执行
  • before(Animator anim): 将现有动画插入到传入的动画之前执行
  • with(Animator anim) :将现有动画和传入的动画同时执行
//沿x轴放大
ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(btnSet, "scaleX", 1f, 2f, 1f);
//沿y轴放大
ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(btnSet, "scaleY", 1f, 2f, 1f);
//移动
ObjectAnimator translationXAnimator = ObjectAnimator.ofFloat(btnSet, "translationX", 0f, 200f, 0f);
//透明动画
ObjectAnimator animator = ObjectAnimator.ofFloat(btnSet, "alpha", 1f, 0f, 1f);
AnimatorSet set = new AnimatorSet();
//同时沿X,Y轴放大,且改变透明度,然后移动
set.play(scaleXAnimator).with(scaleYAnimator).with(animator).before(translationXAnimator);
//都设置3s,也可以为每个单独设置
set.setDuration(3000);
set.start();

需求2:动画效果改变Button的宽度,在2秒内宽度先变成0,然后在还原。

ViewWrapper wrapper = new ViewWrapper(btnWarpper);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(wrapper,"Width",btnWarpper.getWidth(),0f,btnWarpper.getWidth());
objectAnimator.setDuration(2000);
objectAnimator.start();
public class ViewWrapper {
    public View viewWrapper;
    public ViewWrapper(View viewWrapper){
        this.viewWrapper = viewWrapper;
    }

    public void setWidth(float width){
        viewWrapper.getLayoutParams().width = (int) width;
        viewWrapper.requestLayout();
    }

    public float getWidth(){
        return viewWrapper.getLayoutParams().width;
    }
}

需求3:实现一个球自由落体运动

这部分我们需要了解TimeInterpolator 时间插值器和TypeEvaluator估值器,插值器已经有很多实现,一般不需要重写,它的作用是根据时间流逝的百分比计算出属性改变的百分比,从而在估值器中计算出当前时间估算的属性值。

ObjectAnimator objectAnimator = new ObjectAnimator();
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.setObjectValues(new PointF(0,0));
objectAnimator.setEvaluator(new TypeEvaluator<PointF>() {
    @Override
    public PointF evaluate(float v, PointF pointF, PointF t1) {
        PointF point = new PointF();
        point.x = 200*v*2;
        point.y = 0.5f*200*v*2*v*2;
        return point;
    }
});

objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator valueAnimator) {
        PointF pointF = (PointF) valueAnimator.getAnimatedValue();
        if(y==0){
            y = circleView.getY();
        }
        circleView.setX(pointF.x);
        circleView.setY(pointF.y+y);
    }
});

objectAnimator.setDuration(2000);
objectAnimator.start();

ValueAnimator的使用

使用valueAnimator实现需求1

ValueAnimator animator = ValueAnimator.ofFloat(0f,300,0f);
animator.setDuration(2000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator valueAnimator) {
        btnValueAnimator.setTranslationX((Float) valueAnimator.getAnimatedValue());
    }
});
animator.start();

以上是属性动画的一些基本用法。当然属性动画也可以使用xml文件实现,暂时就不介绍了。下面介绍一下属性动画的原理。

属性动画要求作用的对象提供该属性的set、get方法,属性动画根据你传递的该属性的初始值和最终值,以动画效果多次调用set方法,每次传递给set方法的值是不一样的,随着时间的推移越来越接近最终值,最后等于最终值。下面根据源码来解析属性动画,首先找到一个入口,就用ObjectAnimator.start()方法开始。

public void start() {
    AnimationHandler.getInstance().autoCancelBasedOn(this);
    if (DBG) {
        Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
        for (int i = 0; i < mValues.length; ++i) {
            PropertyValuesHolder pvh = mValues[i];
            Log.d(LOG_TAG, "   Values[" + i + "]: " +
                pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
                pvh.mKeyframes.getValue(1));
        }
    }
    super.start();
}

可以看到调用的是super.start(),也就是ValueAnimator的start()方法。

private void start(boolean playBackwards) {
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }
    mReversing = playBackwards;
    mSelfPulse = !mSuppressSelfPulseRequested;
    // Special case: reversing from seek-to-0 should act as if not seeked at all.
    if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
        if (mRepeatCount == INFINITE) {
            // Calculate the fraction of the current iteration.
            float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
            mSeekFraction = 1 - fraction;
        } else {
            mSeekFraction = 1 + mRepeatCount - mSeekFraction;
        }
    }
    mStarted = true;
    mPaused = false;
    mRunning = false;
    mAnimationEndRequested = false;
    // Resets mLastFrameTime when start() is called, so that if the animation was running,
    // calling start() would put the animation in the
    // started-but-not-yet-reached-the-first-frame phase.
    mLastFrameTime = -1;
    mFirstFrameTime = -1;
    mStartTime = -1;
    addAnimationCallback(0);

    if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
        // If there's no start delay, init the animation and notify start listeners right away
        // to be consistent with the previous behavior. Otherwise, postpone this until the first
        // frame after the start delay.
        startAnimation();
        if (mSeekFraction == -1) {
            // No seek, start at play time 0. Note that the reason we are not using fraction 0
            // is because for animations with 0 duration, we want to be consistent with pre-N
            // behavior: skip to the final value immediately.
            setCurrentPlayTime(0);
        } else {
            setCurrentFraction(mSeekFraction);
        }
    }
}

可以看出属性动画必须执行在有looper的线程中,往下执行到了setCurrentFraction(mSeekFraction),然后在往下就是animateValue方法

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);
    }
    if (mUpdateListeners != null) {
        int numListeners = mUpdateListeners.size();
        for (int i = 0; i < numListeners; ++i) {
            mUpdateListeners.get(i).onAnimationUpdate(this);
        }
    }
}

上述代码中的calculateValue方法就是在计算每一帧动画对应的属性值。对象的get跟set方法,在动画初始化时,没有初始值,通过反射调用get方法,当动画的每一帧到来的时候通过反射调用对象的set方法设置属性值。

发布了12 篇原创文章 · 获赞 6 · 访问量 923

猜你喜欢

转载自blog.csdn.net/qq_30867605/article/details/87252442