安卓动画

2019-10-17  本文已影响0人  migill

Android 中的动画可以分为以下几类:

1、逐帧动画

逐帧动画的原理就是让一系列的静态图片依次播放,利用人眼“视觉暂留”的原理,实现动画。

步骤:
1、res/drawable 下新建 xml 文件,这里定义动画的每一帧,素材图片放到 drawable 下。
frame_animation.xml

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">
    <item android:drawable="@drawable/frame01" android:duration="100"/>
    <item android:drawable="@drawable/frame02" android:duration="100"/>
    <item android:drawable="@drawable/frame03" android:duration="100"/>
    <item android:drawable="@drawable/frame04" android:duration="100"/>
    <item android:drawable="@drawable/frame05" android:duration="100"/>
    <item android:drawable="@drawable/frame06" android:duration="100"/>
    <item android:drawable="@drawable/frame07" android:duration="100"/>
</animation-list>

2、布局中将 AnimationDrawable 对象直接作为背景

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


    <ImageView
        android:layout_marginTop="50dp"
        android:layout_centerHorizontal="true"
        android:id="@+id/frame_image"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="@drawable/frame_animation"
        />
</RelativeLayout>

3、Activity 中控制播放

public class FrameAnimation extends AppCompatActivity {
    ImageView frame_image;
    AnimationDrawable animationDrawable;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_frame_animation);
        frame_image = findViewById(R.id.frame_image);
        // 获取 AnimationDrawable 对象
         animationDrawable = (AnimationDrawable) frame_image.getBackground();
        // 开始播放
        animationDrawable.start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
         //停止播放
        animationDrawable.stop();
    }
}

利用 Java 代码实现逐帧动画

public class FrameAnimation extends Activity {
    ImageView frame_image;
    AnimationDrawable animationDrawable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_frame_animation);
        frame_image = findViewById(R.id.frame_image);
        animationDrawable = new AnimationDrawable();

        for (int i = 1; i < 10; i++) {
            int id = getResources().getIdentifier("frame0" + i, "drawable", getPackageName());
            Drawable drawable = getResources().getDrawable(id);
            animationDrawable.addFrame(drawable, 100);
        }
        animationDrawable.setOneShot(true);
        frame_image.setImageDrawable(animationDrawable);
        animationDrawable.stop();
        // 特别注意:在动画start()之前要先stop(),不然在第一次动画之后会停在最后一帧,这样动画就只会触发一次
        animationDrawable.start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        animationDrawable.setOneShot(true);
        frame_image.setImageDrawable(animationDrawable);
        animationDrawable.stop();
    }
}

2、补间动画

补间动画就是指开发者指定动画的开始、动画的结束的"关键帧",而动画变化的"中间帧"由系统计算,并补齐。文件一般存放在res/anim文件夹下,访问时采用R.anim.XXX.xml的方式。
补间动画有四种:

scale标签——调节尺寸
android:fromXScale:起始的X方向上相对自身的缩放比例,浮点值,比如1.0代表自身无变化,0.5代表起始时缩小一倍,2.0代表放大一倍;
android:toXScale:结尾的X方向上相对自身的缩放比例,浮点值;
android:fromYScale:起始的Y方向上相对自身的缩放比例,浮点值,
android:toYScale:结尾的Y方向上相对自身的缩放比例,浮点值;
android:pivotX :缩放起点X轴坐标,可以是数值、百分数、百分数p 三种样式,比如 50、50%、50%p,当为数值时,表示在当前View的左上角,即原点处加上50px,做为起始缩放点;如果是50%,表示在当前控件的左上角加上自己宽度的50%做为起始点;如果是50%p,那么就是表示在当前的左上角加上父控件宽度的50%做为起始点x轴坐标。
android:pivotY:缩放起点Y轴坐标,取值及意义跟android:pivotX一样。

从Animation类继承的属性
android:duration:动画持续时间,以毫秒为单位
android:fillAfter:如果设置为true,控件动画结束时,将保持动画最后时的状态
android:fillBefore:如果设置为true,控件动画结束时,还原到开始动画前的状态
android:fillEnabled:与android:fillBefore 效果相同,都是在动画结束时,将控件还原到初始化状态
android:repeatCount:重复次数
android:repeatMode:重复类型,有reverse和restart两个值,reverse表示倒序回放,restart表示重新放一遍,必须与repeatCount一起使用才能看到效果。因为这里的意义是重复的类型,即回放时的动作。
android:interpolator:设定插值器,其实就是指定的动作效果,比如弹跳效果等,不在这小节中讲解,后面会单独列出一单讲解。
android:duration:动画的持续时长,以毫秒为单位

alpha标签——调节透明度
android:fromAlpha 动画开始的透明度,从0.0 --1.0 ,0.0表示全透明,1.0表示完全不透明
android:toAlpha 动画结束时的透明度,也是从0.0 --1.0 ,0.0表示全透明,1.0表示完全不透明

