Android动画系列(1)—帧动画

Android动画系列(1)—帧动画

首语

  • 这是Android动画系列的目录,有兴趣的可以学习:Android动画
  • 在某些情况下,图片需要在屏幕上呈现动画效果。如果您希望显示由多张图片组成的自定义加载动画,或者希望一个图标在用户执行操作后变为另一个图标,这种做法就非常实用。Android 提供了两个选项可以实现。
  • 第一个选项是使用 AnimationDrawable。使用该选项,您可以指定多个静态图片资源(每次展示一个)来创建动画。第二个选项是使用 AnimatedVectorDrawable。使用该选项,您可以为矢量图添加动画效果。

帧动画(AnimationDrawable)

  • 帧动画就是由N张静态图片,然后通过控制依次显示这些图片,就形成了动画。
  • 实现帧动画有两种方式,第一种是xml中实现,第二种是代码实现。
XML实现
  • 帧动画的XML文件位置在res/drawable/ 目录中,XML 文件包含一个 <animation-list> 元素(用作根节点)和一系列子 <item> 节点(每个节点定义一个帧)。
	<!--android:oneshot="true"表示是否循环播放,true表示只播放一次。-->
    <!--android:duration="200"表示这张图片动画播放的时长,单位ms。-->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true">

    <item android:drawable="@drawable/lockscreen_01" android:duration="200"/>
    <item android:drawable="@drawable/lockscreen_02" android:duration="200"/>
    <item android:drawable="@drawable/lockscreen_03" android:duration="200"/>
    <item android:drawable="@drawable/lockscreen_04" android:duration="200"/>
    <item android:drawable="@drawable/lockscreen_05" android:duration="200"/>
    <item android:drawable="@drawable/lockscreen_06" android:duration="200"/>
    <item android:drawable="@drawable/lockscreen_07" android:duration="200"/>
    <item android:drawable="@drawable/lockscreen_08" android:duration="200"/>
    <item android:drawable="@drawable/lockscreen_09" android:duration="200"/>
    <item android:drawable="@drawable/lockscreen_10" android:duration="200"/>
    <item android:drawable="@drawable/lockscreen_11" android:duration="200"/>
    <item android:drawable="@drawable/lockscreen_12" android:duration="200"/>
    <item android:drawable="@drawable/lockscreen_13" android:duration="200"/>
    <item android:drawable="@drawable/lockscreen_14" android:duration="200"/>
    <item android:drawable="@drawable/lockscreen_15" android:duration="200"/>
    <item android:drawable="@drawable/lockscreen_16" android:duration="200"/>
    <item android:drawable="@drawable/lockscreen_17" android:duration="200"/>

</animation-list>
  • 在Activity中,该动画添加到了ImageView中。特别要 注意 的是:AnimationDrawablestart()方法不能在Activity的onCreate()方法中调用,因为AnimationDrawable有可能在加载的时候还没有完全加载到Window上,所以最好的使用时机是onWindowFocusChanged()方法中。
		daiv.setBackgroundResource(R.drawable.drawable_animation);
        animationDrawable = (AnimationDrawable) daiv.getBackground();
        animationDrawable.start();
代码实现
public void animationDrawable() {
        //创建一个AnimationDrawable
        AnimationDrawable animationDrawable1 = new AnimationDrawable();
        //准备好资源图片
        int[] ids = {R.drawable.lockscreen_01, R.drawable.lockscreen_02, R.drawable.lockscreen_03, R.drawable.lockscreen_04, R.drawable.lockscreen_05};
        //通过for循环添加每一帧动画
        for (int i = 0; i < ids.length; i++) {
            Drawable frame = getResources().getDrawable(ids[i]);
            //设定时长
            animationDrawable1.addFrame(frame, 200);
        }
        animationDrawable1.setOneShot(false);
        //将动画设置到背景上
        daiv.setBackground(animationDrawable1);
        //开启帧动画
        animationDrawable1.start();
    }
在指定地方播放帧动画
public class Frame extends android.support.v7.widget.AppCompatImageView {

    private AnimationDrawable anim;

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

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

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

    public void setAnim(AnimationDrawable anim) {
        this.anim = anim;
    }

