ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android动画

Android动画

/res/anim/下存放定义补间动画的xml文件

/res/animator/下存放定义属性动画的xml文件

ImageDecoder

Android9引入了Image Decoder,提供了更强大的图片解码支持,可以解码PNG、JPEG等静态态图片,也能直接解码GIF、WEBP等动画图片。
Android9新增了支持HEIF格式,这种压缩格式具有超高的压缩比,相较JPEG格式,其文件大小可以压缩到只有其一半,且可保证近似的图像质量。
当使用ImageDecoder解码GIF、WEBP等动画图片时,程序将会返回一个AnimatedImageDrawable
(Android9新增的API)对象,调用AnimatedImageDrawable对象的start()方法开始执行动画。

帧动画

帧动画是一个Drawable对象:AnimationDrawable,使用<animation-list.../>标签来定义逐帧动画drawable资源,其中使用<item.../>子元素定义动画的各帧,并指定各帧的持续时间,使用android:oneshot设置是否只播放一次。然后把帧动画对象设置给ImageView的source或background。AnimationDrawable代表的动画默认是不播放的,必须在程序中调用start方法播放动画。

补间动画

补间(Tween)动画,只需指定三个必要信息:动画开始帧信息、动画结束帧信息、动画的持续时间,无须"逐一"定义动画过程中的每一帧,而动画变化的"中间帧"由系统计算并补齐,或者叫补帧动画。

补间动画可以分为两大类:视图补间动画(View Animation)和属性补间动画(Property Animation)。

(视图)补间动画

视图动画是通过XML文件或代码定义动画效果,作用于视图(View)的属性上,如位置、大小、旋转等,最后把动画对象设置给视图的startAnimation方法。即先直接构造通用动画Animation,再把动画对象Animation传递给视图,由视图启动动画。

Animation类是抽象的视图动画类,它有如下几个子类:

  • AlphaAnimation:透明度改变的动画时要指定动画时要指定动画开始时的透明度、结束时的透明度和动画持续时间。其中透明度可从0变化到1。
  • ScaleAnimation:大小缩放的动画时要指定动画时要指定动画开始时的缩放比(以X、Y轴的缩放参数来表示)、结束时的缩放比(以X、Y轴的缩放参数来表示),并指定动画持续时间。由于缩放时以不同点为中心的缩放效果并不相同,日因此指定缩放动画时还要通过pivotX、pivotY来指定"缩放中心"的坐标。
  • TranslateAnimation:位移变化的动画时只要指定定动画开始时的位置(以X、Y坐标来表示)、结束时的位置(以X、Y坐标来表示),并打指定动画持续时间即可
  • RotateAnimation:旋转动画。创建该动画时只要指定动画开始时的旋转角度、结束时的旋转角度转角度,并指定动画持续时间即可。由于旋转时以不同点为中心的旋转效果并不相同,因此指定旋转动画时还要通过pivotX、pivotY来指定"旋转轴心"的坐标。

TranslateAnimation ‌只改变视图的绘制位置‌,‌需通过setFillAfte设置是否改变实际布局属性‌。当点击区域仍位于原始位置‌,即使视觉上已移动,可能导致交互错位。
‌Android 5.0+ 推荐使用 view.animate().translationX(...)‌(属性动画),更可靠且可支持硬件加速。

插值Interpolator

Interpolator控制在动画期间需要动态"补入"多少顿,具体在动画运行的哪些时刻补入帧,控制插入帧的行为。

Interpolator根据特定算法计算出整个动画所需要动态插入帧的密度和位置,最终实现动画帧的匀速、加速、减速、抛物线速度等各种速度变化。
Interpolator是一个接口,可以通过实现Interpolator来控制动画的变化速度。
Android为Interpolator提供了如下几个实现类,用于实现不同的动画变化速度。

  • LinearInterpolator:动画以均匀的速度改变。
  • AccelerateInterpolator:在动画开始的地方改变速度较慢,然后开始加速。
  • AccelerateDecelerateInterpolator:在动画开始、结束的地方改变速度较慢,在中间的时候加速。
  • CycleInterpolator:动画循环播放特定的次数,变化速度按正弦曲线改变。
  • DecelerateInterpolator:在动画开始的地方改变速度较快,然后开始减速。

在定义补间动画的<set.../>元素中设置android:interpolator属性,可指定为Android默认支持的Interpolator。例如:@android:anim/linear_interpolator,@android:anim/accelerate_interpolator。

