Android动画(一):视图动画

本系列将介绍以下内容:
Android动画概览

Android动画

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类图

Animation类图

抽象类Animation包含一个接口AnimationListener和两个具体类NoImagePreloadHolder、Description,详见源码。

补间动画有两种实现方式,一是使用标签,二是代码实现,其对应关系如下:

标签
代码类
< alpha >
AlphaAnimation
< scale >
ScaleAnimation
< translate >
TranslateAnimation
< rotate >
RotateAnimation
< set >
AnimationSet

抽象类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()播放动画,其类图如图所示:
AnimationDrawable类图

AnimationDrawable类图

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
在这里插入图片描述

相关推荐

  1. android ——动画

    2024-03-31 23:54:01       26 阅读
  2. Android共享元素动画

    2024-03-31 23:54:01       22 阅读

最近更新

  1. leetcode705-Design HashSet

    2024-03-31 23:54:01       5 阅读
  2. Unity发布webgl之后打开streamingAssets中的html文件

    2024-03-31 23:54:01       5 阅读
  3. vue3、vue2中nextTick源码解析

    2024-03-31 23:54:01       6 阅读
  4. 高级IO——React服务器简单实现

    2024-03-31 23:54:01       5 阅读
  5. 将图片数据转换为张量(Go并发处理)

    2024-03-31 23:54:01       4 阅读
  6. go第三方库go.uber.org介绍

    2024-03-31 23:54:01       6 阅读
  7. 前后端AES对称加密 前端TS 后端Go

    2024-03-31 23:54:01       7 阅读

热门阅读

  1. 记 SpringBoot 使用@RequestBody 接收不到参数

    2024-03-31 23:54:01       6 阅读
  2. of_get_named_gpio()函数解析

    2024-03-31 23:54:01       4 阅读
  3. go | channel direction、channel sync、channelbuffer

    2024-03-31 23:54:01       4 阅读