rotate标签——旋转
android:fromDegrees:开始旋转的角度位置,正值代表顺时针方向度数,负值代码逆时针方向度数
android:toDegrees:结束时旋转到的角度位置,正值代表顺时针方向度数,负值代码逆时针方向度数
android:pivotX:缩放起点X轴坐标,可以是数值、百分数、百分数p 三种样式,比如 50、50%、50%p,具体意义已在scale标签中讲述,这里就不再重讲
android:pivotY:缩放起点Y轴坐标,可以是数值、百分数、百分数p 三种样式,比如 50、50%、50%p

translate标签 —— 平移
android:fromXDelta:起始点X轴坐标,可以是数值、百分数、百分数p 三种样式,比如 50、50%、50%p,具体意义已在scale标签中讲述,这里就不再重讲
android:fromYDelta:起始点Y轴从标,可以是数值、百分数、百分数p 三种样式;
android:toXDelta:结束点X轴坐标
android:toYDelta:结束点Y轴坐标

示例:
1、定义动画资源:
res\anim\tween_anim.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    >
    <scale
        android:duration="3000"
        android:fromXScale="0.0"
        android:fromYScale="0.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="1.0"
        android:toYScale="1.0"/>
    <alpha
        android:duration="3000"
        android:fromAlpha="1.0"
        android:toAlpha="0.5" />
    <rotate
        android:fromDegrees="0"
        android:toDegrees="720"
        android:pivotX = "50%"
        android:pivotY="50%"
        android:duration = "3000"
        />
    <translate
        android:fromXDelta="0"
        android:toXDelta="100"
        android:fromYDelta="0"
        android:toYDelta="100" />
</set>

2、Animation 控制图片播放动画

public class tweenAnimation extends Activity {
    // tween_image;
    Button tween_start;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tween_animation);

        final ImageView  tween_image = findViewById(R.id.tween_image);
        tween_start = findViewById(R.id.tween_start);
        
        // 加载动画资源
        final Animation anim = AnimationUtils.loadAnimation(this,R.anim.tween_anim);
        //设置动画结束后保留结束状态
        anim.setFillAfter(true);
        
        tween_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                tween_image.startAnimation(anim);
            }
        });
        
    }
}

这几个动画可以组合在一起使用,同时完成缩放、透明的、旋转或者位移等的变化。

3、属性动画

工作原理 指定时间内,修改属性(对象中对应的字段)的值,以此实现该对象在属性上的动画效果。

属性动画的使用:

ObjectAnimator animator = ObjectAnimator.ofFloat(button, "ScaleX", 1f, 2f, 3f, 1f);
animator.setInterpolator(new LinearInterpolator());
animator.setDuration(3000);
animator.start();

动画的本质:
动画实际上是改变View在某一时间点的样式属性,比如在0.1s的时候View的坐标是100px,在0.2s的时候改变成了200px,用户感觉View在向右移动
实际上是通过一个线程每隔一段时间通过调用View.setX()改变属性值,也能产生动画效果

为什么要将动画分解成不同的关键帧:
动画是需要时间开销才能够完成的,如果不给出关键帧动画,动画的过程将无法控制。在不同的时间点,控件的状态也不一样。

什么时候开始绘制呢?
在Choreographer类里面存在FrameDisplayEventReceiver对象,用于接收Vsync信号,接收到信号之后就会回调ViewRootImpl里面TraversalRunnable的run方法。


自定义动画框架的实现:


实现属性动画实际是通过反射获取View中的方法,在不同的时间改变对象的属性值

   //每隔16ms执行一次
    @Override
    public boolean doAnimationFrame(long currentTime) {
        //后续的效果渲染
        //动画的总帧数
        float total = mDuration / 16;
        //拿到执行百分比 (index)/total
        float fraction = (index++) / total;
        //通过插值器去改变对应的执行百分比
        if (interpolator != null) {
            fraction = interpolator.getInterpolator(fraction);
        }
        //循环 repeat
        if (index >= total) {
            index = 0;
        }
        //交给mFloatPropertyValuesHolder,改变对应的属性值
        mFloatPropertyValuesHolder.setAnimatedValue(mTarget.get(), fraction);
        return false;
    }
public class MyFloatPropertyValuesHolder {
    //属性名
    String mPropertyName;
    //属性类型 float
    Class mValueType;
    //反射
    Method mSetter = null;
    //关键帧管理类
    MyKeyframeSet mKeyframeSet;

    public MyFloatPropertyValuesHolder(String propertyName, float... values) {
        this.mPropertyName = propertyName;
        mValueType = float.class;
        //交给关键帧管理初始化
        mKeyframeSet = MyKeyframeSet.ofFloat(values);
    }

    //通过反射获取控件对应的方法
    public void setupSetter() {
        char firstLetter = Character.toUpperCase(mPropertyName.charAt(0));
        String theRest = mPropertyName.substring(1);
        //setScaleX
        String methodName = "set" + firstLetter + theRest;
        try {
            mSetter = View.class.getMethod(methodName, float.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    //给控件设置相应的属性值
    public void setAnimatedValue(View view, float fraction) {
        Object value = mKeyframeSet.getValue(fraction);
        try {
            mSetter.invoke(view, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
上一篇下一篇

猜你喜欢

热点阅读