4.Android 高级UI<三>之属性动画原理 (2024精华版)

目录:

1.属性动画原理

2.差值器和估值器

3.动画的常用操作,组合

  1. 补间动画和属性动画区别,几种动画的区别

  2. 具体的案例

jietu-1713786890400.jpg

1.属性动画原理

作用: 属性动画可以改变view的属性和View里面的某个对象操作

其实就是利用插值器和估值器,来计算出各个时刻View的属性,然后通过改变View的属性来,实现View的动画效果(set和get方法改变view的属性)。

到这里,属性动画的整个过程以及原理都分析完了。下面来总结一下这个过程:

(1) 动画是由许多的关键帧组成的,这是一个动画能够动起来的最基本的原理。
(2) 属性动画的主要组成是 PropertyValuesHolder,而 PropertyValuesHolder 又封装了关键帧。
(3) 动画开始后,其监听了 Choreographer 的 vsync,使得其可以不断地调用 doAnimationFrame() 来驱动动画执行每一个关键帧。
(4) 每一次的 doAnimationFrame() 调用都会去计算时间插值,而通过时间插值器计算得到 fraction 又会传给估值器,使得估值器可以计算出属性的当前值。
(5) 最后再通过 PropertyValuesHolder 所记录下的 Setter 方法,以反射的方式来修改目标属性的值。当属性值一帧一帧的改变后,形成连续后,便是我们所见到的动画。

比较好的总结:
  • 在调用start()方法或,属性动画会执行一些初始化工作,并通知动画流程开始;

  • 然后通过AnimationHandler将动画回调注册进Choreographer的工作队列中,一次监听屏幕刷新,直至动画结束(这里是每帧处理一次,当回调列表数量大于0时就继续注册监听)

  • View的属性动画:

  • 1.插值器:作用是根据时间的流逝的百分比来计算属性改变的百分比(比如时间擦值器:TimeInterPolater)

  • 2.估值器:在1的基础上由这个东西来计算出属性到底变化了多少数值的类

问题: 属性动画更新时会回调onDraw吗?

属性动画更新时会回调onDraw吗?不会,因为它内部是通过AnimationHandler中的Choreographer机制来实现的更新

其核心方法为ValueAnimator.doAnimationFrame:

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
 @Override
 public void doFrame(long frameTimeNanos) {
   //帧回调,在此方法里通知给所有的动画
   doAnimationFrame(getProvider().getFrameTime());
   if (mAnimationCallbacks.size() > 0) {
     getProvider().postFrameCallback(this);
   }
 }
};

public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
 //仅当有动画时才向Provider注册帧回调
 if (mAnimationCallbacks.size() == 0) {
   //默认为MyFrameCallbackProvider,实现是通过持有的Choreographer进行postFrameCallback
   getProvider().postFrameCallback(mFrameCallback);
 }
 if (!mAnimationCallbacks.contains(callback)) {
   mAnimationCallbacks.add(callback);
 }

 if (delay > 0) {
   mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
 }
}

public final boolean doAnimationFrame(long frameTime) {
 ...
 if (mPaused) {
   mPauseTime = frameTime;
   //如果暂停,移除callback,在调用resume时再次注册
   removeAnimationCallback();
   return false;
 } else if (mResumed) {
   mResumed = false;
   if (mPauseTime > 0) {
     mStartTime += (frameTime - mPauseTime);
   }
 }
 if (!mRunning) {
   if (mStartTime > frameTime && mSeekFraction == -1) {
     return false;
   } else {
     //在延迟开启的情况下,此时实际开始动画
     mRunning = true;
     //初始化动画
     startAnimation();
   }
 }

 if (mLastFrameTime < 0) {
   if (mSeekFraction >= 0) {
     long seekTime = (long) (getScaledDuration() * mSeekFraction);
     mStartTime = frameTime - seekTime;
     mSeekFraction = -1;
   }
   mStartTimeCommitted = false;
 }
 mLastFrameTime = 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) {
   ...
   //实际计算动画值
   animateValue(currentIterationFraction);
 }
 return done;
}

