自定义UI 属性动画

Posted Notzuonotdied

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义UI 属性动画相关的知识,希望对你有一定的参考价值。

系列文章目录

  1. 自定义UI 基础知识
  2. 自定义UI 绘制饼图
  3. 自定义UI 圆形头像
  4. 自定义UI 自制表盘
  5. 自定义UI 简易图文混排
  6. 自定义UI 使用Camera做三维变换
  7. 自定义UI 属性动画


我觉得 android Developer 官方的文档写得特别的详细,而且还是中文文档,建议大家直接观看官网的文章:属性动画概览。如果你想图文结合的看,那你可以看看本篇文章。<(* ̄▽ ̄*)/

前言

这系列的文章主要是基于扔物线的HenCoderPlus课程的源码来分析学习。


  • 扔物线课程源码:(这一块涉及的代码比较多)
  • Android官方文档:
    • 自定义绘制
    • 属性动画概览:属性动画系统是一个强健的框架,用于为几乎任何内容添加动画效果。您可以定义一个随时间更改任何对象属性的动画,无论其是否绘制到屏幕上。属性动画会在指定时长内更改属性(对象中的字段)的值。要添加动画效果,请指定要添加动画效果的对象属性,例如对象在屏幕上的位置、动画效果持续多长时间以及要在哪些值之间添加动画效果。

这一篇文章主要介绍的是属性动画,更多细节请见以下文章:

如果大家有“财力”,建议支持下扔物线。大佬给我们提供了很详细的学习资源。

属性动画和视图动画的区别

该部分内容摘录自:Android Developer 官网之属性动画与视图动画的区别

  视图动画系统仅提供为 View 对象添加动画效果的功能,因此,如果您想为非 对象添加动画效果,则必须实现自己的代码才能做到。视图动画系统也存在一些限制,因为它仅公开 对象的部分方面来供您添加动画效果;例如,您可以对视图的缩放和旋转添加动画效果,但无法对背景颜色这样做。

  视图动画系统的另一个缺点是它只会在绘制视图的位置进行修改,而不会修改实际的视图本身。例如,如果您为某个按钮添加了动画效果,使其可以在屏幕上移动,该按钮会正确绘制,但能够点击按钮的实际位置并不会更改,因此您必须通过实现自己的逻辑来处理此事件。

  有了属性动画系统,您就可以完全摆脱这些束缚,还可以为任何对象(视图和非视图)的任何属性添加动画效果,并且实际修改的是对象本身。属性动画系统在执行动画方面也更为强健。概括地讲,您可以为要添加动画效果的属性(例如颜色、位置或大小)分配 Animator,还可以定义动画的各个方面,例如多个 Animator 的插值和同步。

  不过,视图动画系统的设置需要的时间较短,需要编写的代码也较少。如果视图动画可以完成您需要执行的所有操作,或者现有代码已按照您需要的方式运行,则无需使用属性动画系统。在某些用例中,也可以针对不同的情况同时使用这两种动画系统。

android.view.View#animate

相关源码:

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {

    /**
     * This method returns a ViewPropertyAnimator object, which can be used to animate
     * specific properties on this View.
     *
     * @return ViewPropertyAnimator The ViewPropertyAnimator associated with this View.
     */
    public ViewPropertyAnimator animate() {
        if (mAnimator == null) {
            mAnimator = new ViewPropertyAnimator(this);
        }
        return mAnimator;
    }
}

android.view.View#animate 方法返回了一个属性动画 ViewPropertyAnimator 的对象。使用该对象可以定义一系列的动画过程。话不多说,直接看下效果。

使用示例

属性动画定义

public class MainActivity extends AppCompatActivity {

    CircleView view;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        view = findViewById(R.id.view);
        view.animate()
        		// 向x轴正方向移动100dp
                .translationX(Utils.dpToPixel(100))
                // 向y轴正方向移动100dp
                .translationY(100)
                // 旋转180°(看不出来...)
                .rotation(180)
                // 取值范围[0, 1],透明度50%
                .alpha(0.5f)
                // 延迟5s执行
                .setStartDelay(5000)
                .start();
    }
}

View 定义

public class CircleView extends View {
    // 抗锯齿
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    // 定义下View的宽高
    private float width;
    private float height;
	// 圆的半径长度
    private final float radius = Utils.dpToPixel(50);

    public CircleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    {
        paint.setColor(Color.RED);
        width = (float) (getWidth() / 2);
        height = (float) (getHeight() / 2);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = (float) (getWidth() / 2);
        height = (float) (getHeight() / 2);
    }

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