定义动画资源之后,利用 AnimationUtils 工具类加载指定的动画资源,加载成功后返回 Animation对象, 动画视图使用该对象作为播放参数。

XML方式

res/anim目录下创建例如fade_in.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<alpha
android:duration="2000"
android:fromAlpha="0.0"
android:interpolator="@android:anim/linear_interpolator"
android:toAlpha="1.0" />
</set>

然后在代码中加载这个动画,再应用到动画视图的播放方法中:

Animation fadeIn = AnimationUtils.loadAnimation(context, R.anim.fade_in);
view.startAnimation(fadeIn);

代码方式

直接在代码中创建动画,然后应用给动画视图的动画启动方法。

Animation fadeIn = new AlphaAnimation(0, 1);
fadeIn.setDuration(2000); // 设置动画持续时间
fadeIn.setFillAfter(true); // 保持动画结束时的状态
view.startAnimation(fadeIn);

更多示例:

<!--xml配置--> <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator"> <!--从屏幕右侧飞入--> <translate android:fromXDelta="100%" android:toXDelta="0%" android:duration="200"/> <!--向屏幕右侧飞出--> <translate android:fromXDelta="0%" android:toXDelta="100%" android:duration="200"/> </set>
//代码加载 val translate: Animation? = AnimationUtils.loadAnimation(menuPanel.context, com.bjxdljg.common.R.anim.anim_in_from_right) menuPanel.startAnimation(translate)
//神图补间动画代码实现 //从屏幕右侧飞入 val translate: Animation= TranslateAnimation(Animation.RELATIVE_TO_SELF,1f, Animation.RELATIVE_TO_SELF,0f,Animation.RELATIVE_TO_SELF,0f,Animation.RELATIVE_TO_SELF,0f) translate.duration=200 menuPanel.startAnimation(translate) //向屏幕右侧飞出 val translate: Animation= TranslateAnimation(Animation.RELATIVE_TO_SELF,0f, Animation.RELATIVE_TO_SELF,1f,Animation.RELATIVE_TO_SELF,0f,Animation.RELATIVE_TO_SELF,0f) translate.duration=200 menuPanel.startAnimation(translate) //属性补间动画代码实现, //从屏幕右侧飞入 menuPanel.translationX=menuPanel.width.toFloat() menuPanel.animate().translationX(0f).setDuration(200).start()

自定义(视图)补间动画

Animation是视图补间动画抽象基类,系统为该抽象基类提供实现了AlphaAnimation、RotateAnimation、ScaleAnimation、Translate Animation四个视图补间动画的基本形式。在实际项目中可能还需要一些更复杂的动画,比如让图片在"三维"空间内进行旋转动画等,这需要继承Animation实现自定义补间动画。

为了控制视图进行三维空间的变换,需要借助于Android提供的android.graphics.Camera,这个Camera是一个空间变换工具,作用有点类似于Matrix,但功能更强大。

属性(补间)动画

属性动画是增强版的补间动画。属性动画直接作用于对象的属性上,它的直接作用只是在时间线上不断修改任何类型对象的任何属性值。

  • 视图补间动画只能定义两个关键帧在"透明度""旋转""缩放""位移"4个方面的变化,属性补间动画可以定义任何属性的变化。
  • 视图补间动画只能对UI组件执行动画,属性补间动画几乎可以对任何对象执行动画(不管它是否显示在屏幕上)。

Animator类是抽象的属性动画类,它有如下几个子类:

  • ValueAnimator:是Animator的子类,属性动画主要的时间引擎,它负责计算各个帧的属性值。它定义了属性动画的绝大部分核心功能,包括计算各帧的相关属性值,负责处理更新事件,按属性值的类型控制计算规则。属性动画主要由两方面组成:1计算各帧的相关属性值;2为指定对象设置这些计算后的值。ValueAnimator只负责第一方面内内容,因此程序员必须根据ValueAnimator计算并监听值更新来更新对象的相关属性值。
  • ObjectAnimator:是ValueAnimator的子类,允许对指定对象的属性执行动画。在实际应用中,ObjectAnimator使用起来更加简单,因此更加常用用。在少数场景下,由于ObjectAnimator存在一些限制,可能需要考虑使用ValueAnimator。
  • AnimatorSet:是Animator的子类,用于组合多个Animator,并指定多个Animator是按次序播放还是同时播放。

