自学内容网 自学内容网

Android——动画简述,属性动画用法概述

动画简述

Android动画大致分为帧动画、补间动画和属性动画三种
在这里插入图片描述

一、帧动画(Drawable Animation)

在这里插入图片描述


在这里插入图片描述

二、补间动画(Tween Animation)

补间动画一共可以分成四类:

  • 透明度动画 – AlphaAnimation
  • 缩放动画 – ScaleAnimation
  • 旋转动画 – RotateAnimation
  • 位移动画 – TranslateAnimation
    在这里插入图片描述

**注:**用xml实现补间动画,需要将xml放到res下的anim目录,Android工程默认是没有anim文件夹的,在读取文件之前需要先把anim 文件夹以及文件建好

用代码实现补间动画主要是通过构造xxxAnimation实例,然后去设置动画的一些属性

通用的xml属性及对应方法如下:

xml属性方法解释
android:durationsetDuration(long durationMillis)设置动画持续时间,单位毫秒
android:fillAftersetFillAfter(boolean fillAfter)如果设置为true,控件动画结束时,将保持动画最后时的状态
android:fillBeforesetFillBefore(boolean fillBefore)如果设置为true,控件动画结束时,还原到开始动画前的状态
android:repeatCountsetRepeatCount(int repeatCount)设置动画的重复次数,当传入值小于0时,例如Animation.INFINITE(其实就是-1),无限重复
注:传入值为重复次数,例如传入1,动画会播放两遍
android:repeatModesetRepeatMode(int repeatMode)设置动画的重复类型,例如
Animation.RESTAR:倒序回放动画
Animation.RESTAR:重新播放动画
注:该方法必须与setRepeatCount方法一起使用

(1)透明度动画 – AlphaAnimation

在这里插入图片描述

xml实现:

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:fromAlpha="1.0"
    android:toAlpha="0.1"
    android:duration="2000"/>
ImageView image = findViewById(R.id.image);
Button button1 = findViewById(R.id.button);
button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Animation animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.anim_alpha);
        image.startAnimation(animation);
    }
});

代码实现:

public AlphaAnimation(float fromAlpha, float toAlpha) {
    ...
}

和上述xml效果相同

public class TestActivity extends AppCompatActivity {

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

        ImageView image = findViewById(R.id.image);

        Animation alphaAnimation = new AlphaAnimation(1.0F, 0.1F);
        alphaAnimation.setDuration(2000);
        image.setAnimation(alphaAnimation);

    }
}
xml属性代码对应参数含义
android:fromAlphafromAlpha起始透明度(透明度的范围为:0-1,完全透明-完全不透明)
android:toAlphatoAlph结束透明度

(2)缩放动画 – ScaleAnimation

在这里插入图片描述

xml实现:

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:fromXScale="0.2"
    android:toXScale="1.5"
    android:fromYScale="0.2"
    android:toYScale="1.5"
    android:pivotX="50%"
    android:pivotY="50%"
    android:duration="2000"/>

attrs.xm中TranslateAnimation内容如下,即:

  • 输入float:单位为像素
  • 输入fraction(百分数):即当前组件尺寸的百分比
  • dimension:可以输入xxdp
<declare-styleable name="ScaleAnimation">
    <attr name="fromXScale" format="float|fraction|dimension" />
    <attr name="toXScale" format="float|fraction|dimension" />
    <attr name="fromYScale" format="float|fraction|dimension" />
    <attr name="toYScale" format="float|fraction|dimension" />
    <attr name="pivotX" />
    <attr name="pivotY" />
</declare-styleable>

代码实现:

