Android动画(一):视图动画

文章概览

  • 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 代码实现逐帧动画

本系列将介绍以下内容:
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
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/505299.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL 之 数据库操作 及 表操作

&#x1f389;欢迎大家观看AUGENSTERN_dc的文章(o゜▽゜)o☆✨✨ &#x1f389;感谢各位读者在百忙之中抽出时间来垂阅我的文章&#xff0c;我会尽我所能向的大家分享我的知识和经验&#x1f4d6; &#x1f389;希望我们在一篇篇的文章中能够共同进步&#xff01;&#xff01;&…

(一)kafka实战——kafka源码编译启动

前言 本节内容是关于kafka消息中间键的源码编译&#xff0c;并通过idea工具实现kafka服务器的启动&#xff0c;使用的kafka源码版本是3.6.1&#xff0c;由于kafka源码是通过gradle编译的&#xff0c;以及服务器是通过scala语言实现&#xff0c;我们要预先安装好gradle编译工具…

了解XSS和CSRF攻击与防御

什么是XSS攻击 XSS&#xff08;Cross-Site Scripting&#xff0c;跨站脚本攻击&#xff09;是一种常见的网络安全漏洞&#xff0c;它允许攻击者在受害者的浏览器上执行恶意脚本。这种攻击通常发生在 web 应用程序中&#xff0c;攻击者通过注入恶意脚本来利用用户对网站的信任&…

springboot论坛管理系统

论坛管理系统 摘要&#xff1a; 在社会快速发展的影响下&#xff0c;论坛管理系统继续发展&#xff0c;使论坛管理系统的管理和运营比过去十年更加信息化。依照这一现实为基础&#xff0c;设计一个快捷而又方便的网上论坛管理系统是一项十分重要并且有价值的事情。对于传统的论…

Linux简单命令

Linux简单命令 本文是自己学习过程中的一些记录&#xff0c;对于熟悉的部分并未全部列出&#xff0c;仅供参考 内核架构图 一切皆是文件 常用的linux命令 用户的管理 修改密码&#xff1a;passwd 创建一个新用户&#xff1a;useradd 用户名给新用户设置密码&#xff1a;…

【Linux】详解文件系统以及周边知识

一、磁盘的基本知识 磁盘中可以被划分成一个一个的环&#xff0c;每个环都是一个磁道。每个磁道又可以被均分成一个一个的扇区&#xff0c;扇区是磁盘IO的基本单位&#xff08;想要修改扇区中的一个比特位就必须把该扇区的全部比特位都加载到内存中&#xff09;。磁盘中的盘面&…

C++入门(函数重载、缺省参数、引用)

目录 函数重载函数重载的概念函数重载的原理----名字的修饰 缺省参数缺省参数的分类 引用引用的特征使用场景 函数重载 函数重载的概念 在自然语言中&#xff0c;相同的一个词可能有多重含义&#xff0c;人们可以通过上下文来判断这个词的具体意思&#xff0c;在C中也存在这种…

虚函数和纯虚函数

虚函数 被virtual修饰的成员函数称为虚函数 定义一个函数为虚函数&#xff0c;是为了使用基类指针调用子类函数。虚函数&#xff0c;不代表函数不被实现。只有纯虚函数才不被实现&#xff0c;纯虚函数定义了一个接口&#xff0c;起到规范的作用。 #include <iostream>…

快讯!TiDB v8 发版!超硬核 v8 引擎!

TiDB 是 PingCAP 公司自主设计、研发的开源分布式关系型数据库&#xff0c;是一款同时支持在线事务处理与在线分析处理 (Hybrid Transactional and Analytical Processing, HTAP) 的融合型分布式数据库产品。 具备水平扩容或者缩容、金融级高可用、实时 HTAP、云原生的分布式数…

初识C++ · 入门(2)

目录 1 引用 1.1引用的概念 1.2 引用的特性 2 传值&#xff0c;传引用的效率 3 引用和指针的区别 4 内联函数 4.1 内联函数的定义 4. 2 内联函数的特性 5 关键字auto 5.1关于命名的思考 5.2 关于auto的发展 5.3 auto使用规则 6 范围for的使用 7 空指针 1 引用 …