属性动画分为两类:ValueAnimator和ObjectAnimatorAnimatorSet用于组合多个动画。即先要由视图对象和其属性名称构造出动画对象Animator,然后由动画对象Animator启动动画。

ObjectAnimator继承了ValueAnimator,它可以直接将ObjectAnimator在动画过程中计算出来的值应用到指定对象的指定属性上,因此使用ObjectAnimator就不需要注册AnimatorUpdateListener监听器了。使用ObjectAnimator的ofint()、ofFloat()或ofObject(静态方法创建CObjectAnimator时,需要指
定具体的对象,以及对象的属性名。

与ValueAnimator不同的是,使用ObjectAnimator有如下几个注意点。

  • 要为该对象对应的属性提供setter方法。
  • 调用ObjectAnimator的ofint()、ofFloat()或 ofobject()工厂方法时,如果values...参数只提供了一个值(本来需要提供开始值和结束值),那么该值会被认为为是结束值。该对象应该为该属性提供一个getter方法,该getter方法的返回值将被作为开始值。
  • 如果动画的对象不是View,为了能显示动画效果,可能还需要在onAnimationUpdate()事件监听方法中调用View.invalidate()方法来刷新屏幕的显示,比如对]Drawable对象的color属性执行动画。但如果是View定义的setter方法,如setAlpha()和setTraInslationX()等方法都会自动地调用invalidate()方法,因此不需要额外地调用invalidat()方法。

Evaluator

属性动画还需要利用一个Evaluator(计算器)工具类,控制属性动画如何计算属性值。Android提供了如下Evaluator。

  • IntEvaluator:用于计算int类型属性值的计算器。
  • FloatEvaluator:用于计算float类型属性值的计算器。
  • ArgbEvaluator:用于计算以十六进制形式表示的颜色值的计算器。
  • TypeEvaluator:它是计算器接口,开发者可以通过实现该接口来实现自定义计算器。如果需要对int、float或者颜色值以外类型的属性执行属性动画,可能需要实现TypeEvaluator接口来实现自定义计算器。

Android8还为AnimatorSet新增了如下方法。

  • reverse():反向播放属性动画。通过reverse()方法则允许对动画进行倒播--以前可能需要定义两组动画,其中一组用于正向播放(比如放大、淡入);另一组用于反向播放(比如缩小、淡出),有了倒播功能之后,只需定义一组动画即可。
  • long getCurrentPlayTime():获取动画的当前播放时间。
  • setCurrentPlayTime(longplayTime):设置动画的播放时间。通过调用setCurrentPlayTime()方法,允许AnimatorSet动画直接调到指定时间点进行播放,不需要总是从头开始播放。

使用属性动画的步骤如下。

  1. 创建ValueAnimator或ObjectAnimator对象--既可从XML资源文件加载该动画资源,也可直接调用ValueAnimator或ObjectAnimator的静态工厂方法来创建动画。
  2. 为Animator对象设置属性。
  3. 如果需要监听Animator的动画开始事件、动画结束事件、动画重复事件、动画值改变事件,并根据事件提供相应的处理代码,则应该为Animattor对象设置事件监听器。
  4. 如果有多个动画需要按次序或同时播放,则应使用AnimatorSet组合这些动画。
  5. 调用Animator对象的start()方法启动动画。

ViewPropertyAnimator

android.view.ViewPropertyAnimator‌‌ 是 Android 提供的一个用于简化和优化对View对象属性进行动画处理的工具类。它并非继承自ValueAnimatorObjectAnimator,而是专门为View设计提供更简洁的链式调用语法,并在多属性同时动画时提升性能。

调用 view.animate().translationX()是使用 ‌ViewPropertyAnimator‌ 实现属性动画的一种常见方式,用于对 View 沿 X 轴进行平移动画。

使用SurfaceView实现动画

View的绘图机制存在如下缺陷:

  • View缺乏双缓冲机制。
  • 当需要更新View上的图片时,必须重绘View上显示的整张图片。
  • 新线程无法直接更新View组件。

Android提供了一个SurfaceView来代替View,在实现游戏绘图方面,SurfaceView比View性能高。