public ScaleAnimation(float fromX, float toX, float fromY, float toY,
        int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) {
    ...
}
xml属性对应参数解释
android:fromXScalefromX沿着X轴缩放的起始比例
android:toXScaletoX沿着X轴缩放的结束比例
android:fromYScalefromY沿着Y轴缩放的起始比例
android:toYScaletoY沿着Y轴缩放的结束比例
android:pivotXpivotXValue缩放的中点X坐标,即距离当前视图左边缘的位置
xml输入浮点数则默认单位sp,输入分数(百分数)则为当前视图尺寸的百分比
代码实现是和pivotXType结合使用
pivotXTypeAnimation.ABSOLUTE:输入的为绝对的像素个数
Animation.RELATIVE_TO_SELF:输入的浮点数需要与组件宽度相乘
Animation.RELATIVE_TO_PARENT:输入浮点数需要与该组件的父组件宽度相乘
android:pivotYpivotYValue与pivotXValue同理
pivotYType与pivotXType同理

(3)位移动画 – TranslateAnimation

在这里插入图片描述

xml实现

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:fromXDelta="0"
    android:toXDelta="0"
    android:fromYDelta="0"
    android:toYDelta="-320"
    android:duration="2000"/>

attrs.xm中TranslateAnimation内容如下:

<declare-styleable name="TranslateAnimation">
    <attr name="fromXDelta" format="float|fraction|dimension" />
    <attr name="toXDelta" format="float|fraction|dimension" />
    <attr name="fromYDelta" format="float|fraction|dimension" />
    <attr name="toYDelta" format="float|fraction|dimension" />
</declare-styleable>

代码实现

public TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,
            int fromYType, float fromYValue, int toYType, float toYValue) {
   ......
}
xml属性对应参数解释
fromXType
android:fromXDeltafromXValue
toXType
android:toXDeltatoXValue
fromYType
android:fromYDeltafromYValue
toYType
android:toYDeltatoYValue

(4)旋转动画 – RotateAnimation

在这里插入图片描述
xml实现:

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:fromDegrees="0"
    android:toDegrees="360"
    android:duration="1000"
    android:pivotX="50%"
    android:pivotY="50%"/>

attrs.xm中TranslateAnimation内容如下:

<declare-styleable name="RotateDrawable">
    <attr name="visible" />
    <attr name="fromDegrees" format="float" />
    <attr name="toDegrees" format="float" />
    <attr name="pivotX" format="float|fraction" />
    <attr name="pivotY" format="float|fraction" />
    <attr name="drawable" />
</declare-styleable>

代码实现:

public RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue,
        int pivotYType, float pivotYValue) {
    ......
}
xml属性对应参数解释
android:fromDegreesfromDegrees旋转的起始角度
android:toDegreestoDegrees旋转的结束角度

(5)动画融合

可以将多种动画结合到一起:
在这里插入图片描述

xml实现:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:shareInterpolator="true" >

    <scale
        android:duration="2000"
        android:fromXScale="0.2"
        android:fromYScale="0.2"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="1.5"
        android:toYScale="1.5" />

    <rotate
        android:duration="1000"
        android:fromDegrees="0"
        android:toDegrees="360"
        android:pivotX="50%"
        android:pivotY="50%"/>

    <translate
        android:duration="2000"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:toXDelta="320"
        android:toYDelta="0" />

    <alpha
        android:duration="2000"
        android:fromAlpha="1.0"
        android:toAlpha="0.1" />

</set>

代码实现:

public class TestActivity extends AppCompatActivity {

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

        ImageView image = findViewById(R.id.image);

        Animation alphaAnimation = new AlphaAnimation(0.1f, 1.0f);
        alphaAnimation.setDuration(2000);