瑞吉外卖实战学习--9、mybatisPlus公共字段自动填充

mybatisPlus公共字段自动填充 前言实现步骤实体类添加注解按照框架要求编写元数据对象处理器&#xff0c;在此类中统一为公共字段赋值&#xff0c;此类需要实现MetaObjectHandler接口1、在创建和更新的时候修改创建和更新的时候自动填充时间2、如何获取到当前的id 测试结果 前言…

【MySQL】内外连接——内连接、外连接、左外连接、右外连接、内外连接的区别、左外连接和右外连接的区别

文章目录 MySQLMySQL表的内连接和外连接1. 内连接2. 外连接2.1 左外连接2.2 右外连接 3. 内外连接的区别4. 左外连接和右外连接的区别 MySQL MySQL表的内连接和外连接 MySQL 中的内连接&#xff08;INNER JOIN&#xff09;和外连接&#xff08;包括左外连接 LEFT JOIN 和右外连…

腾讯2024实习生在线笔试-0331

Q1 小红的图上染色 小红拿到了一个无向图&#xff0c;其中一些边被染成了红色。 小红定义一个点是“好点”&#xff0c;当且仅当这个点的所有邻边都是红边。 现在请你求出这个无向图“好点”的数量。 注&#xff1a;如果一个节点没有任何邻边&#xff0c;那么它也是好点。 …

【Web】NSSCTF Round#20 Basic 个人wp

目录 前言 真亦假&#xff0c;假亦真 CSDN_To_PDF V1.2 前言 感谢17&#x1f474;没让我爆零 真亦假&#xff0c;假亦真 直接getshell不行&#xff0c;那就一波信息搜集呗&#xff0c;先开dirsearch扫一下 扫的过程中先试试常规的robots.txt,www.zip,shell.phps,.git,.sv…

类的新功能

类的新功能 默认成员函数 在C11之前&#xff0c;一个类中有如下六个默认成员函数&#xff1a; 构造函数。拷贝构造函数赋值重载析构函数取地址重载函数const取地址函数 其中前四个默认成员函数最重要&#xff0c;后面两个默认成员函数一般不会用到&#xff0c;这里默认成员…

[MSSQL]理解SQL Server AlwaysOn AG的备份

AG提供了以下几种备份策略 下面来看看各项的解释 Prefer Secondary(首选辅助副本) 应在辅助副本上执行此可用性组的自动备份。如果没有可用的辅助副本,将在主副本上执行备份。 这个选项只是概念上的选项。基本上,用户可以从任何复制节点上执行备份命令。 我们可以在主副本…

《计时器》是谁演唱的?

是李亚云演唱的。李亚云&#xff0c;湖南省中峰富盛控股集团有限责任公司旗下全签艺人 &#xff0c;就读于南昌航空大学本科表演专业&#xff0c;&#xff0c;一个拥有“模特、练习生、爱豆和演员”多重身份的艺人&#xff0c;2003年3月10日出生于湖南长沙&#xff0c;7岁童星出…

设计模式深度解析:AI如何影响装饰器模式与组合模式的选择与应用

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 AI如何影响装饰器模式与组合模式的选择与应用 在今天这个快速发展的技术时代&#…

C++11新特性(二):更好用的 lambda 表达式和 function 包装器

目录 lambda 表达式 基本格式及参数列表 对于 lambda 捕捉列表的说明 function 包装器 bind 包装器 lambda 表达式 C11引入了lambda表达式&#xff0c;它是一种用于创建匿名函数的语法。lambda表达式可以被视为一个匿名函数对象&#xff0c;它可以在需要函数对象的地方使用…

并查集----格子游戏

并查集中最重要的是要搞懂&#xff1a; 不明白的可以拿纸自己先演示一番&#xff0c;find函数不仅能找到他们的祖先数&#xff0c;而且同时也能更新路径的子结点都等于祖先&#xff0c;然后以后寻找时会更加的方便&#xff01;