SurfaceView与普通View其中一个重要的区别:View的绘图小必须在当前U1线程中进行,而SurfaceView不会存在这个问题,因为SurfaceView的绘图是由SurfaceHolder来完成的。
对于View组件,如果程序需要花较长的时间来更新绘图,那么主UI线程将会被阻塞,无法响应用户的任何动作;而SurfaceHolder则会启用新的线程去更新SurfaceView的绘制,因此不会阻塞主UI线程。一般来说,如果程序界面的动画元素较多,而且很多动画元素的移动都需要通过定时器来控制,就可以考虑使用SurfaceView。

ViewFlipper

ViewFlipper可使用动画控制多个组件之间的切换效果。

1.ViewFlipper和OnGestureListener手势接口实现简单手势动画

要点如下:

ViewFlipper类继承自android.widget.ViewAnimator,它可以对内部的视图做动画,它支持2.0及以上。

在布局中添加ViewFlipper视图

<ViewFlipper android:id="@+id/ViewFlipper01" android:layout_width="fill_parent" android:layout_height="fill_parent">

</ViewFlipper>

在ViewFlipper中添加各个view:

flipper.addView(view1);

flipper.addView(view2);

实现OnGestureListener

构造GestureDetector detector = new GestureDetector(this);

重写activity的onTouchEvent方法:

public boolean onTouchEvent(MotionEvent event) {
return this.detector.onTouchEvent(event);
}

实现onFling方法:

public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (e1.getX() - e2.getX() > 120) {
this.flipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_left_in));
this.flipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_left_out));
this.flipper.showNext();
return true;
} else if (e1.getX() - e2.getX() < -120) {
this.flipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_right_in));
this.flipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_right_out));
this.flipper.showPrevious();
return true;
}
return false;
}

2.动画文件

在res\anim\*.xml中,例如:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="100%p" android:toXDelta="0" android:duration="500" />
<alpha android:fromAlpha="0.1" android:toAlpha="1.0" android:duration="500" />
</set>

这个动画中包含两个动作:

translation动画:fromXDelta 动画的开始X位置,toXDelta 动画的结束X位置,duration 持续时间。

alpha透明度通道动画:

layer-list

layer-list中的item有top,start、end、bottom属性,可以理解为内边距:top就是paddingTop,bottom就是paddingBottom。不推荐使用在Layer-List中直接动画。

不建议直接在layer-list中使用动画,因为layer-list主要用于组合静态图层。如果确实需要在第一层实现动画效果,可以将该层单独作为一个视图(例如ImageView),然后通过代码或XML属性动画来控制这个视图的动画。

对于位移动画,建议使用单独的视图(如ImageView)并通过属性动画来控制,而不是尝试在layer-list中直接实现。这样可以更灵活地控制动画效果,并且代码更加清晰、易于维护。如果确实需要在一个复杂的背景中结合多个动画效果,考虑将动画应用于单独的视图或使用矢量图和矢量动画。

AnimatorSet

在使用AnimatorSet时,‌推荐每次使用时都新建一个实例,而不是重用旧的AnimatorSet对象‌。

重用AnimatorSet可能会导致 ‌循环依赖异常‌(java.lang.IllegalStateException: circular dependencies cannot exist in animatorset),尤其是在动画执行过程中被多次启动或中断后。这是因为AnimatorSet内部会记录动画之间的依赖关系(如顺序、同时播放等),当同一个实例被重复使用时,这些依赖关系可能累积并形成循环引用,从而引发崩溃。

推荐做法

  1. 每次使用都新建‌:在需要启动动画时,创建一个新的AnimatorSet实例,并配置所需的动画。

  2. 避免重复调用start()‌:如果一个AnimatorSet已经在运行中,不要再次调用start(),这也会增加出现循环依赖的风险。

  3. 如需复用动画对象,复用的是ObjectAnimator‌:如果多个地方需要使用相同的动画效果(如缩放、移动),可以复用ObjectAnimator对象,但每次仍需将其添加到一个新的AnimatorSet中。

总结

  • 不要重用AnimatorSet实例。
  • 总是新建AnimatorSet实例来管理动画组合。
  • 可以安全地‌复用ObjectAnimatorValueAnimator等基础动画对象。

这种做法能有效避免循环依赖错误,确保动画稳定运行。‌

Scroller

Android里Scroller类 是为了实现View平滑滚动的一个Helper类。通常在自定义的View时使用,在View中定义一个私有成员mScroller = new Scroller(context)。设置mScroller滚动的位置时,并不会导致View的滚动,通常是用mScroller记录/计算View滚动的位置,再重写View的computeScroll(),完成实际的滚动。

返回列表