        Animation scaleAnimation = new ScaleAnimation(0.2f, 1.5f, 0.2f, 1.5f,
                ScaleAnimation.RELATIVE_TO_SELF,
                0.5f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
        scaleAnimation.setDuration(2000);

        Animation translateAnimation = new TranslateAnimation(TranslateAnimation.ABSOLUTE,
                0, Animation.ABSOLUTE, 320,
                Animation.ABSOLUTE, 0,
                Animation.ABSOLUTE, 0);
        translateAnimation.setDuration(2000);

        Animation rotateAnimation = new RotateAnimation(0, 360,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        rotateAnimation.setDuration(1000);

        AnimationSet animationSet = new AnimationSet(true);
        animationSet.addAnimation(alphaAnimation);
        animationSet.addAnimation(scaleAnimation);
        animationSet.addAnimation(translateAnimation);
        animationSet.addAnimation(rotateAnimation);
        animationSet.setFillAfter(true);
        image.startAnimation(animationSet);
    }
}

三、属性动画(Property Animation)

  1. 补间动画作用的对象是View,也就是Android中的组件,如ImageView、Button、TextView等,当然也可以作用在布局上,如LinearLayout、ConstraintLayout、RelativeLayout等,但是对于一些不是View的对象,将无法进行动画操作。比如想要对某个组件的某个属性做进行动画操作,例如颜色,这个颜色也可以看成一个对象,但其并不是View对象,补间动画就无法实现,但属性动画可以对这个颜色值做动画
  2. 补间动画只是改变了View的视觉效果,而不会真正去改变View的属性。而属性动画不但会帮助我们实现View动画的一些视觉效果,而且还能改变View的属性值
    在这里插入图片描述

属性动画使用类如下所示:主要使用类是:ValueAnimator 类 和 ObjectAnimator

  • ObjectAnimator
  • ValueAnimator
  • PropertyValueHolder
  • AnimatorSet
  • Interpolator
  • TypeEvaluator

(1)对象动画 – ObjectAnimator

Object中有很多重载的ofFloat、ofInt和ofObject方法

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
    ObjectAnimator anim = new ObjectAnimator(target, propertyName);
    anim.setFloatValues(values);
    return anim;
}

public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
    ObjectAnimator anim = new ObjectAnimator(target, propertyName);
    anim.setIntValues(values);
    return anim;
}

public static ObjectAnimator ofObject(Object target, String propertyName,
            TypeEvaluator evaluator, Object... values) {
    ObjectAnimator anim = new ObjectAnimator(target, propertyName);
    anim.setObjectValues(values);
    anim.setEvaluator(evaluator);
    return anim;
}
  • 第一个参数Object target:作用对象通常是View,也就是Android中的组件或布局
  • 第二个参数String propertyName:需要执行动画的属性,具体值一般有以下选择:
    • rotation
    • alpha
    • translationX / translationY:X/Y轴方向的旋转
    • scaleX /scaleY:X/Y轴方向上的缩放
    • rotationX / rotationY:X/Y轴方向的平移
  • 第三个参数: 表示属性的变换范围,该参数可以传多个值,传入多个值的时候为开始值、中间值和结束值,当传入两个值的时候为开始值和结束值

ofObject方法在(7)估值器 – TypeEvaluator中再学习

使用这种方式实现上面图片透明度动画:
在这里插入图片描述

xml实现:

**注:**在res目录下新建animator文件夹,在其中创建动画xml文件

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:propertyName="alpha"
    android:valueFrom="1"
    android:valueTo="0.2"
    android:duration="2000"
    android:valueType="floatType"/>

Java代码中通过加载该xml启动动画:

public class TestProActivity extends AppCompatActivity {

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

        ImageView image = findViewById(R.id.image_pro);
        Animator animator = AnimatorInflater.loadAnimator(TestProActivity.this, R.animator.pro_animation_alpha);
        animator.setTarget(image);
        animator.start();
    }
}

代码实现:

ImageView image = findViewById(R.id.image_pro);

Button button1 = findViewById(R.id.object);
button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ObjectAnimator animator = ObjectAnimator.ofFloat(image, "alpha", 1F, 0F, 1F);
        animator.setDuration(2000);
        animator.setStartDelay(500);
        animator.setRepeatCount(1);
        animator.setRepeatMode(ObjectAnimator.RESTART);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(@NonNull ValueAnimator animation) {
                // Log.d("TAG", "mafangda onAnimationUpdate: --- " + animation.getAnimatedValue());
                Log.d("TAG", "mafangda onAnimationUpdate: --- " + image.getAlpha());
            }
        });
        animator.start();
    }
});

