文章概览
- 1 Android动画概述
- 1.1 动画的分类
- 1.2 视图动画与属性动画的区别
- 2 视图动画View Animation
- 2.1 补间动画Tween Animation
- 2.1.1 XML中用标签实现补间动画
- 2.1.2 代码实现补间动画
- 2.2 逐帧动画Frame Animation
- 2.2.1 XML实现逐帧动画
- 2.2.2 代码实现逐帧动画
本系列将介绍以下内容:
1 Android动画概述
1.1 动画的分类
Android中共有两种类型的动画,View Animation(视图动画)和Property Animation(属性动画)。
其中View Animation包括Tween Animation(补间动画)和Frame Animation(逐帧动画),Property Animation包括ValueAnimator和ObjectAnimator。
在API Level 12(Android 3.1)时又引入了ViewPropertyAnimator来完善属性动画。
1.2 视图动画与属性动画的区别
1、引入时间不同
View Animation是在API Level 1(Android 1.0)时引入的;
Property Animation是在API Level 11(Android 3.0)时引入的;
ViewPropertyAnimator是在API Level 12(Android 3.1)时引入的。
因此,Property Animation是用来弥补View Animation的不足。
2、所在的包不同
View Animation中Tween Animation的相关API位于包android.view.animation中,Frame Animation的相关API位于包android.graphics.drawable中;
Property Animation相关API位于包android.animation中;
ViewPropertyAnimator类位于包android.view中。
3、动画类的命名不同
View Animation中的补间动画类都命名为XxxAnimation,逐帧动画由AnimationDrawable类单独完成;
Property Animation中的动画类都命名为XxxAnimator;
ViewPropertyAnimator使得view控件可以直接调用相关动画API,如textView.animate().alpha(0f)。
4、动画原理不同
视图动画中的补间动画主要是实现控件的渐入渐出、移动、旋转、缩放等效果,且并没有改变控件的相关属性的值,而逐帧动画主要是用来实现“动画”的。
视图动画只能对派生自View类的控件起作用,而属性动画是作用于控件属性,通过改变控件某一属性值来做动画的。
2 视图动画View Animation
视图动画是在API Level 1(Android 1.0)时引入的,包括补间动画和逐帧动画,它们有两种实现方式,一是在XML中用标签实现,二是用代码实现。
2.1 补间动画Tween Animation
补间动画位于包android.view.animation中,主要由抽象类Animation及其五个具体类来实现,其UML图(类图Class Diagram)如图所示:
抽象类Animation包含一个接口AnimationListener和两个具体类NoImagePreloadHolder、Description,详见源码。
补间动画有两种实现方式,一是使用标签,二是代码实现,其对应关系如下:
|
|
|
|
|
|
|
|
|
|
|
|
抽象类Animation及其全部5个子类:
package java.lang;
/**
* Cloneable是一个空接口,什么都没有
*/
public interface Cloneable {
}
package android.view.animation;
public abstract class Animation implements Cloneable {
...
private static class NoImagePreloadHolder {
public static final boolean USE_CLOSEGUARD
= SystemProperties.getBoolean("log.closeguard.Animation", false);
}
boolean mInitialized = false;
Interpolator mInterpolator;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 117519981)
private AnimationListener mListener;
@Override
protected Animation clone() throws CloneNotSupportedException {
final Animation animation = (Animation) super.clone();
animation.mPreviousRegion = new RectF();
animation.mRegion = new RectF();
animation.mTransformation = new Transformation();
animation.mPreviousTransformation = new Transformation();
return animation;
}
public void reset() {
mPreviousRegion.setEmpty();
mPreviousTransformation.clear();
mInitialized = false;
mCycleFlip = false;
mRepeated = 0;
mMore = true;
mOneMoreTime = true;
mListenerHandler = null;
}
public void cancel() {
if (mStarted && !mEnded) {
fireAnimationEnd();
mEnded = true;
guard.close();
}
// Make sure we move the animation to the end
mStartTime = Long.MIN_VALUE;
mMore = mOneMoreTime = false;
}
public boolean isInitialized() {
return mInitialized;
}
public void initialize(int width, int height, int parentWidth, int parentHeight) {
reset();
mInitialized = true;
}
public void setAnimationListener(AnimationListener listener) {
mListener = listener;
}
public void setInterpolator(Context context, @AnimRes @InterpolatorRes int resID) {
setInterpolator(AnimationUtils.loadInterpolator(context, resID));
}
public void setInterpolator(Interpolator i) {
mInterpolator = i;
}
public void setDuration(long durationMillis) {
if (durationMillis < 0) {
throw new IllegalArgumentException("Animation duration cannot be negative");
}
mDuration = durationMillis;
}
public void setRepeatMode(int repeatMode) {
mRepeatMode = repeatMode;
}
public void setRepeatCount(int repeatCount) {
if (repeatCount < 0) {
repeatCount = INFINITE;
}
mRepeatCount = repeatCount;
}
public Interpolator getInterpolator() {
return mInterpolator;
}
...
protected static class Description {
...
}
public static interface AnimationListener {
void onAnimationStart(Animation animation);
void onAnimationEnd(Animation animation);
void onAnimationRepeat(Animation animation);
}
}
package android.view.animation;
public class AlphaAnimation extends Animation {
...
}
package android.view.animation;
public class ScaleAnimation extends Animation {
...
}
package android.view.animation;
public class TranslateAnimation extends Animation {
...
}
package android.view.animation;
public class RotateAnimation extends Animation {
...
}
package android.view.animation;
public class AnimationSet extends Animation {
...
public void addAnimation(Animation a) {
...
}
...
}
2.1.1 XML中用标签实现补间动画
标签文件应放在res/anim目录下,使用R.anim.xxx访问;也可以放在res/drawable目录下,使用R.drawable.xxx访问。
应用举例,定义几个动画标签文件:
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:fillBefore="true"
android:fromAlpha="1.0"
android:toAlpha="0.1">
</alpha>
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:fillAfter="true"
android:fromDegrees="0"
android:pivotX="100%"
android:pivotY="100%"
android:toDegrees="-650">
</rotate>
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="700"
android:fillBefore="true"
android:fromXScale="1.0"
android:fromYScale="1.2"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="1"
android:repeatMode="reverse"
android:toXScale="0.4"
android:toYScale="0.6" />
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="-80"
android:toYDelta="-80">
</translate>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:fillAfter="true">
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0" />
<scale
android:fromXScale="0.0"
android:fromYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.4"
android:toYScale="1.4" />
<rotate
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="720" />
<translate
android:duration="2000"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="-80"
android:toYDelta="-80" />
</set>
布局文件
<?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">
<Button
android:id="@+id/anim_xml_activity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="XML生成动画"
android:textAllCaps="false"
android:textSize="18sp" />
<Button
android:id="@+id/anim_java_activity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="代码生成动画"
android:textAllCaps="false"
android:textSize="18sp" />
</LinearLayout>
和
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="Start Anim"
android:textAllCaps="false"
android:textSize="18sp" />
<TextView
android:id="@+id/tv"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center_horizontal"
android:background="@android:color/darker_gray"
android:gravity="center"
android:text="Hello World"
android:textSize="18sp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
点击按钮时给TextView应用动画
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.TextView;
public class AnimationXMLActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.animation_xml_activity);
final TextView tv = (TextView) findViewById(R.id.tv);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// 缩放动画
Animation scaleAnim = AnimationUtils.loadAnimation(AnimationXMLActivity.this, R.anim.scaleAnim);
// 透明度动画
Animation alphaAnim = AnimationUtils.loadAnimation(AnimationXMLActivity.this, R.anim.alphaAnim);
// 旋转动画
Animation rotateAnim = AnimationUtils.loadAnimation(AnimationXMLActivity.this, R.anim.rotateAnim);
// 平移动画
Animation translateAnim = AnimationUtils.loadAnimation(AnimationXMLActivity.this, R.anim.translateAnim);
// 动画集合
Animation setAnim = AnimationUtils.loadAnimation(AnimationXMLActivity.this, R.anim.setAnim);
// 开启动画
tv.startAnimation(scaleAnim);
// tv.startAnimation(alphaAnim);
// tv.startAnimation(rotateAnim);
// tv.startAnimation(translateAnim);
// tv.startAnimation(setAnim);
}
});
}
}
标签使用较简单,效果图:
用法要点1,使用AnimationUtils.loadAnimation(Context context, @AnimRes int id)加载动画标签,详见源码。
注意:
Property Animation(属性动画)用XML形式实现动画的方式是调用类方法AnimatorInflater.loadAnimator(xxx),该方法返回值是Animator。
package android.view.animation
public class AnimationUtils {
...
public static Animation loadAnimation(Context context, @AnimRes int id)
throws NotFoundException {
XmlResourceParser parser = null;
try {
parser = context.getResources().getAnimation(id);
return createAnimationFromXml(context, parser);
} catch (XmlPullParserException | IOException ex) {
throw new NotFoundException(
"Can't load animation resource ID #0x" + Integer.toHexString(id), ex);
} finally {
if (parser != null) parser.close();
}
}
...
}
用法要点2,使用View的startAnimation(Animation animation)开启动画,源码:
package android.view;
@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
...
protected Animation mCurrentAnimation = null;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private ViewPropertyAnimator mAnimator = null;
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
public ViewPropertyAnimator animate() {
if (mAnimator == null) {
mAnimator = new ViewPropertyAnimator(this);
}
return mAnimator;
}
...
}
2.1.2 代码实现补间动画
标签中定义的效果可以用代码实现,例如前例中的alpha标签用代码实现就是:
final TextView tv = (TextView) findViewById(R.id.tv);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0.1f);
alphaAnim.setDuration(3000);
alphaAnim.setFillBefore(true);
tv.startAnimation(alphaAnim);
}
其余标签的代码实现方法类似,参考下述代码:
import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.view.View;
import android.view.animation.*;
import android.widget.TextView;
public class AnimationJAVAActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.animation_java_activity);
final TextView tv = (TextView) findViewById(R.id.tv);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// ScaleAnimation
ScaleAnimation scaleAnim = new ScaleAnimation(
0.0f,
1.4f,
0.0f,
1.4f,
Animation.RELATIVE_TO_SELF,
0.5f,
Animation.RELATIVE_TO_SELF,
0.5f
);
scaleAnim.setDuration(700);
// AlphaAnimation
AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0.1f);
alphaAnim.setDuration(3000);
alphaAnim.setFillBefore(true);
// RotateAnimation
RotateAnimation rotateAnim = new RotateAnimation(
0,
-650,
Animation.RELATIVE_TO_SELF,
0.5f,
Animation.RELATIVE_TO_SELF,
0.5f
);
rotateAnim.setDuration(3000);
rotateAnim.setFillAfter(true);
// TranslateAnimation
TranslateAnimation translateAnim = new TranslateAnimation(
Animation.ABSOLUTE,
0,
Animation.ABSOLUTE,
-80,
Animation.ABSOLUTE,
0,
Animation.ABSOLUTE,
-80
);
translateAnim.setDuration(2000);
translateAnim.setFillBefore(true);
// AnimationSet的用法
Animation alpha_Anim = new AlphaAnimation(1.0f, 0.1f);
Animation scale_Anim = new ScaleAnimation(
0.0f,
1.4f,
0.0f,
1.4f,
Animation.RELATIVE_TO_SELF,
0.5f,
Animation.RELATIVE_TO_SELF,
0.5f
);
Animation rotate_Anim = new RotateAnimation(
0,
720,
Animation.RELATIVE_TO_SELF,
0.5f,
Animation.RELATIVE_TO_SELF,
0.5f
);
AnimationSet setAnim = new AnimationSet(true);
// AnimationSet的addAnimation(Animation a)添加动画
setAnim.addAnimation(alpha_Anim);
setAnim.addAnimation(scale_Anim);
setAnim.addAnimation(rotate_Anim);
setAnim.setDuration(3000);
setAnim.setFillAfter(true);
// tv.startAnimation(scaleAnim);
// tv.startAnimation(alphaAnim);
// tv.startAnimation(rotateAnim);
// tv.startAnimation(translateAnim);
tv.startAnimation(setAnim);
}
});
}
}
Animation有动画监听方法,其用法是:
private void animListener(final TextView tv) {
final RotateAnimation rotateAnim = new RotateAnimation(
0,
-650,
Animation.RELATIVE_TO_SELF,
0.5f,
Animation.RELATIVE_TO_SELF,
0.5f
);
rotateAnim.setDuration(3000);
rotateAnim.setFillAfter(true);
ScaleAnimation scaleAnim = new ScaleAnimation(
0.0f,
1.4f,
0.0f,
1.4f,
Animation.RELATIVE_TO_SELF,
0.5f,
Animation.RELATIVE_TO_SELF,
0.5f
);
scaleAnim.setDuration(700);
scaleAnim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
}
public void onAnimationEnd(Animation animation) {
tv.startAnimation(rotateAnim);
}
public void onAnimationRepeat(Animation animation) {
}
});
tv.startAnimation(scaleAnim);
}
效果图参考上图示例。
2.2 逐帧动画Frame Animation
逐帧动画就是一帧一帧的播放动画,就像放电影一样。
逐帧动画既可以通过XML实现,也可以用代码实现,且这两种实现方式均由AnimationDrawable类完成。它位于包android.graphics.drawable中,是Drawable抽象类的间接子类,它主要用来创建一个逐帧动画,并对帧进行拉伸,把它设置为View的背景,即可使用AnimationDrawable.start()播放动画,其类图如图所示:
Drawable内部定义了一个接口Callback和一个抽象类ConstantState;
DrawableContainer内部定义了一个抽象类DrawableContainerState(继承自ConstantState)和具体类BlockInvalidateCallback;
AnimationDrawable内部定义了具体类AnimationState(继承自DrawableContainerState),其源码见下文。
Animatable接口、Drawable抽象类、DrawableContainer类、AnimationDrawable类均位于包android.graphics.drawable中。
2.2.1 XML实现逐帧动画
逐帧动画也可以用XML方式实现,它使用< animation-list />标签,其具体用法是:
1、将一帧一帧的动画图片存放在res.drawable或res.mipmap目录下
2、在res.anim或res.drawable中新建xml文件,如playing_ani.xml,引用存放的那几帧图片,如
<?xml version="1.0" encoding="UTF-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item
android:drawable="@drawable/list_icon_gif_playing1"
android:duration="60" />
<item
android:drawable="@drawable/list_icon_gif_playing2"
android:duration="60" />
<item
android:drawable="@drawable/list_icon_gif_playing3"
android:duration="60" />
</animation-list>
3、在布局文件中定义ImageView(继承自View),并在其属性android:src或android:background中引用动画帧。
使用android:src时,用ImageView.getDrawable()获取资源。
使用android:background时,用View.getBackground()获取资源。
上述两种获取资源的方法都返回一个Drawable,区别是该Drawable是定义在View中还是ImageView中。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<ImageView
android:id="@+id/frame_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:src="@drawable/playing_ani" />
<ImageView
android:id="@+id/frame_image2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:background="@drawable/playing_ani" />
</LinearLayout>
ImageView继承自View,且都与Drawable关联。
package android.graphics.drawable;
public abstract class Drawable {
...
public interface Callback {
void invalidateDrawable(@NonNull Drawable who);
void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when);
void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what);
}
public static abstract class ConstantState {
...
}
...
}
package android.graphics.drawable;
public class BitmapDrawable extends Drawable {
...
final static class BitmapState extends ConstantState {
...
}
}
package android.view;
@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
...
@ViewDebug.ExportedProperty(deepExport = true, prefix = "bg_")
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private Drawable mBackground;
@InspectableProperty
public Drawable getBackground() {
return mBackground;
}
...
}
package android.widget;
@RemoteView
public class ImageView extends View {
...
@UnsupportedAppUsage
private Drawable mDrawable = null;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private BitmapDrawable mRecycleableBitmapDrawable = null;
/**
* Gets the current Drawable, or null if no Drawable has been
* assigned.
*
* @return the view's drawable, or null if no drawable has been
* assigned.
*/
@InspectableProperty(name = "src")
public Drawable getDrawable() {
if (mDrawable == mRecycleableBitmapDrawable) {
// Consider our cached version dirty since app code now has a reference to it
mRecycleableBitmapDrawable = null;
}
return mDrawable;
}
...
}
4、使用AnimationDrawable开始动画
ImageView imgView = findViewById(R.id.frame_image);
// 此处要强转为AnimationDrawable
AnimationDrawable animDrawable = (AnimationDrawable) imgView.getDrawable();
//AnimationDrawable animDrawable = (AnimationDrawable) imgView.getBackground();
animDrawable.start();
与AnimationDrawable有关的源码如下所示:
package android.graphics.drawable;
/**
* Interface that drawables supporting animations should implement.
*/
public interface Animatable {
void start();
void stop();
boolean isRunning();
}
package java.lang;
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
package android.graphics.drawable;
public class DrawableContainer extends Drawable implements Drawable.Callback {
...
public abstract static class DrawableContainerState extends ConstantState {
public final int addChild(Drawable dr) {
...
}
...
}
private static class BlockInvalidateCallback implements Drawable.Callback {
...
}
}
package android.graphics.drawable;
public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable {
private AnimationState mAnimationState;
@UnsupportedAppUsage
private int mCurFrame = 0;
private boolean mRunning;
private boolean mAnimating;
@Override
public void start() {
mAnimating = true;
if (!isRunning()) {
// Start from 0th frame.
setFrame(0, false, mAnimationState.getChildCount() > 1
|| !mAnimationState.mOneShot);
}
}
@Override
public void stop() {
mAnimating = false;
if (isRunning()) {
mCurFrame = 0;
unscheduleSelf(this);
}
}
@Override
public boolean isRunning() {
return mRunning;
}
public void setOneShot(boolean oneShot) {
mAnimationState.mOneShot = oneShot;
}
public void addFrame(@NonNull Drawable frame, int duration) {
mAnimationState.addFrame(frame, duration);
if (!mRunning) {
setFrame(0, true, false);
}
}
private void setFrame(int frame, boolean unschedule, boolean animate) {
if (frame >= mAnimationState.getChildCount()) {
return;
}
mAnimating = animate;
mCurFrame = frame;
selectDrawable(frame);
if (unschedule || animate) {
unscheduleSelf(this);
}
if (animate) {
// Unscheduling may have clobbered these values; restore them
mCurFrame = frame;
mRunning = true;
scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]);
}
}
...
private final static class AnimationState extends DrawableContainerState {
private int[] mDurations;
private boolean mOneShot = false;
public void addFrame(Drawable dr, int dur) {
int pos = super.addChild(dr);
mDurations[pos] = dur;
}
...
}
...
}
一个完整的XML实现的Demo:
布局文件main_activity
<?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">
<Button
android:id="@+id/xml_create"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="XML生成动画"
android:textAllCaps="false"
android:textSize="18sp" />
<Button
android:id="@+id/java_create"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="代码生成动画"
android:textAllCaps="false"
android:textSize="18sp" />
</LinearLayout>
布局文件frame_anim_xml_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal">
<Button
android:id="@+id/start_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="start"
android:textAllCaps="false"
android:textSize="18sp" />
<Button
android:id="@+id/stop_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="150dp"
android:text="stop"
android:textAllCaps="false"
android:textSize="18sp" />
<ImageView
android:id="@+id/frame_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:background="@drawable/playing_ani" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
实现代码
package com.example.FrameAnim;
import android.app.Activity;
import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
public class FrameAnimXMLActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.frame_anim_xml_activity);
ImageView image = (ImageView) findViewById(R.id.frame_image);
final AnimationDrawable anim = (AnimationDrawable) image.getBackground();
findViewById(R.id.start_btn).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
anim.start();
}
});
findViewById(R.id.stop_btn).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
anim.stop();
}
});
}
}
效果图:
2.2.2 代码实现逐帧动画
代码实现方式主要是由AnimationDrawable.addFrame(xxx)完成,它可以添加动画帧并设置持续时间。
布局文件与XML实现方式一样,需要有一个ImageView但不需要设置其src或background属性。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<ImageView
android:id="@+id/frame_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp" />
</LinearLayout>
主要是实现代码与XML方式有所区别,该方式不需要在xml中引用帧图,全部在代码中添加:
package com.example.FrameAnim;
import android.app.Activity;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.widget.ImageView;
public class FrameAnimJAVAActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.frame_anim_java_activity);
ImageView image = findViewById(R.id.frame_image);
final AnimationDrawable anim = new AnimationDrawable();
// 添加14帧图片
for (int i = 1; i <= 14; i++) {
// 难点:通过文件名拿到对应资源的ID(请看getIdentifier(xxx)源码)
int id = getResources().getIdentifier("list_icon_gif_playing" + i, "drawable", getPackageName());
Drawable drawable = getResources().getDrawable(id);
anim.addFrame(drawable, 60);
}
anim.setOneShot(false);
image.setBackgroundDrawable(anim);
anim.start();
}
}
效果图见上图示例。
至此,逐帧动画的代码实现方式也由AnimationDrawable完成了。
参考文献:
[1] UML中的类图及类图之间的关系
[2] 启舰.Android自定义控件开发入门与实战[M].北京:电子工业出版社,2018
微信公众号:TechU