安卓的动画

1,528 阅读15分钟

在安卓日常开发时经常会用到一些动画知识.像是直播中的图标我们经常用到帧动画,像是平常的加载中,或转盘等操作,我们经常用到补间动画,如果再想实现一些更加复杂的效果,我们可以使用系统给我们提供的属性动画.那么这些动画在我们日常开发中是怎么结合使用的.他们又有哪些特点和区别呢?接下来我将给大家做一个简要的介绍.

动画分类

视图动画(View Animation)

  • 作用对象,视图(View)
  • 具体分类:
    • 补间动画
    • 逐帧动画 下面简单这两种动画.

补间动画(Tween Animation)

补间动画公共配置

// 以下参数是4种动画效果的公共属性,即都有的属性
android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果
android:startOffset ="1000" // 动画延迟开始时间(ms)
android:fillBefore = “true” // 动画播放完后,视图是否会停留在动画开始的状态,默认为true
android:fillAfter = “false” // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
android:fillEnabled= “true” // 是否应用fillBefore值,对fillAfter值无影响,默认为true
android:repeatMode= “restart” // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart
android:repeatCount = “0” // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
android:interpolator = 动画插值器,即影响动画的播放速度,下面会详细讲

平移动画(Translate)

// 以下参数是平移动画特有的属性
android:fromXDelta="0" // 视图在水平方向x 移动的起始值
android:toXDelta="500" // 视图在水平方向x 移动的结束值
android:fromYDelta="0" // 视图在竖直方向y 移动的起始值
android:toYDelta="500" // 视图在竖直方向y 移动的结束值

缩放动画(Scale)

// 以下参数是缩放动画特有的属性
android:fromXScale="0.0" // 动画在水平方向X的起始缩放倍数
android:toXScale="2"  //动画在水平方向X的结束缩放倍数
android:fromYScale="0.0" //动画开始前在竖直方向Y的起始缩放倍数
android:toYScale="2" //动画在竖直方向Y的结束缩放倍数
上面四个:
0.0表示收缩到没有;1.0表示正常无伸缩,值小于1.0表示收缩;值大于1.0表示放大

// 轴点 = 视图缩放的中心点
android:pivotX="50%" // 缩放轴点的x坐标
android:pivotY="50%" // 缩放轴点的y坐标

// pivotX pivotY,可取值为数字,百分比,或者百分比p
// 设置为数字时(如50),轴点为View的左上角的原点在x方向和y方向加上50px的点。在Java代码里面设置这个参数的对应参数是Animation.ABSOLUTE。
// 设置为百分比时(如50%),轴点为View的左上角的原点在x方向加上自身宽度50%和y方向自身高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_SELF。
// 设置为百分比p时(如50%p),轴点为View的左上角的原点在x方向加上父控件宽度50%和y方向父控件高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_PARENT
// 两个50%表示动画从自身中间开始,具体如下图

旋转动画(Rotate)

// 以下参数是旋转动画特有的属性
android:duration="1000"
android:fromDegrees="0" // 动画开始时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
android:toDegrees="270" // 动画结束时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
android:pivotX="50%" // 旋转轴点的x坐标
android:pivotY="0" // 旋转轴点的y坐标
// 轴点 = 视图缩放的中心点
// pivotX pivotY,可取值为数字,百分比,或者百分比p
// 设置为数字时(如50),轴点为View的左上角的原点在x方向和y方向加上50px的点。在Java代码里面设置这个参数的对应参数是Animation.ABSOLUTE。
// 设置为百分比时(如50%),轴点为View的左上角的原点在x方向加上自身宽度50%和y方向自身高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_SELF。
// 设置为百分比p时(如50%p),轴点为View的左上角的原点在x方向加上父控件宽度50%和y方向父控件高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_PARENT
    // 两个50%表示动画从自身中间开始,具体如下图

透明度动画(Alpha)