以上面的ObjectAnimator中ofFloat为例,继续看一下源码:

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
    ObjectAnimator anim = new ObjectAnimator(target, propertyName);
    anim.setFloatValues(values);
    return anim;
}

private ObjectAnimator(Object target, String propertyName) {
    setTarget(target);
    setPropertyName(propertyName);
}

// 这里就是通过属性名可以自动推导出setter和getter方法,例如传入属性为Foo,那么对应的方法即setFoo和getFoo方法
// 这里注释中提示为了获得调用由属性名称确定的 setter 函数的最佳性能,请使用 float 或 int 类型的值,并使这些属性的 setter 函数返回 void
// 其他属性类型和返回类型也可以工作,但由于反射机制,会在处理请求时产生更多开销
public void setPropertyName(@NonNull String propertyName) {
    // mValues could be null if this is being constructed piecemeal. Just record the
    // propertyName to be used later when setValues() is called if so.
    if (mValues != null) {
        PropertyValuesHolder valuesHolder = mValues[0];
        String oldName = valuesHolder.getPropertyName();
        valuesHolder.setPropertyName(propertyName);
        mValuesMap.remove(oldName);
        mValuesMap.put(propertyName, valuesHolder);
    }
    mPropertyName = propertyName;
    // New property/values/target should cause re-initialization prior to starting
    mInitialized = false;
}

从上面的源码中我们可以知道,动画更新过程中是通过不断调用setPropertyName更新元素的属性

我们可以测试一下,自定义一个ImageView的子类如下,在这里我们自定义了一个value属性,但是在getter和setter方法中调用了ImageView中的getAlpha和setAlpha方法,即通过setValue和getValue应该可以获得自定义View的透明度。

@SuppressLint("AppCompatCustomView")
public class TestView extends ImageView {

    float value;

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

    public TestView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TestView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    public float getValue() {
        Log.d("TAG", "mafangda getValue() called value -- " + this.value);
        return super.getAlpha();
    }

    public void setValue(float value) {
        Log.d("TAG", "mafangda setValue() called with: value = [" + value + "]");
        this.value = value;
        super.setAlpha(value);
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">

    <com.example.animation.TestView
        android:id="@+id/testView"
        android:layout_width="120dp"
        android:src="@drawable/xi_an"
        android:layout_height="120dp"/>
</LinearLayout>

主程序,动画无限循环

@SuppressLint("MissingInflatedId")
public class TestObjectAnimator extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_object_animator);
        TestView ballView = findViewById(R.id.testView);
        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(ballView, "value",
                1F, 0F, 1F);
        objectAnimator.setDuration(2000);
        objectAnimator.setRepeatCount(-1);
        objectAnimator.start();
    }
}


注:如果操作组件的该属性对应的setter方法中没有调用View的重绘,可以使用下面手动重新绘制view

@SuppressLint("MissingInflatedId")
public class TestObjectAnimator extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_object_animator);
        TestView ballView = findViewById(R.id.testView);
        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(ballView, "rotationX",
                0, 100, 0);
        objectAnimator.setDuration(5000);
        objectAnimator.setRepeatCount(-1);
        objectAnimator.start();

        objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(@NonNull ValueAnimator animation) {
//ballView.postInvalidate();
//ballView.invalidate();
            }
        });
    }
}

​ 在这里,如果希望一个View的动画是既可以缩小,又可以降低透明度(淡出效果),同时修改scaleX、scaleY和alpha属性,后面我们可以使用AnimatorSet实现,现在可以如下操作:

xml布局,就一个图片

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:src="@drawable/xi_an" />
</LinearLayout>

java程序:

@SuppressLint("MissingInflatedId ")
public class TestObjectAnimator extends Activity {

    private ImageView imageView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.object_animator);
        imageView = findViewById(R.id.imageView);
        // 在这里我们随便填写一个UI组件没有的属性
        @SuppressLint("ObjectAnimatorBinding") ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView, "mfd",1F, 0F);
        objectAnimator.setDuration(5000);
        objectAnimator.setRepeatCount(-1);
        objectAnimator.start();

        objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(@NonNull ValueAnimator animation) {
                Float value = (Float) animation.getAnimatedValue();
                imageView.setAlpha(value);
                imageView.setScaleX(value);
                imageView.setScaleY(value);
            }
        });
    }
}

结果:
在这里插入图片描述
可以看到,设置属性的字符串我们随便填写一个UI组件没有的属性,只需要动画按照时间插值和持续时间不断的计算那个值,然后我们自己手动调用

(2)值动画 – ValueAnimator

值动画通过控制值的变化,赋值给对象的属性,从而实现动画。

ValueAnimator的核心方法如下:

// 以整数值进行动画处理
public static ValueAnimator ofInt(int... values)  

// 以浮点数值进行动画处理
public static ValueAnimator ofFloat(float... values) 

// 允许创建一个在动画过程中改变任意类型值的ValueAnimator
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) 

添加值动画,在值动画的监听函数里来获取值得变化,根据值的变化对控件设置相应的属性。这里的属性可以是控件的任意属性

Button button2 = findViewById(R.id.value);
button2.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ValueAnimator animator = ValueAnimator.ofFloat(0.2F, 1F);
        animator.setDuration(3000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(@NonNull ValueAnimator animation) {
                float value = (Float) animation.getAnimatedValue();
                Log.d("TAG", "mafangda onAnimationUpdate: value -- " + value);
                image.setAlpha(value);
            }
        });
        animator.start();
    }
});

效果如下所示:
在这里插入图片描述
​ 可以发现,ValueAnimator在使用方法上与ObjectAnimator 非常相似,区别在于不需要填写设置的属性,类似上面写的UI淡出示例的使用方法,

(3)监听动画事件

​ 对于动画,一般都是一些辅助效果,比如我要删除个元素,我可能希望是个淡出的效果,但是最终还是要删掉,并不是你透明度没有了,还占着位置,所以我们会使用到动画生命周期中的一些节点。

ValueAnimator animator = ValueAnimator.ofFloat(1.0F, 0F);
animator.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(@NonNull Animator animation) {
        
    }

    @Override
    public void onAnimationEnd(@NonNull Animator animation) {

    }

    @Override
    public void onAnimationCancel(@NonNull Animator animation) {

    }

    @Override
    public void onAnimationRepeat(@NonNull Animator animation) {

    }
});

​ 通过添加Animator.AnimatorListener我们就可以监听到动画的开始、结束、被取消、重复等事件,但是有时候我们只是想知道整个动画的某个节点,这样的代码我们不能接受,我们可以使用AnimatorListenerAdapter

AnimatorListenerAdapter源码如下:,我们可以发现AnimatorListenerAdapter继承自Animator.AnimatorListener,并且对空实现了其中的所有方法,这样我们想知道动画的哪个节点可以单独重写对应的方法,并不需要重写所有方法

public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener,
        Animator.AnimatorPauseListener {

    @Override
    public void onAnimationCancel(Animator animation) {
    }

    @Override
    public void onAnimationEnd(Animator animation) {
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
    }

    @Override
    public void onAnimationStart(Animator animation) {
    }

    @Override
    public void onAnimationPause(Animator animation) {
    }

    @Override
    public void onAnimationResume(Animator animation) {
    }
}

(4)PropertyValueHolder

PropertyValueHolder可以让动画同时执行