2.差值器和估值器

2.1 Evaluator其实就是一个转换器,他能把小数进度转换成对应的数值位置

估值器原理:系统的一些估值器

@RestrictTo(LIBRARY_GROUP_PREFIX)
public class ArgbEvaluator implements TypeEvaluator {
    private static final ArgbEvaluator sInstance = new ArgbEvaluator();

    /**
     * Returns an instance of <code>ArgbEvaluator</code> that may be used in
     * {@link ValueAnimator#setEvaluator(TypeEvaluator)}. The same instance may
     * be used in multiple <code>Animator</code>s because it holds no state.
     *
     * @return An instance of <code>ArgbEvalutor</code>.
     */
    public static ArgbEvaluator getInstance() {
        return sInstance;
    }

    /**
     * This function returns the calculated in-between value for a color
     * given integers that represent the start and end values in the four
     * bytes of the 32-bit int. Each channel is separately linearly interpolated
     * and the resulting calculated values are recombined into the return value.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue A 32-bit int value representing colors in the
     *                   separate bytes of the parameter
     * @param endValue   A 32-bit int value representing colors in the
     *                   separate bytes of the parameter
     * @return A value that is calculated to be the linearly interpolated
     * result, derived by separating the start and end values into separate
     * color channels and interpolating each one separately, recombining the
     * resulting values in the same way.
     */
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        int startInt = (Integer) startValue;
        float startA = ((startInt >> 24) & 0xff) / 255.0f;
        float startR = ((startInt >> 16) & 0xff) / 255.0f;
        float startG = ((startInt >> 8) & 0xff) / 255.0f;
        float startB = ((startInt) & 0xff) / 255.0f;

        int endInt = (Integer) endValue;
        float endA = ((endInt >> 24) & 0xff) / 255.0f;
        float endR = ((endInt >> 16) & 0xff) / 255.0f;
        float endG = ((endInt >> 8) & 0xff) / 255.0f;
        float endB = ((endInt) & 0xff) / 255.0f;

        // convert from sRGB to linear
        startR = (float) Math.pow(startR, 2.2);
        startG = (float) Math.pow(startG, 2.2);
        startB = (float) Math.pow(startB, 2.2);

        endR = (float) Math.pow(endR, 2.2);
        endG = (float) Math.pow(endG, 2.2);
        endB = (float) Math.pow(endB, 2.2);

        // compute the interpolated color in linear space
        float a = startA + fraction * (endA - startA);
        float r = startR + fraction * (endR - startR);
        float g = startG + fraction * (endG - startG);
        float b = startB + fraction * (endB - startB);

        // convert back to sRGB in the [0..255] range
        a = a * 255.0f;
        r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;
        g = (float) Math.pow(g, 1.0 / 2.2) * 255.0f;
        b = (float) Math.pow(b, 1.0 / 2.2) * 255.0f;

        return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b);
    }
}
2.2 Android 四种特殊Interpolator源码解析 动画原理解析 插值器的意义其实就相当于物理公式中的加速度参数

基类最重要的方法:getInterpolation()

public interface TimeInterpolator {
    float getInterpolation(float input);
}

3.动画的常用操作,组合

3.1 常用的类

1).ValueAnimator,ValueAnimator有个缺点,就是只能对数值对动画计算

2). ObjectAnimator: 为了能让动画直接与对应控件相关联,以使我们从监听动画过程中解放出来,谷歌的开发人员在ValueAnimator的基础上,又派生了一个类ObjectAnimator;

3). AnimatorSet: 用于把不同的动画进行组合

4).playTogether:表示将所有动画一起播放

4. 补间动画和属性动画区别,几种动画的区别
4.1 这个是补间动画的原理:通过矩阵的变换。

工作原理:在一定时间间隔内,通过不断对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在该属性上的动画效果。

