首语
- 这是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
中。特别要 注意 的是:AnimationDrawable
的start()
方法不能在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©:三阶贝塞尔曲线。相当于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该标记的子标记应被视为资源并提取到其自己的资源文件中。使用这种方式更紧凑。实现效果相同。