// 以下参数是透明度动画特有的属性
android:fromAlpha="1.0" // 动画开始时视图的透明度(取值范围: -1 ~ 1)
android:toAlpha="0.0"// 动画结束时视图的透明度(取值范围: -1 ~ 1)

几种动画的综合运用

  • 代码
private void startTranslate(){
    TranslateAnimation translateAnimation = new TranslateAnimation(0,0,0,600);
    translateAnimation.setFillAfter(true);
    translateAnimation.setDuration(1000);
    translateAnimation.setRepeatCount(4);
    translateAnimation.setRepeatMode(Animation.REVERSE);
    binding.first.startAnimation(translateAnimation);
}
private void startRotate(){
    float pivotX = binding.second.getMeasuredWidth()/2;
    float pivotY = binding.second.getMeasuredHeight();
    RotateAnimation rotateAnimation = new RotateAnimation(0,180,pivotX,pivotY);
    rotateAnimation.setFillAfter(true);
    rotateAnimation.setDuration(1000);
    rotateAnimation.setRepeatCount(4);
     rotateAnimation.setRepeatMode(Animation.REVERSE);
     binding.second.startAnimation(rotateAnimation);
 }
 private void startScale(){
     float width = binding.third.getMeasuredWidth();
     float height = binding.third.getMeasuredHeight();
     ScaleAnimation scaleAnimation = new ScaleAnimation(1,0,1,0,width/2,height/2);
     scaleAnimation.setFillAfter(true);
     scaleAnimation.setDuration(1000);
     scaleAnimation.setRepeatCount(4);
     scaleAnimation.setRepeatMode(Animation.REVERSE);
     binding.third.startAnimation(scaleAnimation);
}
private void startAlpha(){
    AlphaAnimation alphaAnimation = new AlphaAnimation(1,0);
    alphaAnimation.setFillAfter(true);
    alphaAnimation.setDuration(1000);
    alphaAnimation.setRepeatCount(4);
    alphaAnimation.setRepeatMode(Animation.REVERSE);
    binding.four.startAnimation(alphaAnimation);
}
  • 效果

设置视图动画监听器

  • public void setAnimationListener(Animation.AnimationListener listener)
  • 动画监听器接口源码:
public interface AnimationListener {
    void onAnimationStart(Animation var1);//在动画开始播放时调用
    void onAnimationEnd(Animation var1);在动画结束播放时调用。
    void onAnimationRepeat(Animation var1);//在动画重复播放时调用。
}

注意 假如我们不希望实现所有方法的话我们可以用AnimatorListenerAdapter包装类,减少自己实现无用的方法.

组合动画

有时候我们想让一组动画按照顺序或者同时播放,此时我们把上面的四种动画组合起来就能构成组合动画了.同时我们还能控制是让他们顺序播放还是同时播放

  • 代码
private void startAnim(){
        //步骤1:创建组合动画对象(设置为true)
        AnimationSet setAnimation = new AnimationSet(true);

        /**
         * 步骤2:设置组合动画的属性
         * 因为在下面的旋转动画设置了无限循环(RepeatCount = INFINITE)
         * 所以动画不会结束,而是无限循环
         * 所以组合动画的下面两行设置是无效的
         */
        setAnimation.setRepeatMode(Animation.RESTART);
        setAnimation.setRepeatCount(10);// 设置了循环一次,但无效

        // 步骤3:逐个创建子动画(方式同单个动画创建方式,此处不作过多描述)
        //子动画1:旋转动画
        Animation rotate = new RotateAnimation(0,360,
                Animation.RELATIVE_TO_SELF,0.5f,
                Animation.RELATIVE_TO_SELF,0.5f);
        rotate.setDuration(3000);
        //rotate.setRepeatMode(Animation.RESTART);
        //rotate.setRepeatCount(Animation.INFINITE);
        //子动画2:平移动画
        Animation translate = new TranslateAnimation(TranslateAnimation.RELATIVE_TO_PARENT,-0.5f,
                TranslateAnimation.RELATIVE_TO_PARENT,0.5f,
                TranslateAnimation.RELATIVE_TO_SELF,0
                ,TranslateAnimation.RELATIVE_TO_SELF,0);
        translate.setDuration(3000);

        //子动画3:透明度动画
        Animation alpha = new AlphaAnimation(1,0);
        alpha.setDuration(3000);
        //alpha.setStartOffset(3000);

        //子动画4:缩放动画
        Animation scale1 = new ScaleAnimation(1,0.5f,1,0.5f,Animation.RELATIVE_TO_SELF,0.5f, Animation.RELATIVE_TO_SELF,0.5f);
        scale1.setDuration(3000);
        //scale1.setStartOffset(4000);

        //步骤4:将创建的子动画添加到组合动画里
        setAnimation.addAnimation(alpha);
        setAnimation.addAnimation(rotate);
        setAnimation.addAnimation(translate);
        setAnimation.addAnimation(scale1);

        binding.start.startAnimation(setAnimation);
    }
  • 执行效果