Button button3 = findViewById(R.id.property_value_holder);
button3.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        PropertyValuesHolder alphaProper = PropertyValuesHolder.ofFloat("alpha", 0.2f, 1f);
        PropertyValuesHolder scaleXProper = PropertyValuesHolder.ofFloat("scaleX", 0.5f, 1.2f);
        PropertyValuesHolder scaleYProper = PropertyValuesHolder.ofFloat("scaleY", 0.5f, 1.2f);
        PropertyValuesHolder translationXProper = PropertyValuesHolder.ofFloat("translationX", -100, 100);
        PropertyValuesHolder translationYProper = PropertyValuesHolder.ofFloat("translationY", -100, 100);
        PropertyValuesHolder rotationProper = PropertyValuesHolder.ofFloat("rotation", 0, 360);
        ValueAnimator animator = ObjectAnimator.ofPropertyValuesHolder(image, alphaProper,
                scaleXProper, scaleYProper, translationXProper, translationYProper, rotationProper);
        animator.setDuration(5000);
        animator.start();
    }
});

结果如下所示:
在这里插入图片描述

(5)动画组合 – AnimatorSet

PropertyValueHolder 类能实现将多个动画同时执行,AnimatorSet类不仅能让多个动画同时执行,还能让多个动画按一定的顺序执行,同时也能穿插多个动画同时执行。

主要的方法如下:

// 将现有动画延迟指定毫秒后执行
public Builder after(long delay) {
    // setup a ValueAnimator just to run the clock
    ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
    anim.setDuration(delay);
    after(anim);
    return this;
}
// 将现有动画插入到传入的动画之后执行
public Builder after(Animator anim)
   
// 将现有动画和传入的动画同时执行
public Builder with(Animator anim)

// 将现有动画插入到传入的动画之前执行
public Builder before(Animator anim)

示例:

Button button4 = findViewById(R.id.animator_set);
button4.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ObjectAnimator rotate = ObjectAnimator.ofFloat(image, "rotation", 0f, 360f);
        ObjectAnimator translationX = ObjectAnimator.ofFloat(image, "translationX", -100, 100f);
        ObjectAnimator translationY = ObjectAnimator.ofFloat(image, "translationY", -100, 100f);
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(image, "scaleX", 0, 1f);
        ObjectAnimator scaleY = ObjectAnimator.ofFloat(image, "scaleY", 0, 1f);
        ObjectAnimator alpha = ObjectAnimator.ofFloat(image, "alpha", 1f, 0f, 1f);
        AnimatorSet animSet = new AnimatorSet();
        animSet.play(rotate)
                .with(alpha)
                .after(scaleX)
                .before(translationX)
                .after(1000)
                .before(translationY)
                .with(scaleY);
        animSet.setDuration(5000);
        animSet.start();
    }
});

结果如下所示:按照after -> with -> before 的顺序合并后播放
在这里插入图片描述

(6)插值器 – Interpolator

前面的动画属性的变换都是均匀变换,可以通过差值器Interpolator来控制值变化的速率

Button button5 = findViewById(R.id.interpolator);
button5.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ObjectAnimator translationX = ObjectAnimator.ofFloat(image, "translationX", -200f, 200f);
        translationX.setDuration(5000);
        //加速查值器,参数越大,速度越来越快
        translationX.setInterpolator(new AccelerateInterpolator(5));
        translationX.start();
    }
});

在这里插入图片描述

系统提供的插值器:

名称效果
AccelerateInterpolator加速插值器:参数越大,速度越来越快
DecelerateInterpolator减速插值器:和加速插值器相反
AccelerateDecelerateInterpolator先加速后减速
AnticipateInterpolator先后退在加速前进
AnticipateOvershootInterpolator以X/Y轴为轴的旋转度数
BounceInterpolator在动画结束时产生弹跳效果
CycleInterpolator周期运动插值
LinearInterpolator匀速插值
OvershootInterpolator先快速完成动画,再回到结束样式

一般不会自己设计插值器,系统提供的插值器足够使用