        canvas.drawCircle(width, height, radius, paint);
    }
}

android.animation.ObjectAnimator

这个类的使用建议结合官方文档享用:使用 ObjectAnimator 添加动画效果

ObjectAnimatorValueAnimator 的子类,它融合了 ValueAnimator 的计时引擎和值计算以及为目标对象的命名属性添加动画效果这一功能。这可以极大地简化为任何对象添加动画效果的过程,因为动画属性会自动更新,因此您也无需再实现 ValueAnimator.AnimatorUpdateListener 了。

使用示例

属性动画定义

public class MainActivity extends AppCompatActivity {

    CircleView view;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        view = findViewById(R.id.view);

        ObjectAnimator animator = ObjectAnimator.ofFloat(
                view,
                "radius",
                Utils.dpToPixel(150));
        animator.setStartDelay(5000);
        animator.start();
    }
}

注意:

  • ObjectAnimator.ofFloat 的第二个参数定义的是 CircleViewradius 属性。该方法的调用需要我们根据Java的命名规则,实现对应的 settergetter 方法,否则,将会报错。
    • com.hencoder.a08_animation.view.CircleView#getRadius
    • com.hencoder.a08_animation.view.CircleView#setRadius
  • 也可以参考官方文章的说明:使用 ObjectAnimator 添加动画效果

View 定义

这里仅仅贴出来增量的代码,完整的代码定义请见上一章节。

public class CircleView extends View {
    // 圆的半径
    private float radius = Utils.dpToPixel(50);

    public float getRadius() {
        return radius;
    }

    public void setRadius(float radius) {
        this.radius = radius;
        // 将当前View标记为失效,在下一次绘制周期中重新绘制该View
        invalidate();
    }
}

注意:

  • setter 方法中,我们需要在属性变化后,调用 android.view.View#invalidate() 方法,确保修改的属性生效。
public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {

    /**
     * Invalidate the whole view. If the view is visible,
     * {@link #onDraw(android.graphics.Canvas)} will be called at some point in
     * the future.
     * <p>
     * This must be called from a UI thread. To call from a non-UI thread, call
     * {@link #postInvalidate()}.
     */
    public void invalidate() {
        invalidate(true);
    }
}

多动画串行示例

动画顺序:(下面三个动画分先后,串行处理

  1. 下半部分向上翻起45°
  2. 图片以对角线交点为中心,旋转360
    1. 该部分不明白的话,请先阅读一遍自定义UI 使用Camera做三维变换
  3. 上半部分向下翻起45°
public class MainActivity extends AppCompatActivity {

    FancyFlipView view;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        view = findViewById(R.id.view);
        // 底部向上翻起45°
        ObjectAnimator bottomFlipAnimator = ObjectAnimator.ofFloat(
                view,
                "bottomFlip",
                45);
        bottomFlipAnimator.setDuration(1500);

        ObjectAnimator flipRotationAnimator = ObjectAnimator.ofFloat(
                view,
                "flipRotation",
                360);
        flipRotationAnimator.setDuration(1500);

		// 顶部向下翻起45°
        ObjectAnimator topFlipAnimator = ObjectAnimator.ofFloat(
                view,
                "topFlip",
                -45);
        topFlipAnimator.setDuration(1500);

        AnimatorSet animatorSet = new AnimatorSet();
        // 串行执行
        animatorSet.playSequentially(
                bottomFlipAnimator,
                flipRotationAnimator,
                topFlipAnimator
        );
        animatorSet.setStartDelay(1000);
        animatorSet.start();
    }
}

以下内容摘录自:使用 AnimatorSet 编排多个动画

AnimatorSet:在许多情况下,您需要根据一个动画开始或结束的时间来播放另一个动画。借助 Android 系统,您可以将动画捆绑到一个 AnimatorSet中,以便指定是同时播放动画、按顺序播放还是在指定的延迟时间后播放。您还可以相互嵌套 AnimatorSet 对象。


多动画串行的更多知识点可见扔物线官方博文介绍:HenCoder Android 自定义 View 1-7:属性动画 Property Animation(进阶篇)

多动画并行示例

这动画看起来有点像冬天冷颤的样子……╮(╯▽╰)╭