逐帧动画Frame Animation

简单讲解完上面的视图动画,我们看一下今天的重头戏,属性动画.

属性动画

属性动画是在安卓3.0之后提供的一种全新的动画模式.

为什么要使用属性动画?

属性动画的具体介绍

关键类作用备注
ViewPropertyAnimator采用对对象操作实现属性动画
ObjectAnimator先改变值,然后自动赋值给对象的属性,从而实现动画效果采用getXXX()和getXXX()进行自动赋值
ValueAnimator先改变值,然后手动赋值给对象的属性,从而实现动画效果本质是按照指定规律操作数值从开始到结束的一种机制
AnimatorSet实现组合动画的类

属性动画的使用

最简单的ViewPropertyAnimator

  • 代码:
view.animate().translationX(500);
view.animate().translationX(0);

注意:动画没设置duration默认是300ms执行完毕

默认采用先加速在减速的插值器AccelerateDecelerateInterpolator

  • 效果:
  • 介绍
  1. ViewPropertyAnimator它定义在我们所有view的顶级父类View类中.也就是说几乎我们所有的view都能简单的使用此属性动画来达到我们想要的动画效果
  2. 此Api使用方式的简单性决定了它不能完成一些较为复杂的动画效果
  3. ViewPropertyAnimator支持的动画效果如下: |View中的方法|功能|对应的ViewProPertyAnimator中的方法| |:-:|:-:|:-:| |setTranslationX()|设置x轴偏移|translationX() translationXBy()| |setTranslationY()|设置y轴偏移|translationY() translationYBy()| |setTranslationZ()|设置z轴偏移|translationZ() translationZBy()| |setX()|设置x轴绝对位置 x像素|x() xBy()| |setY()|设置x轴绝对位置|y() yBy()| |setZ()|设置z轴绝对位置|z() zBy()| |setRotation()|设置平面旋转 x度|rotation() rotationBy()| |setRotationX()|设置沿X轴旋转|rotationX() rotationXBy()| |setRotationY()|设置沿Y轴旋转|rotationY() rotationYBy()| |setScaleX()|设置横向缩放 x倍|scaleX() scaleXBy()| |setScaleY()|设置纵向缩放|scaleY() scaleYBy()| |setAlpha()|设置透明度|alpha() alphaBy()|

ObjectAnimator使用

  • 示例代码:
ObjectAnimator animator = ObjectAnimator.ofFloat(binding.object,"rotation",0,360,0,360,0,360,0,360,0);
animator.setDuration(2000);
animator.start();
  • 效果:
  • 说明:
  1. 属性动画根据设置的对象及其属性来执行设定的动画
  2. 参数中的rotation在代码中必须对应setRotation(float f)方法系统才能准确识别到.否则动画不会执行.
  3. ObjectAnimator中的ofXXX()类型对应的是setXXX()方法的参数,比如说ofFloat方法生成的ObjectAnimator,起setXXX()方法的参数必须是Float类型的.
  4. ObjectAnimator中最后一个是可变参数,可以设置多个值,系统会按照设置的值依次执行动画.
  5. 调用start方法来开始执行动画
  6. ObjectAnimator支持任意对象的任意属性执行动画效果,但是该属性需要实现getXXX()和setXXX()方法,同时,在set方法内必须调用invalidate();来进行view的重绘.