(7)估值器 – TypeEvaluator

值动画(ValueAnimator)和 对象动画(ObjectAnimator)中有一个传对象的方法:

public static ObjectAnimator ofObject(Object target, String propertyName,
            TypeEvaluator evaluator, Object... values)
            
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)

TypeEvaluator接口:

public interface TypeEvaluator<T> {
    public T evaluate(float fraction, T startValue, T endValue);
}

TypeEvaluator的源码可以看出该类的作用就是告诉动画,如何从起始值过度到结束值

Android源码中有好几个类实现来该接口,也就是系统提供的一些默认估值器, 以FloatEvaluator为例:

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);
    }
}

FloatEvaluator 的实现可以看出在evaluate方法中用结束值减去初始值,算出它们之间的差值,然后乘以fraction这个系数,再加上初始值,那么就得到当前动画设置的值了

四、示例

实现一个例子,两个小球,一个做抛物线运动,一个垂直落下

分析一下:貌似只和时间有关系,但是根据时间的变化,横向和纵向的移动速率是不同的

结果:此时就要重写TypeValue的时候了,因为我们在时间变化的同时,需要返回给对象两个值,x当前位置,y当前位置

实现:

xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="220dp" >

        <ImageView
            android:id="@+id/ball1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ball"/>

        <ImageView
            android:id="@+id/ball2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ball"/>
    </FrameLayout>

    <Button
        android:id="@+id/start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Start" />

</LinearLayout>

java程序:

public class TestValueAnimator extends Activity {

    private ImageView ball1;
    private ImageView ball2;

    private ValueAnimator ball1Animator;
    private ValueAnimator ball2Animator;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.value_animator);
        ball1 = findViewById(R.id.ball1);
        ball2 = findViewById(R.id.ball2);

        // 为第一个小球自由落体设置动画
        ball1Animator = new ValueAnimator();
        ball1Animator.setDuration(3000);
        ball1Animator.setObjectValues(new PointF(0, 0));
        ball1Animator.setInterpolator(new LinearInterpolator());
        ball1Animator.setTarget(ball1);
        ball1Animator.setEvaluator(new TypeEvaluator<PointF>() {
            @Override
            public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
                PointF point = new PointF();
                point.x = 0;
                point.y = 0.5F * 10 * (fraction * 20) * (fraction * 20);
                return point;
            }
        });
        ball1Animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(@NonNull ValueAnimator animation) {
                PointF point = (PointF) animation.getAnimatedValue();
                ball1.setX(point.x);
                ball1.setY(point.y);
            }
        });


        // 为第二个小球有水平速度,做抛物线运动设置动画
        TypeEvaluator<PointF> pointFTypeEvaluator = new TypeEvaluator<PointF>() {
            @Override
            public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
                PointF point = new PointF();
                point.x = 100 * fraction * 20;
                point.y = 0.5F * 10 * (fraction * 20) * (fraction * 20);
                return point;
            }
        };
        ball2Animator = ValueAnimator.ofObject(pointFTypeEvaluator, new PointF(0, 0));
        ball2Animator.setDuration(3000);
        ball2Animator.setInterpolator(new LinearInterpolator());
        ball2Animator.setTarget(ball2);
        ball2Animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(@NonNull ValueAnimator animation) {
                PointF point = (PointF) animation.getAnimatedValue();
                ball2.setX(point.x);
                ball2.setY(point.y);
            }
        });

        // 点击开始 两个小球同时运动
        Button startButton = findViewById(R.id.start);
        startButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ball1Animator.start();
                ball2Animator.start();
                ;
            }
        });

    }
}

**注:**这里fraction是插值器产生的随时间变化的系数,变化区间是[0,1],所以这里乘了一个20作为系数,不然变化太小导致看不到动画

动画如下:
在这里插入图片描述


原文地址:https://blog.csdn.net/weixin_50995731/article/details/142236708

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!