ValueAnimator:通过不断控制值的变化(初始值->结束值),将值手动赋值给对象的属性,再不断调用View 的invalidate()方法,去不断onDraw 重绘

view,达到动画的效果。

Animation框架定义了透明度,旋转,缩放和位移几种常见的动画,而且控制的是整个View,实现原理是每次绘制视图时View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation值,然后调用canvas.concat(transformToApply.getMatrix()),通过矩阵运算完成动画帧,如果动画没有完成,继续调用invalidate()函数,启动下次绘制来驱动动画,动画过程中的帧之间间隙时间是绘制函数所消耗的时间,可能会导致动画消耗比较多的CPU资源,最重要的是,动画改变的只是显示,并不能相应事件。

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
  ...
  //可以理解为是否还有动画的标志位
  boolean more = false;
  ...
  //动画最终用到的transform
  Transformation transformToApply = null;
  ...
  //获取view的动画
  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();
  }
  ...
  //如果apply不为空(有动画时赋值),则将动画变换矩阵运用到canvas上
  if (transformToApply != null) {
    if (concatMatrix) {
      if (drawingWithRenderNode) {
        renderNode.setAnimationMatrix(transformToApply.getMatrix());
      } else {
        canvas.translate(-transX, -transY);
        canvas.concat(transformToApply.getMatrix());
        canvas.translate(transX, transY);
      }
      parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
    }

    float transformAlpha = transformToApply.getAlpha();
    if (transformAlpha < 1) {
      alpha *= transformAlpha;
      parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
    }
  }
  ...
  return more;
}

getTransformation内部会计算动画的进度并将view传递的transformation进行设值,最终返回给view.draw方法应用到canvas画布。

4.2 补间动画还有一个致命的缺陷,

就是它只是改变了View的显示效果而已,而不会真正去改变View的属性。

问题: 能否兼容点击事件?

老版本的不能捕捉点击事件,其他的动画,只是一个影像而已!

4.3 其他几种实现动画的方式

1). setX跟setTranslationX区别

5. 具体的案例
public class DouYinLayout extends RelativeLayout {

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

    public DouYinLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        init();
    }

    public DouYinLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private CircleView circleViewRedLeft, circleViewBlueRight;

    private float translationX = 30;

    /**
     * 把2个点添加进去
     */
    private void init() {
        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        layoutParams.addRule(CENTER_IN_PARENT);//设置位置居中
        circleViewRedLeft = new CircleView(getContext());
        circleViewBlueRight = new CircleView(getContext());
        addView(circleViewRedLeft, layoutParams);
        addView(circleViewBlueRight, layoutParams);

        circleViewRedLeft.changeCorlor(Color.RED);
        circleViewBlueRight.changeCorlor(Color.BLUE);

        oneAnimation();
    }

    private void oneAnimation() {
        //动起来:中间的先向左移动。然后向右移动。然后不停的循环
        ObjectAnimator animatorleft = ObjectAnimator.ofFloat(circleViewRedLeft, "translationX", 0, -translationX);//left move
        ObjectAnimator animatorRight = ObjectAnimator.ofFloat(circleViewBlueRight, "translationX", 0, translationX);
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(animatorleft, animatorRight);
        animatorSet.setDuration(300);//
        animatorSet.start();

        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                circleViewRedLeft.changeCorlor(Color.BLUE);
                circleViewBlueRight.changeCorlor(Color.RED);
                twoAnimation();
            }
        });
    }

    private void twoAnimation() {
        ObjectAnimator animatorleft = ObjectAnimator.ofFloat(circleViewRedLeft, "translationX", -translationX, 0);//left move
        ObjectAnimator animatorRight = ObjectAnimator.ofFloat(circleViewBlueRight, "translationX", translationX, 0);
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(animatorleft, animatorRight);
        animatorSet.setDuration(300);//
        animatorSet.start();

        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                circleViewRedLeft.changeCorlor(Color.RED);
                circleViewBlueRight.changeCorlor(Color.BLUE);
                oneAnimation();
            }
        });
    }


}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容