ValueAnimator使用

ValueAnimator是更加灵活的动画定义方式.它支持任何类型按照特定规律的变化效果.

  • 简单代码:
/**
 * 启动valueAnimator
 */
private void startValueAnim(){
    ValueAnimator animator = ValueAnimator.ofFloat(0,360,0,360,0,360,0,360,0);
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            binding.value.setRotation((float)animation.getAnimatedValue());
        }
    });
    animator.setDuration(4000);
    animator.start();
}
  • 效果
  • 原理:

使用AnimatorSet安排多个属性动画同时播放

在许多情况下,我们需要根据一个动画开始或结束的时间来播放另一个动画。借助AnimatorSet,我们可以将动画捆绑到一个 AnimatorSet 中,以便指定是同时播放动画、按顺序播放还是在指定的延迟时间后播放。当然我们还可以嵌套 AnimatorSet 对象。来实现更为复杂的效果

  • 以下代码段通过以下方式播放相应的 Animator 对象:
  1. 播放anim1
  2. 同时播放anim2_1,anim2_2,anim2_3
  3. 播放anim3
  4. 播放 anim4
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(anim1).before(anim2_1);
bouncer.play(anim2_1).with(anim2_2);
bouncer.play(anim2_1).with(anim2_3);
bouncer.play(anim3).after(anim2_3);
ValueAnimator anim4 = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(anim4);
animatorSet.start();

另外还有:

  • 同时播放两个动画
animatorSet.playTogether(animator1, animator2);
  • 两个动画依次执行
animatorSet.playSequentially(animator1, animator2);

PropertyValuesHolder 同一个动画中改变多个属性

PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 1);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 1);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 1);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder1, holder2, holder3)
animator.start();

PropertyValuesHolders.ofKeyframe() 把同一个属性拆分

// 在 0% 处开始
Keyframe keyframe1 = Keyframe.ofFloat(0, 0);
// 时间经过 50% 的时候,动画完成度 100%
Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 100);
// 时间见过 100% 的时候,动画完成度倒退到 80%,即反弹 20%
Keyframe keyframe3 = Keyframe.ofFloat(1, 80);
PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("progress", keyframe1, keyframe2, keyframe3);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder);
animator.start();

属性动画监听器

public static interface AnimatorListener {
    default void onAnimationStart(Animator animation, boolean isReverse) {//isReverse 动画是否反向播放
        onAnimationStart(animation);
    }
    default void onAnimationEnd(Animator animation, boolean isReverse) {//isReverse 动画是否反向播放
        onAnimationEnd(animation);
    }
    void onAnimationStart(Animator animation);//动画开始执行时调用
    void onAnimationEnd(Animator animation);//动画执行完成时调用
    void onAnimationCancel(Animator animation);//动画执行被取消时调用
    void onAnimationRepeat(Animator animation);//动画重复执行时调用
}
  • 设置监听器
animator.addListener(new Animator.AnimatorListener(){
    @Override
    public void onAnimationStart(Animator animation) {
    }
    @Override
    public void onAnimationEnd(Animator animation) {
    }
    @Override
    public void onAnimationCancel(Animator animation) {
    }
    @Override
    public void onAnimationRepeat(Animator animation) {
    }
});

动画插值器Interpolator

根据时间完成度的百分比 计算当前属性完成度的百分比。比如在线性插值器中,把时间的50%转成动画完成了50%,

注意:插值器不返回动画改变的具体数值,只返回动画改变的百分比.

  1. 源码