    public void setLocation(int top, int left) {
        this.setFrame(left, top, left + 200, top + 200);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        try {
            Field mCurFrame = AnimationDrawable.class.getDeclaredField("mCurFrame");
            mCurFrame.setAccessible(true);
            int frameInt = mCurFrame.getInt(anim);
            if (frameInt == anim.getNumberOfFrames() - 1) {
                setVisibility(INVISIBLE);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        super.onDraw(canvas);
    }
}
		FrameLayout frameLayout = new FrameLayout(this);
        setContentView(frameLayout);
        final Frame frame = new Frame(this);
        frame.setBackgroundResource(R.drawable.drawable_animation);//animation-list
        frame.setVisibility(View.INVISIBLE);
        final AnimationDrawable anim = (AnimationDrawable) frame.getBackground();
        frame.setAnim(anim);
        frameLayout.addView(frame);
        frameLayout.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                //设置按下才产生动画效果
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    anim.stop();
                    float x = event.getX();
                    float y = event.getY();
                    frame.setLocation((int) y - 40, (int) x - 20);
                    frame.setVisibility(View.VISIBLE);
                    anim.start();
                }
                return false;
            }
        });
实现效果

AnimatedVectorDrawable

  • 矢量图是一种无需像素化或进行模糊处理即可缩放的图片。借助 AnimatedVectorDrawable 类(以及用于实现向后兼容的 AnimatedVectorDrawableCompat),您可以为矢量图添加动画效果,例如旋转或更改路径数据以将其变为其他图片。不过在学习AnimatedVectorDrawable之前,我们有必要从VectorDrawable(矢量图)开始,因为熟悉了矢量图,才可以添加动画。
VectorDrawable
  • VectorDrawable 是一种矢量图形,在 XML 文件中定义为一组点、线条和曲线及其相关颜色信息。使用矢量图的主要优势在于图片可缩放。您可以在不降低显示质量的情况下缩放图片,也就是说,可以针对不同的屏幕密度调整同一文件的大小,而不会降低图片质量。这不仅能缩减 APK 文件大小,还能减少开发者维护工作。您还可以对动画使用矢量图片,具体方法是针对各种显示屏分辨率使用多个 XML 文件,而不是多张图片。
  • AndroidStudio中也提供了一些VectorDrawable,使用步骤如下:
  • File—>new—>Vector Asset;打开界面如图所示,点击Clip Art右边的图标可以选择Android自带的VectorDrawable,同时也可以从外部选择SVG,PSD格式的图片生成VectorDrawable。点击next,finish。图片的XML代码如下。
  • 这里我们特别说明一下SVG(Scalable Vector Graphics),意为可缩放矢量图,SVG基于可扩展标记语言,使用XML格式定义图形,与其它图像格式相比,使用SVG的优势在于:
    • SVG 可被非常多的工具读取和修改(比如记事本)。
    • SVG 与 JPEG 和 GIF 图像比起来,尺寸更小,且可压缩性更强。
    • SVG 是可伸缩的。
    • SVG 图像可在任何的分辨率下被高质量地打印。
    • SVG 可在图像质量不下降的情况下被放大。
    • SVG 图像中的文本是可选的,同时也是可搜索的(很适合制作地图)。
    • SVG 可以与 Java 技术一起运行。
    • SVG 是开放的标准。
    • SVG 文件是纯粹的 XML。
<vector android:height="24dp" android:tint="#FF0006"
    android:viewportHeight="24.0" android:viewportWidth="24.0"
    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="#FF000000" android:pathData="M 0,0 L50,100 L100,0Z"/>
</vector>
  • 看到这个是不是直接很懵逼, android:viewportHeight定义矢量图视图的高度,视图就是矢量图path路径数据所绘制的虚拟画布。android:viewportWidth定义矢量图视图的宽度。画布宽高均为24,意味着绘制了一个24*24的画布,path路径必须在这个画布大小里去绘制,超出画布就不显示了。android:fillColor定义填充路径的颜色,如果没有定义则不填充路径。最难理解的就是android:pathData,它的值是一系列字母和数字,为了告诉解析器如何绘制的,这些字母代表一些指令,具体如下:

    • M(m):相当于Path.moveTo(),开始新一段的path。
    • L(l):相当于Path.lineTo(),移动到指定的点。
    • H(h):水平移动。
    • V(v):竖直移动。
    • C(c):三阶贝塞尔曲线。相当于Path.cubicTo()。
    • S(s):同C,但比C要更平滑。
    • Q(q):二阶贝塞尔曲线
    • T(t):同Q,但比q平滑。
    • A(a):弧线。相当于Path.arcTo()。
    • Z(z):关闭。相当于Path.close()。
  • 每一个指令都有大小写,大写表示绝对定位,小写表示相对定位(相对定位参考点是上一次坐标)。 由于绘制路径的复杂性,因此强烈建议您使用 SVG 编辑器来创建复杂的图形。

  • android:pathData="M 0,0 L50,100 L100,0Z"表示坐标(0,0)lineto(50,100)lineto(100,0)关闭,绘制出来是一个等腰三角形。