动画顺序:(下面三个动画不分先后,并行处理

  1. 下半部分向上翻起45°
  2. 图片以对角线交点为中心,旋转360
    1. 该部分不明白的话,请先阅读一遍自定义UI 使用Camera做三维变换
  3. 上半部分向下翻起45°
public class MainActivity extends AppCompatActivity {

    FancyFlipView view;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        view = findViewById(R.id.view);
        PropertyValuesHolder bottomFlipHolder = PropertyValuesHolder.ofFloat(
                "bottomFlip",
                45);
        PropertyValuesHolder flipRotationHolder = PropertyValuesHolder.ofFloat(
                "flipRotation",
                360);
        PropertyValuesHolder topFlipHolder = PropertyValuesHolder.ofFloat(
                "topFlip",
                -45);
        ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(
                view,
                bottomFlipHolder,
                flipRotationHolder,
                topFlipHolder);
        objectAnimator.setStartDelay(3000);
        objectAnimator.setDuration(1500);
        objectAnimator.start();
	}
}

以下内容摘录自:使用 ObjectAnimator 添加动画效果

ObjectAnimatorObjectAnimator 融合了 ValueAnimator 的计时引擎和值计算以及为目标对象的命名属性添加动画效果这一功能。这可以极大地简化为任何对象添加动画效果的过程,因为动画属性会自动更新,因此您也无需再实现 ValueAnimator.AnimatorUpdateListener 了。


多动画并行的更多知识点可见扔物线官方博文介绍:HenCoder Android 自定义 View 1-7:属性动画 Property Animation(进阶篇)

指定动画关键帧示例


上述动画仅仅修改头像的x轴坐标。我们定义下图象最终沿着 x 轴偏移的长度为 O O O px,总长度为 L L L px,动画总耗时为: D D D ms。


动画顺序:

  1. D D D [ 0 , 0.2 × D ] [0, 0.2 \\times D] [0,0.2×D] 区间时, O O O 0 0 0 增加到 1.5 × L 1.5 \\times L 1.5×L
  2. D D D [ 0.2 × D , 0.8 × D ] [0.2 \\times D, 0.8 \\times D] [0.2×D,0.8×D] 区间时, O O O 1.5 × L 1.5 \\times L 1.5×L 减少到 0.6 × L 0.6 \\times L 0.6×L
  3. D D D [ 0.8 × D , D ] [0.8 \\times D, D] [0.8×D,D] 区间时, O O O 0.6 × L 0.6 \\times L 0.6×L 增加到 L L L
public class MainActivity extends AppCompatActivity {

    FancyFlipView view;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        view = findViewById(R.id.view);
        float length = Utils.dpToPixel(123);
        // 定义起点
        Keyframe keyframe1 = Keyframe.ofFloat(0, 0);
        // 定义0.2 * duration的时候的位置
        Keyframe keyframe2 = Keyframe.ofFloat(0.2f, 1.5f * length);
        // 定义0.8 * duration的时候的位置
        Keyframe keyframe3 = Keyframe.ofFloat(0.8f, 0.6f * length);
        // 定义结束的位置
        Keyframe keyframe4 = Keyframe.ofFloat(1, 1 * length);
        PropertyValuesHolder viewHolder = PropertyValuesHolder.ofKeyframe(
                // 改变的维度
                "translationX",
                keyframe1,
                keyframe2,
                keyframe3,
                keyframe4
        );
        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
                view,
                viewHolder
        );
        animator.setStartDelay(1000);
        // 动画总时长
        animator.setDuration(2000);
        animator.start();
    }
}

以下内容摘录自:指定关键帧

KeyframeKeyframe 对象由时间值对组成,用于在动画的特定时间定义特定的状态。每个关键帧还可以用自己的插值器控制动画在上一关键帧时间和此关键帧时间之间的时间间隔内的行为。


更多知识点可见扔物线官方博文介绍:PropertyValuesHolders.ofKeyframe() 把同一个属性拆分

使用 TypeEvaluator


摘录自:使用 TypeEvaluator

如果要为 Android 系统无法识别的类型添加动画效果,则可以通过实现 TypeEvaluator 接口来创建您自己的评估程序。Android 系统可以识别的类型为 int、float 或颜色,分别由 IntEvaluatorFloatEvaluatorArgbEvaluator 类型评估程序提供支持。

TypeEvaluator接口中只有一种要实现的方法,那就是 evaluate() 方法。这样,您使用的 Animator 就会在动画的当前点为添加动画效果之后的属性返回适当的值。

属性动画定义

public class MainActivity extends AppCompatActivity {

    PointView view;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        view = findViewById(R.id.view);
        Point targetPoint = new Point(
                (int) Utils自定义UI 属性动画

自定义UI 属性动画

VSCode自定义代码片段——CSS动画

VSCode自定义代码片段7——CSS动画

VSCode自定义代码片段7——CSS动画

自定义UI 简易图文混排