public interface TimeInterpolator {
/**
     * Maps a value representing the elapsed fraction of an animation to a value that represents
     * the interpolated fraction. This interpolated value is then multiplied by the change in
     * value of an animation to derive the animated value at the current elapsed animation time.
     *
     * @param input 取值范围是[0.0,1.0],代表时间流失百分比
     * @return 返会属性改变的百分比.
     */
    float getInterpolation(float input);
}
  1. 用法
animator.setInterpolator(new AccelerateInterpolator());
  1. 系统提供的动画插值器 |类名|介绍| |-|-| |AccelerateDecelerateInterpolator|先加速后减速| |AccelerateInterpolator|一直加速| |LinearInterpolator|匀速| |DecelerateInterpolator|一直减速| |AnticipateInterpolator|先回拉一下然后在正常执行动画(类似于打弹弓)| |OvershootInterpolator|到达目标点后会超过一些再回弹回来(类似于坐沙发)| |AnticipateOvershootInterpolator|上面两个的结合版,开始回拉一下然后正常执行最后超过目标点回弹| |BounceInterpolator|在目标值处弹跳(类似于打球的效果)| |CycleInterpolator|没用过不知道| |PathInterpolator|自定义动画完成度和时间完成度曲线,这个比较复杂,如果把动画完成度时间完成度绘制成一个坐标轴的话,那么在这个轴上画出的线就是动画执行的速度| |FastOutLinearInInterpolator|加速运动与AccelerateInterpolator相似只不过初始加速会快一些,此插值器曲线公式用的是贝塞尔曲线,而AccelerateInterpolator用的是指数曲线| |FastOutSlowInInterpolator|先加速在减速,与AccelerateDecelerateInterpolator相似,只不过是它用的贝塞尔曲线所以前期加速度快,AccelerateDecelerateInterpolator用的是余弦曲线| |LinearOutSlowInInterpolator|减速运动,比DecelerateInterpolator 初始速度更高|

动画估值器TypeEvaluator

估值器就是根据当前属性改变的百分比来计算改变后的属性值。
插值器决定属性值随时间变化的规律;而具体变化属性数值则交给估值器去计算。

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

  1. FloateEvaluator源码
public class FloatEvaluator implements TypeEvaluator<Number> {
    /**
     * 此方法根据动画完成度,和开始值结束值,来计算最终的动画数值.
     */
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}

其中fraction参数是经过插值器处理之后的一个float类型的小数,他的取值范围是0.0-1.0,他代表动画完成的一个百分比. 2. 使用

animator.setEvaluator(new PointFEvaluator());
或
ValueAnimator.ofObject(new PointFEvaluator(),obj1,obj2);
  1. 自定义TypeEvaluate的一个示例: 假如说我们自定义了一个圆形,圆形的圆心即我们下面定义的Point,现在我们要让这个圆进行抛物轨迹运动,代码如下: BallPoint.java
//用来代表小球中心点
public class BallPoint {
    private float pointX;
    private float pointY;

    public BallPoint(float pointX, float pointY) {
        this.pointX = pointX;
        this.pointY = pointY;
    }

    public float getPointX() {
        return pointX;
    }

    public float getPointY() {
        return pointY;
    }
}

BallView.java

//自定义的一个圆球view
public class BallView extends View {
    private Paint circlePaint = new Paint();
    private BallPoint point = new BallPoint(0,0);

    public BallView(Context context) {
        super(context);
    }

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

    public BallView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        circlePaint.setColor(Color.parseColor("#aabbcc"));
        circlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
    }


    public BallPoint getPoint() {
        return point;
    }

    public void setPoint(BallPoint point) {
        this.point = point;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.parseColor("#aabbcc"));
        canvas.drawCircle(point.getPointX(),point.getPointY(),60,circlePaint);
    }
}

BallViewEvaluator.java

/**
 * 模拟抛物运动的估值器
 */