Vector语法
<vector>
<!--<group>用来把多个<path>组合在一起,进行相同的处理。-->
    <group>
        <path>
        <path>
    </group>
</vector>

——关于VectorDrawable的介绍就到这里!

使用 AnimatedVectorDrawable
  • 我们通常需要在三个XML文件中定义添加动画效果后的矢量图:
    • 矢量图,其中位于res/drawable/
    • 添加动画效果的矢量图,其中 位于 res/drawable/
    • 一个或多个Animator,其中 元素位于 res/animator/
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportHeight="100"
    android:viewportWidth="100">
    <group
        android:name="group">
        <path android:name="tangle"
            android:strokeColor="@color/colorAccent"
            android:strokeWidth="2"
            android:pathData="M 0,0 L50,100 L100,0Z"/>
    </group>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/vector_drawable">
    <target
        android:animation="@anim/anim_vector"
        android:name="tangle"/>
    <target
        android:animation="@anim/anim_group"
        android:name="group"/>

</animated-vector>
  • 两个动画,一个缩放动画(属性动画介绍参考Android动画目录),一个针对pathData的动画。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="5000"
        android:propertyName="scaleX"
        android:repeatCount="infinite"
        android:repeatMode="reverse"
        android:valueFrom="1"
        android:valueTo="0.5"
        android:valueType="floatType" />
   
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="2000"
        android:propertyName="pathData"
        android:repeatCount="infinite"
        android:repeatMode="reverse"
        android:valueFrom="M 0,0 L50,100 L100,0Z"
        android:valueTo="M 0,100 L50,0 L100,100Z"
        android:valueType="pathType" />
</set>
  @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        AnimatedVectorDrawable animatedVectorDrawable = (AnimatedVectorDrawable) vectoriv.getDrawable();
        animatedVectorDrawable.start();
    }
aapt合并xml
  • 这里有很多文件只是为了让一个动画可以绘制!如果矢量图动画在其他地方重复使用,这是实现矢量可绘制动画的最佳方法。如果它们只用于这个动画矢量绘图,那么有一个更紧凑的方法来实现它们。使用AAPT的内联资源格式,可以在同一个XML文件中定义所有三个资源。因为我们正在制作一个矢量图的动画效果,所以我们将文件放在res/drawable/下。
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt">
    <aapt:attr name="android:drawable">
        <vector xmlns:android="http://schemas.android.com/apk/res/android"
            android:width="200dp"
            android:height="200dp"
            android:viewportWidth="100"
            android:viewportHeight="100">
            <group android:name="group">
                <path
                    android:name="tangle"
                    android:strokeWidth="2"
           			android:pathData="M 0,0 L50,100 L100,0Z"
                    android:strokeColor="@color/colorAccent" />
            </group>
        </vector>
    </aapt:attr>

    <target android:name="tangle">
        <aapt:attr name="android:animation">
            <objectAnimator
        		android:duration="2000"
       			android:propertyName="pathData"
       			    <!--无限次infinite-->
       			android:repeatCount="infinite"
       			android:repeatMode="reverse"
       			android:valueFrom="M 0,0 L50,100 L100,0Z"
       			android:valueTo="M 0,100 L50,0 L100,100Z"
        		android:valueType="pathType" />

        </aapt:attr>
    </target>

    <target android:name="group">
        <aapt:attr name="android:animation">
            <objectAnimator
        		android:duration="5000"
        		android:propertyName="scaleX"
        		android:repeatCount="infinite"
        		android:repeatMode="reverse"
	        	android:valueFrom="1"
	        	android:valueTo="0.5"
		        android:valueType="floatType" />
        </aapt:attr>
    </target>

</animated-vector>
  • XML标记aapt:attr告诉aapt该标记的子标记应被视为资源并提取到其自己的资源文件中。使用这种方式更紧凑。实现效果相同。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.yanghujun.com/archives/animationdrawable

Buy me a cup of coffee ☕.