1.动画的定义
动画就是在两个不同的场景之间,添加过渡,让过程看起来尽可能自然(也可以说和现实世界的物理规律相符合),常见的场景有
- 表现层级关系: 为了展现层与层的关系,是抽屉,是打开,还是平级切换等等,让用户知道这个界面和上一个、下一个的关系。这已经是非常最常见的运用了,如QQ侧滑打开个人中心
-
与用户手势/遥控器 结合,更自然的动画表现 : 当用户手势操作的时候,让界面的动态走向更符合手指的运动,从而让用户感觉到是自己控制了界面的动向,而不是机械化的跳转,通常会配合震动、视图的选中状态来表现
-
减少不可避免的不适感: 启动,下载,加载,刷新,发送 等界面,适当的动画效果让等待变的可视化,用户就没有那么无聊,产品体验也会上升;
-
不易被察觉的动效 1.比如B站的点赞特效, 2.随着用户的操作,有的内容已经是用户不再关注的。这时候可以将他们隐藏起来,比如说标题栏。
2.动画的分类和基本使用
视图按照大类分为两类:ViewAnimation(视图动画)和 PropertyAnimator(属性动画);视图动画又包含Tween Animation(补间动画)和Frame Animation(帧动画),属性动画包含ValueAnimator 和 objectAnimator。
下面一个个来介绍一下他们的基本使用 先上效果图
2.1 补间动画--Alpha(透明度)
2.1.1. 使用xml 实现Alpha动画:
达到View的透明度从某个值变化到某个值的效果,现在res文件夹下,新建一个文件夹anim,存放xml文件,以后的关于动画的xml文件都放在这个文件夹下,xml文件夹下
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="1.0"
android:toAlpha="0.1"
android:duration = "2000"
android:fillAfter="false"
android:fillBefore="true"
>
</alpha>
然后再activity中可以这样使用
binding.btAlpha.setOnClickListener {
//加载动画
val animation = AnimationUtils.loadAnimation(this@AnimatorActivity,R.anim.alpha_anim)
//调用startAnimation 开始动画
//动画开始之后,会将imageview的透明度从1.0变为0.1,并保持不变
binding.ivCat.startAnimation(animation)
}
-
android:fromAlpha 取值为0.0 - 1.0 ,0.0表示全透明,1表示不透明
-
android:toAlpha 取值为0.0 - 1.0,0.0表示全透明,1表示不透明
-
android:duration 表示持续时间,持续4s
-
android:fillBefore:设置为true,动画结束之后,复原到初始状态,默认值为true
-
android:fillAfter 设置为true,动画结束之后,保持动画结束时的状态,默认值为false
-
android:fillEnabled 与fillBefore效果相同,
- 当fillBefore 和 fillAfter 同时设置为true 时,fillAfter的优先级高于fillBefore,动画将维持在最后一帧 透明度效果图如下:
2.1.2. 使用纯代码的形式
AlphaAnimation的构造函数为
1.AlphaAnimation(context:Context!,attrs:AttributeSet!) //用于从xml中构建Animation对象
2.AlphaAnimation(fromAlpha:Float,toAlpha:Float,) //代码构建对象
因此可以像下面这样写
val animation = AlphaAnimation(1.0f,0.2f)
animation.duration = 2000 // 设置动画持续时间
animation.fillAfter = true //动画结束之后维持在最后一帧
binding.ivCat.startAnimation(animation) //开始动画
透明度效果图如下:
2.2 补间动画--Scale(缩放)
2.2.1.使用xml实现scale动画,
scale标签用于缩放view,从而实现调整控件尺寸的效果
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXScale="1.0"
android:toXScale="1.4"
android:fromYScale="1.0"
android:toYScale="1.4"
android:duration = "1000"
android:fillAfter = "true">
</scale>
-
android:fromXscale 动画开始时,view在X轴上相对于自身的缩放比例,1.0 等于view 自身的大小,0.5为view 自身的0.5倍,2.0为view 自身的2 倍
-
android:toXscale 动画结束时,view在X 轴上相对于自身的缩放比例
-
android:fromYScale 动画开始时,view 在Y轴上相对于自身的缩放比例
-
android:toYScale 动画结束时,view在Y轴上相对于自身的缩放比例
-
在scale 标签中,pivotX 和pivotY 指定动画的开始坐标,有3种不同的取值,20,20%,20%p,20表示动画从距离左上角20单位的距离开始,20%表示动画从距离左边 (view的宽度*0.2)的位置开始,20%p 表示从距离view的最左上角边(父容器的宽度或者高度 乘以 0.2)的开始,view 在
-
在rotate 标签种,pivotX 和 pivotY 指定动画旋转中心的坐标
上面整个xml 表示这个view 在动画开始时,从自身的1.0倍变化到自身的1.4倍,持续时间为1s,并在动画结束之后维持在最后一帧。 调用过程为
binding.btScale.setOnClickListener {
val animation = AnimationUtils.loadAnimation(this@AnimatorActivity,R.anim.scale_anim)
binding.ivCat.startAnimation(animation)
}
2.2.2.使用纯代码的形式实现scale动画
//ScaleAnimation对象 的构建方法有4个,
//第一个是我们从xml中加载animation时会用到的构造函数,
//第2,3,4是我们自己使用代码构建ScaleAnimation对象时会使用的构造方法。
1.ScaleAnimation(context:Context!,attrs:AttributeSet!) //用于从xml中构建Animation对象
2.ScaleAnimation(fromx:Float,toX:Float,fromY:Float,toY:Float)//动画默认从左上角开始
3.ScaleAnimation(fromX:Float,toX:Float,fromY:Float,toY:Float,pivotX:Float,pivotY:Float)//动画从开发者指定的位置开始
4.ScaleAnimation(fromX:Float,toX:Float,fromY:Float,toY:Float,pivotXType:Int,povitXValue:Float,
povitYType:Int,povitYValue:Float)//动画从开发者指定的位置开始
在xml中
Animation.ABSOLUTE,Animation_RELATEIVE_TO_SELF和 Animation_RELATIVE_TO_PARENT
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXScale="1.0"
android:toXScale="1.4"
android:fromYScale="1.0"
android:toYScale="1.4"
android:povitX = "40%p"
android:povitY = "40%p"
android:duration = "1000"
android:fillAfter = "true">
</scale>
上面标签用代码表示可以表示为
val scaleAnimation = ScaleAnimation(1.0f,1.4f,1.0f,1.4f,40%,RELETIVE_TO_PARENT,
40%,RELETIVE_TO_PARENT)
scaleAnimation.apply{
duration = 1000
fillAfter = true
}
binding.ivCat.startAnimation(scaleAnimation) //开始动画
2.3 补间动画--translate(移动)
Translate 主要用于控件位置的移动,可以实现类似于上滑解锁,滑块等动画
2.3.1 使用xml定义translate动画
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta ="0"
android:toXDelta = "-180"
android:fromYDelta = "0"
android:toYDelta = "100"
android:duration = "2000"
android:fillAfter = "true"
/>
- fromXDelta 控件开始移动时左上角的位置的X轴坐标
- fromYDelta 控件开始移动时左上角的位置的Y轴坐标
- toXDelata 控件结束移动式左上角的位置的X轴坐标,负值代表向反方向移动,即向左移动,正值即向右边移动
- toYDelata 控件结束移动式左上角的位置的Y轴坐标,负值代表向反方向移动,即向上移动,正值即向下边移动
2.3.2 使用代码实现translate动画
TranslationAnimation对象的构造方法有3个,分别是
Translation(context:Context!,attrs:AttributeSet!) //用于从xml中加载动画
Translation(fromXDelta:Float,toXDelta:Float,fromYDelta:Float,toYDelta:Float)
Translation(fromXType:Int,fromXValue:Float,toXType:Int,toXValue:Float,fromYType:Int,fromYValue:Float toYType:Int,toYValue:Float)
fromXType,toXType,fromYType,toYType的取值同样是3个,分别是 - RELATIVE_TO_SELF - RELATIVE_TO_PARENT - RELATIVE_TO_ABSOULTE
实现和上面xml一样效果可以这样写
val tranalationAnimation = TranslationAnimation(0f,-180f,0f,200f).apply{
duration = 2000
fillAfter = true
}
binding.ivCat.startAnimation(animation)
2.4 补间动画--rotate(旋转动画)
2.4.1 使用xml实现如下
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="650"
android:pivotX="0"
android:pivotY="0"
android:duration = "4000"
android:fillAfter = "true"
/>
使用
binding.btRotate.setOnClickListener {
val animation = AnimationUtils.loadAnimation(this@AnimatorActivity,R.anim.rotate_anim)
binding.ivCat.startAnimation(animation)
}
-
android:fromDegrees 旋转的开始角度
-
android:toDegrees 旋转的结束角度,正数为顺时针方向,负数为逆时针方向
-
pivotX 旋转动画的中心的横坐标,可以为整数,百分数,百分数+p,20,30%,30%p类似于上面的解释
-
pivotY 旋转动画的中心的纵坐标
-
android:interpolator 用于设定插值器,其实就是指定动画效果,比如先慢后快,弹跳效果
2.4.2 实现代码实现旋转动画
构造函数如下:
TranslateAnimation(context:Context!,attrs:AttributeSet) //从xml中加载动画
TranslateAnimation(fromDegrees:Float,toDegrees:Float)
TranslateAnimation(fromDegress:Float,toDegrees:Float,pivotX:Float,pivotY:Float)
TranslateAnimation(fromDegress:Float,toDegrees:Float,pivotXType:Int,pivotXValue:Float,pivotType:Int,pivotYValue:Float)
pivotXType和pivotYType的value的和上面TranslationAnimation的值相同
实现类似2.4.1的效果可以使用代码如下
val rotateAnimation = RotateAnimation(0f,-650f,Animation_TO_ABSOLUTE,0,Animation_TO_ABSOLUTE,0).apply{
duration = 4000
fillAfter = true
}
2.5 动画集合
前面的动画只能完整特定的动画效果,而set标签可以将这些标签都包含在一个容器里面,一起生效,下面是一个例子
2.5.1 xml实现如下
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:repeatMode="reverse"
android:duration="2000"
>
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:repeatCount = "4"/>
<scale
android:fromXScale="0.5"
android:toXScale="1.4"
android:fromYScale="0.5"
android:toYScale="1.4"
android:pivotY="50%"
android:pivotX="50%"
android:repeatCount = "4"/>
<rotate
android:fromDegrees="0"
android:toDegrees="710"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount = "4"
/>
</set>
上面的set 标签实现了透明度从0到1.0,长度和宽度从0.5拉伸到1.4倍,并旋转710,重复4次,单次持续时间为2s,重复模式为reverse
2.5.2 纯代码实现如下
val animationSet = AnimationSet(true)
val alphaAnimation = AlphaAnimation(0f,1f).apply { repeatCount = 4 }
val scaleAnimation = ScaleAnimation(0.5f,1.4f,0.5f,1.4f,
Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f).apply { repeatCount = 4 }
val rotateAnimation = RotateAnimation(0f,710f,0.5f,0.5f).apply { repeatCount = 4 }
animationSet.apply {
addAnimation(alphaAnimation)
addAnimation(scaleAnimation)
addAnimation(rotateAnimation)
duration = 2000
fillAfter = true
repeatMode = Animation.REVERSE //重复时,从最终状态向开始状态变化
}
binding.ivCat.startAnimation(animationSet)
2.6 视图动画的其他方法
AlphaAnimation、ScaleAnimation、TranslateAnimation、RotateAnimation、AnimationSet都是Animation类的抽象类,Animation里面定义了一些公共方法 - void cancel() 取消动画 ,例如 animationSet.cancel(),scaleAnimation.cancel()
-
void reset() 将view 重置到动画开始的状态
-
void setAnimationListener(Animation.AnimationListener listener) 设置动画监听,可以在某个时刻进行事件的处理
animationSet.setAnimationListener(object :Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {
TODO("Not yet implemented")
//动画开始时
}
override fun onAnimationEnd(animation: Animation?) {
TODO("Not yet implemented")
//动画结束时
}
override fun onAnimationRepeat(animation: Animation?) {
//动画重复时
}
})
2.7 视图动画的优缺点总结:
- 优点: 可以实现基本的缩放,旋转,平移,透明度变换,以及这四种变换的组 合动画
- 缺点: 不能实现复杂动画效果。 可以实现组合动画(AnimationSet),但是组合动画只能同时播,不能控制动画播放的先后顺序 不能监听动画的执行过程,只能监听到动画开始,结束和重复事件。
2.8 视图动画的原理解析
2.8.1 动画开始的入口
val translateAnimation = TranslateAnimation(0f,180f,0f,200f)
translateAnimation.duration = 2000
translateAnimation.fillAfter = true
//动画开始
binding.ivCat.startAnimation(translateAnimation)
跟踪下去
// View.java中
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
//设置关联动画
setAnimation(animation);
//用于指示此视图的父级应清除其缓存。此功能用于强制父级重建其显示列表(当硬件加速时),这在视图的各种父级管理属性发生更改时是必要的,例如 alpha、translationXY、scrollXY、scaleXY 和 rotationXY。此方法仅清除父缓存,不会导致无效事件
invalidateParentCaches();
//触发视图重绘
invalidate(true);
}
最后跟踪到view.java中
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if (mGhostView != null) {
mGhostView.invalidate(true);
return;
}
if (skipInvalidate()) {
return;
}
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
//部分代码省略
// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
//执行ViewParent的invalidateChild方法
p.invalidateChild(this, damage);
}
//部分代码省略
}
}
}
注意到p的类型是ViewParent,是一个抽象类,我们找到它的子类ViewGroup ViewGroup中
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
/***部分代码省略***/
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
/***部分代码省略***/
do {
/***部分代码省略***/
//向顶部的View便利找到根View,即:ViewRootImpl
//执行ViewRootImpl的invalidateChildInParent方法
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
// Account for transform on current parent
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
dirty.set((int) (boundingRect.left - 0.5f),
(int) (boundingRect.top - 0.5f),
(int) (boundingRect.right + 0.5f),
(int) (boundingRect.bottom + 0.5f));
}
}
/***部分代码省略***/
} while (parent != null);
}
}
}
这里调用了函数invalidateChildInParent(),需要注意的是这里这个函数的实现有两个,一个是ViewGroup中,而另一个是ViewRootImpl。 ViewRootImpl中:
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
/***部分代码省略***/
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
/***部分代码省略***/
invalidateRectOnScreen(dirty);
return null;
}
private void invalidateRectOnScreen(Rect dirty) {
/***部分代码省略***/
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
//开始View的绘制任务
scheduleTraversals();
}
}
}
函数scheduleTraversals()的逻辑其实是执行一个Runnable,而这个Runnable其实就是去执行函数doTraversal(),而函数doTraversal()会调用performTraversals(),到这里我们发现它开始重绘了。总体来说就是动画的执行会导致整个View Tree重绘,但是Android内部有一些优化,比如一张图片做移动,我们不需要真正的去重新绘制,Android内部提供缓存机制,不会显示的再调用onDraw(canvas)函数
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
//部分代码省略
public void draw(Canvas canvas) {
/***部分代码省略***/
//如果有子 View(DecorView当然有子View),就会调用dispatchDraw() 将绘制事件通知给子 View。
//ViewGroup 重写了 dispatchDraw(),调用了 drawChild()
//drawChild() 调用了子 View 的 draw(Canvas, ViewGroup, long)
}
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
/***部分代码省略***/
Transformation transformToApply = null;
boolean concatMatrix = false;
final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
final Animation a = getAnimation();
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
} else {
/***部分代码省略***/
}
/***部分代码省略***/
// 动画数据应用在RenderNode或者Canvas上的!!!!
if (transformToApply != null) {
if (concatMatrix) {
if (drawingWithRenderNode) {
// 应用动画数据
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
canvas.translate(-transX, -transY);
// 应用动画数据
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
float transformAlpha = transformToApply.getAlpha();
if (transformAlpha < 1) {
// 应用动画数据
alpha *= transformAlpha;
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
}
}
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
/***部分代码省略***/
//绘制动画的当前帧,并获取当前动画的状态(是否继续运行)
boolean more = a.getTransformation(drawingTime, t, 1f);
if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
if (parent.mInvalidationTransformation == null) {
parent.mInvalidationTransformation = new Transformation();
}
invalidationTransform = parent.mInvalidationTransformation;
a.getTransformation(drawingTime, invalidationTransform, 1f);
} else {
invalidationTransform = t;
}
//如果动画没有结果
if (more) {
if (!a.willChangeBounds()) {
if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==
ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;
} else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
//进行绘制
parent.invalidate(mLeft, mTop, mRight, mBottom);
}
} else {
/***部分代码省略***/
//进行绘制
parent.invalidate(left, top, left + (int) (region.width() + .5f),
top + (int) (region.height() + .5f));
}
}
return more;
}
}
View.draw(Canvas) —> ViewGroup.dispatchDraw(Canvas) —> ViewGroup.drawChild(Canvas, View, long) —> View.draw(Canvas, ViewGroup, long) —> View.applyLegacyAnimation(ViewGroup, long, Animation, boolean)
2.2.3 计算动画进度,进行矩阵变换
public abstract class Animation implements Cloneable {
/***部分代码省略***/
public boolean getTransformation(long currentTime, Transformation outTransformation) {
/***部分代码省略***/
//执行时间是否过期
final boolean expired = normalizedTime >= 1.0f;
mMore = !expired;
//动画进度为0.0~1.0之间
if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
/***部分代码省略***/
//插值器计算动画执行进度
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
//真正的动画效果代码执行处(通过矩阵变化)
applyTransformation(interpolatedTime, outTransformation);
}
//如果动画绘制完成
if (expired) {
//判断动画是否需要继续循环
if (mRepeatCount == mRepeated) {
if (!mEnded) {
mEnded = true;
guard.close();
fireAnimationEnd();
}
} else {
if (mRepeatCount > 0) {
mRepeated++;
}
if (mRepeatMode == REVERSE) {
mCycleFlip = !mCycleFlip;
}
mStartTime = -1;
mMore = true;
fireAnimationRepeat();
}
}
if (!mMore && mOneMoreTime) {
mOneMoreTime = false;
return true;
}
return mMore;
}
}
以RotateAnimation动画为例,分析它的applyTransformation方法
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
float scale = getScaleFactor();
if (mPivotX == 0.0f && mPivotY == 0.0f) {
//获取绘制矩阵,设置旋转角度
t.getMatrix().setRotate(degrees);
} else {
t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale);
}
}
说明:Animation的运行依赖Android本身的机制回调(每帧都得回调getTransformation和applyTransformation)无法自身进行运算计算fraction,并且可参与运算的只有Transformation对象里的alpha和matrix,所以Animation只能实现简单的Alpha,Scale,Translate,Rotate变换效果。
3 逐帧动画
逐帧动画是指一帧一帧的播放动画,每一帧由一个图片组成,
3.1xml 定义和使用
<?xml version = "1.0" encoding = "utf-8"?>
<animation-list xmlns:android = "http//schemas.android.como/apk/res/android"
android:oneshot= "true" || "false"
>
<item
android:drawable="@drawable/drawable_name_1
android:duration = "1000"
<item
android:drawable="@drawable/drawable_name_2
android:duration = "1000"
>
</animation-list>
- 根元素为animation-list,是必须的,可以包含一个或者以上的item, android:oneshot = "true"动画只会执行一次,false则会一直循环
- item 中 drawable指向资源文件的名字,duration 指的是持续时间,第一个item的持续时间完了,就播放第二个item,以此类推
然后通过ImageView 设置动画资源,通常有两种方法设置imageView的背景,一是android:src ;而是android:background 在imageview中设置第一帧的图片
<ImageView
android:layout_width = "match_parent"
android:layout_height = "match_parent"
android:background = "@drawable/drawable_name_1"
/>
或者是
<ImageView
android:layout_width = "match_parent"
android:layout_height = "match_parent"
android:src = "@drawable/drawable_name_1"
/>
接着在代码处调用
val imageView = findViewById<ImageView>(R.id.frame_image)
val animation = image.background
//或者是(src 对应drawable ;background 对应backround)
val animation = image.drawable
animation.start() //开始动画
3.2 使用代码实现
使用代码时,我们要用到的是AnimationDrawable类,常用方法有 - void start() //开始播放逐帧动画 - void stop() //停止播放 - int getDuration(int index) //获取第index帧的持续时间 - Drawable getFrame(int index) //获取指定index帧对应的drawable对象 - int getNumberOfFrames() 获取帧数 - boolean isRunning() 动画是否在播放 - void setOneShot(boolean oneShot) true为播放一次,false循环播放 - boolean isOneShot() 判断动画的播放类型,是播放一次还是循环播放的 - void addFrame(Drawable frame,int duration) 为动画添加一帧,表明持续时间
代码实现如下
val imageview = findViewById<ImageView>(R.id.frame_image)
val anim = AnimationDrawable()
for(i in 1..4){
val id = resources.getIdentifier("drawable_name_$i","drawable",packageName)
val drawable = resouces.getDrawable(id)
anim.addFrame(drawable,1000)
}
anim.isOneShot = false
imageview.setBackgroundDrawable(anim)
anim.start()
ps:objectValue和ValueObject的后面另起一篇文章太长了