public class BallViewEvaluator implements TypeEvaluator<BallPoint> {
    @Override
    public BallPoint evaluate(float fraction, BallPoint startValue, BallPoint endValue) {
        float pointX = startValue.getPointX()+(fraction * endValue.getPointX() - startValue.getPointX());
        float pointY = startValue.getPointY()+fraction * fraction * (endValue.getPointY()-startValue.getPointX());
        BallPoint point  = new BallPoint(pointX, pointY);
        return point;
    }
}

xml,及动画代码

<com.zhou.animationdemo.BallView
        android:id="@+id/ballView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
        
private void startBallAnim() {
        ValueAnimator animator = ValueAnimator.ofObject(new BallViewEvaluator(), new BallPoint(0, 0), new BallPoint(600, 1200)).setDuration(1000);
        animator.setInterpolator(new LinearInterpolator());
        animator .addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                BallPoint point = (BallPoint) animation.getAnimatedValue();
                binding.ballView.setPoint(point);
                // ToDo 您也可以在此记录或做相应操作
            }
        });
        animator.start();
    }
  • 动画效果

创建圆形揭露动画

  • 点击时执行代码:
private void showCircleAnim(){
    float finalRadius = (float) Math.hypot(binding.view.getWidth(), binding.view.getHeight());
    Animator anim = ViewAnimationUtils.createCircularReveal(binding.view, 0, 0, 0f, finalRadius);
    binding.view.setVisibility(View.VISIBLE);
    anim.setDuration(800);
    anim.start();
}
  • 效果:

为布局更新添加动画

  • 在容器布局添加代码:
<LinearLayout android:id="@+id/container"
    android:animateLayoutChanges="true"
    ...
/>
  • activity中的示例代码(增加view)
private LinearLayout.LayoutParams getLayoutParams(){
        LinearLayout.LayoutParams params =  new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,100);
        params.topMargin=30;
        return params;
    }

    int i=0;
    private void addView(){
        View v = new View(this);
        if(i%2==0){
            v.setBackgroundColor(Color.parseColor("#00FF00"));
        }else{
            v.setBackgroundColor(Color.parseColor("#0000FF"));
        }
        i++;
        v.setLayoutParams(getLayoutParams());
        binding.viewContainer.addView(v,0);
    }
  • 效果 增加animateLayoutChanges配置前:

增加animateLayoutChanges配置后:

减少子view时与此效果类似,此处不做展示

使用FlingAnimation动画移动view

  • 配置
//应用模块的gradle文件中导入支持库,集成androidx可不用此项配置
dependencies {
  implementation 'com.android.support:support-dynamic-animation:28.0.0'
}
  • 使用
  1. 获取最大值
maxTransitionX = binding.getRoot().getWidth() - binding.fling.getWidth();
maxTransitionY = binding.getRoot().getHeight() - binding.fling.getHeight();
  1. 根据当前手势获取当前速度
1. 初始化监听器
private GestureDetector.OnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
    @Override
    public boolean onDown(MotionEvent e) {
        return true;
    }
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        if (Math.abs(velocityX) > Math.abs(velocityY)) {
            showFlingAnim(velocityX);
        } else {
            showFlingAnimY(velocityY);
        }
        return true;
    }
};
2. 初始化手势监听器,并从onTouchListener中获得EventMotion
detector = new GestureDetector(this,listener);
binding.fling.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return detector.onTouchEvent(event);
    }
});
  • 效果

总结

  1. 对比ValueAnimator类 & ObjectAnimator 类,其实二者都属于属性动画,本质上都是一致的:先改变值,然后 赋值 给对象的属性从而实现动画效果。
  • ValueAnimator 类是先改变值, 然后 手动赋值 给对象的属性从而实现动画;是 间接 对对象属性进行操作
  • ObjectAnimator类是先改变值,然后 自动赋值 给对象的属性从而实现动画;是 直接 对对象属性进行操作
  1. 属性动画类之间关系