Android系统编程入门系列之界面Activity响应丝滑的传统动画
上篇文章介绍了应用程序内对用户操作响应的相关方法位置,简单的响应逻辑可以是从一个界面Activity
跳转到另一个界面Activity
,也可以是某些视图View
的相对变化。然而不管是启动一个界面执行新界面Activity
的生命周期方法,还是视图的相对变化,都需要一段时间,所以在响应的最终结果完成之前是有一段空白时间的。而在这段或长或短的时间里,该怎么给用户展示界面呢?这就用到Android系统推荐的动画流程了。
广义上说,Android系统在屏幕上绘制展示给用户的内容发生变化时,都可以使用相关动画过渡。与用户操作的响应一致,根据动画的作用对象不同,展示动画的效果可以作用于界面Activity
,也可以作用于视图View
。大多文章是基于动画分类的帧动画、补间动画介绍,而这里将按照动画的作用对象分别展开介绍。
视图动画
视图动画可以作用于任何需要展示动画效果的视图View
,视图动画是Android系统最原生的一种动画类型,视图动画的定义类可以查看android.view.animation.Animation,其子类便是视图动画的效果分类,包括渐变效果AlphaAnimation
、旋转效果RotateAnimation
、缩放效果ScaleAnimation
、移动效果TranslateAnimation
、和将上述多种效果集合到一起的合集效果AnimationSet
。
由于视图View
既可以在布局文件中静态声明,也可以在代码中动态注册声明,所以类似的,视图动画的声明也可以分为在布局文件中静态声明,和在代码中动态声明两种方式。但是视图动画的效果启动使用,需要根据不同的用户响应决定,所以只能在代码中动态使用。由于视图动画可能包含大量的效果数据,所以一般推荐视图动画静态声明+动态使用的方式。
动态声明的视图动画效果,只需要在代码中定义上述五种动画效果对应的动画类,并设置相关数据参数即可。其中
渐变效果AlphaAnimation (float fromAlpha, float toAlpha)必须要设置fromAlpha参数作为渐变效果的起始透明度,toAlpha参数作为渐变效果的结束透明度。
旋转效果RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)必须要设置fromDegrees参数作为旋转效果的起始旋转角度,和toDegrees参数作为旋转效果的结束旋转角度;其他参数可选,包括屏幕坐标形式的后四个参数,pivotXType参数为pivotXValue参数类型,pivotXValue参数为绕x轴旋转的角度值,同理,pivotYType参数为pivotYValue参数类型,pivotYValue参数为绕y轴旋转的角度值。其中的参数类型包括Animation.ABSOLUTE
绝对类型,其参数值表示绝对数值;Animation.RELATIVE_TO_SELF
相对自身视图类型,其参数值为相对自身视图在动画效果前的百分值;Animation.RELATIVE_TO_PARENT
相对父视图类型,其参数值为相对父视图在动画效果前的百分值。
缩放效果ScaleAnimation(float fromX, float toX, float fromY, float toY, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)也是以屏幕坐标的形式记录参数,必须要设置fromX参数作为缩放效果开始时在x轴方向的比例,toX参数作为缩放效果结束时在x轴方向的比例,同理,fromY参数作为缩放效果开始时在y轴方向的比例,toY参数作为缩放效果结束时在y轴方向的比例;其他参数可选,其含义与旋转效果的可选参数类似。
移动效果TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)和TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue, int fromYType, float fromYValue, int toYType, float toYValue)同样是以屏幕坐标的形式记录参数,这两个构造方法可以任选一种,其中当八参构造方法中的Type系列参数值为Animation.RELATIVE_TO_SELF
时,Value系列参数所表示的数据与四参构造方法的Delta系列参数一致。
合集效果AnimationSet(boolean shareInterpolator)可以根据shareInterpolator参数决定该动画内的一系列动画展示时是否使用当前类对象中定义的插值器,之后可以调用该对象的addAnimation(Animation a)
依次放入要执行的系列视图动画对象。
这里提到的插值器,是实现了android.view.animation.Interpolator接口的类,这些插值器类计算了视图动画效果中从开始到结束之间那段时间的效果展示,在上述视图动画效果Animation对象中,可以通过调用setInterpolator(Interpolator i)
方法设置,如果不设置会默认使用android.view.animation.LinearInterpolator线性插值器。
上述五种效果类的构造方法中,还有一种(Context context, AttributeSet attrs)
参数的构造方法,使用该构造方法可以通过参数二attrs传入静态声明的动画资源文件,从而在代码中使用实例化对象。
静态声明的视图动画文件,必须保存在res/anim/资源目录下,符合xml格式的文件。该文件的根标签必须是五种视图动画效果之一,包括渐变效果<alpha></alpha>
、旋转效果<scale></scale>
、缩放效果<rotate></rotate>
、移动效果<translate></translate>
、合集效果<set></set>
。而其中的必选参数和可选参数也都与代码动态声明中的相对应。
最终都可以在代码中得到上述五种视图动画类的实例化对象。可以调用setAnimationListener(Animation.AnimationListener listener)
设置动画执行的监听,在实现的android.view.animation.Animation.AnimationListener接口实例中,可以分别实现onAnimationStart(Animation animation)
动画开始前、onAnimationRepeat(Animation animation)
动画重复时、onAnimationEnd(Animation animation)
动画结束时的回调监听。在启动动画展示的地方,调用startNow()
方法可以立即开始;或者先调用setStartTime(long startTimeMillis)
设置启动的延时时间,再调用start()
方法开始计时,等延时时间之后开始展示动画。
图片动画
位图动画
在AndroidSDK提供的系统视图中,有一种类似幕布绘制图像的系列视图,像android.widget.ImageView,这种具有绘制像素位图功能的视图,更适合在其中绘制展示多张图片连续组合的帧动画。本质上图片动画只是一系列图片的组合,所以其声明和使用方式更随意,不仅可以在代码中动态声明+动态使用的方式,也可以直接在资源文件中静态声明+静态使用。不过因为图片动画需要加载大量的图片,所以在代码中动态声明使用的方式是在应用程序运行过程中执行的,可能会影响用户的流畅度,所以推荐图片动画使用静态声明+静态使用的方式。
动态声明的图片动画可以使用android.graphics.drawable.AnimationDrawable类实例化加载位图组成的帧动画。之后依次调用该对象的addFrame(Drawable frame, int duration)
方法增加要展示的每一帧图片,其中参数一frame
即指定了要加载的图片资源,参数二duration
则表示当前图片资源帧在整个动画播放中的时长,单位是毫米。也可以调用该对象的setOneShot(boolean oneShot)
设置当前系列帧的图片动画是否只播放一遍。最终在需要启动图片动画的位置,调用该对象的start()
,而在需要停止图片动画的位置,调用对象的stop()
。
这里需要注意的是,图片动画启动的
start()
方法必须要在界面Activity
的声明周期方法执行完onCreate()
之后调用。
静态声明的图片动画文件,必须保存在res/drawable/目录下,符合xml格式的文件。该文件的根标签必须是<animation-list></animation-list>
,在该标签中可以使用android:oneshot
属性值为true
或false
,来表示当前系列帧的图片动画是否只播放一遍。而其中的每一帧图片使用<item />
中的android:drawable
属性值作为drawable
资源文件引用,同时要通过android:duration
属性值设置当前图片帧的展示时长,根据人眼的识别速度,通常设置在1000(单位毫秒)以下。之后如果想静态使用,可以在要显示动画的视图中,通过设置其android:backgroud
属性,并将静态声明的资源文件名作为drawable
资源类型赋值,即可关联使用。如果想动态使用,首先在代码中找到要显示动画的视图对应的对象,调用该对象的setBackgroundResource(int res)
,同样将静态声明的资源文件名作为R.drawable
资源类型赋值,也可关联使用。最终,在需要启动图片动画的代码中,通过调用视图对象的getBackgroud()
方法获取到图片动画的接口android.graphics.drawable.Animatable对象,就可以在需要启动和停止图片动画的位置分别调用start()
和stop()
方法。
矢量图动画
另外, 在Android5.0 即API 21及以上的版本中,通过在项目modle中增加项目依赖库support-vector-drawable和animated-vector-drawable,以支持在资源文件中定义绘制矢量图,在AndroidStudio创建默认项目时,所使用的默认应用icon就是矢量图对象,其优势就是缩放仍不失真、体积小等,这里不详介绍。针对这种矢量图对象,也可以更加快速的增加矢量图动画。由于矢量图只能在资源文件中静态声明,相应的,矢量图动画也只能在资源文件中静态声明。这种动画效果主要依赖于android.graphics.drawable.AnimatedVectorDrawable类或最新包向下兼容的androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat类。
矢量图的声明是在res/drawable/
资源目录下,以<vector></vector>
标签所包裹的一层表示普通矢量图,可以在该标签内部增加<group></group>
标签包裹一组动画效果,设置其android:name
属性标记该组动画名称,同时使用视图动画相关属性值作为视图动画效果展示,包括渐变、旋转、缩放、平移。也可以在<group>
标签内部,继续使用<path></path>
标签定义一系列路径,同样需要设置其android:name
属性标记该组路径名称。
矢量动画的声明是在res/animator/
资源目录下,以<objectAnimator></objectAnimator>
为根标签包裹的动画效果,通过设置其android:propertyName
属性值,可以描述视图动画效果中的渐变、缩放、旋转、平移等效果,其内部属性与静态声明的视图动画中的属性类似。而以<set></set>
为根标签则可以包裹一系列上述四种动画效果所表示的<objectAnimator></objectAnimator>
标签。
最后是将矢量图与矢量动画关联使用,需要在res/drawable
资源目录下创建新的xml资源文件,以<animated-vector></animated-vector>
作为根标签,并设置其android:drawable
属性并引用上述普通矢量图资源文件为drawable
资源类型赋值。在根标签内通过<target android:name="" android:animation="" />
分别为矢量图定义中的android:name
属性值与矢量图声明中的资源文件相关联。最终在引用普通矢量图资源文件的位置改为引用动画矢量图资源文件。
界面动画
界面动画仅在Android5.0即 API 21 及以上的版本中支持。界面动画作用于界面Activity
,只有在两个界面Activity
相互启动切换时,才需要展示界面动画,因此界面动画的展示对象主要分三种,包括作为旧界面Activity
在退出时动画效果,作为新界面Activity
在进入动画效果,和两个界面之间如果有相同内容的同类视图,称之为共享视图,其在界面切换时的动画效果。而关于这些对象的动画效果,AndroidSDK提供了一些统一的动画效果可供选择,包括针对退出和进入动画的淡入淡出效果的android.transition.Fade类、移动效果android.transition.Slide类、爆炸效果android.transition.Explode类;还有针对共享视图的视图动画效果。或者也可以继承android.transition.Transition实现自定义动画效果。
由于界面Activity
的定义是在清单文件中静态注册的,所以界面动画的使用也可以在注册时采用静态声明使用的方式。在静态使用时用到了<activity android:style/>
样式属性,该属性值是已经定义了<style></style>
标签的xml格式的样式资源文件。在<style>
样式标签中,必须包含属性parent="android:Theme.Material"
或其子样式名。
要想启动界面动画,首先在该样式中增加一条开关控制。
<item name="android:windowActivityTransitions">true</item>
之后重写该样式标签中的<item>
标签,根据name
属性值控制不同的动画对象,而根据<item></item>
标签值指定不同的动画效果。这里将不同动画对象对应关系列为下列表格。
动画对象 | name值 |
---|---|
作为新界面进入动画 | android:windowEnterTransition |
作为旧界面退出动画 | android:windowExitTransition |
作为新界面进入时共享视图动画 | android:windowSharedElementEnterTransition |
作为旧界面退出时共享视图动画 | android:windowSharedElementExitTransition |
既然界面动画是通过样式定义的方式静态声明,那肯定也能在界面Activity
创建后的方法回调中通过代码动态声明使用。这里用到了android.view.Window类,因为样式的修改要在界面加载布局之前,所以在setContentView()
方法之前,通过调用getWindow()
方法可以得到Window
对象。
首先同样需要调用该对象的requestFeature(Window.FEATURE_CONTENT_TRANSITIONS)
方法开启界面动画开关。
之后调用该对象的系列方法来控制不同动画对象,根据传入的Transition
对象的实例指定不同的动画效果。不同动画对象的对应关系如下表。
动画对象 | Window类调用方法 |
---|---|
作为新界面进入动画 | setEnterTransition() |
作为旧界面退出动画 | setExitTransition() |
作为新界面进入时共享视图动画 | setSharedElementEnterTransition() |
作为旧界面退出时共享视图动画 | setSharedElementExitTransition() |
针对共享视图的动画,还需要分别在两个布局文件中标记共享视图,使用android:transitionName
属性指定相同的共享视图名字。
在声明界面动画效果后,还需要在该界面作为新界面被启动的位置或者该界面作为旧界面启动其他界面的位置,将原有的startActivity(Intent intent)
方法修改为startActivity(Intent intent, Bundle bundle)
方法。其中的intent
参数还是之前要启动界面的意图信息,bundle
参数则是新增的控制界面动画的数据包。可以通过ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
系列方法获取当前界面声明的界面动画效果,其结果值作为android.os.Bundle对象传入即可。
如果在界面动画中有单个共享视图动画,可以参考
ActivityOptions.makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName)
方法。
如果在界面动画中有多个共享视图动画,可以参考
ActivityOptions.makeSceneTransitionAnimation(Activity activity, Pair...<View, String> sharedElements)
方法。
除了上面作用于三种对象的基本动画类型,Android系统还提供了一种作用于任何对象的属性动画,该动画具有更全面的功能,详情可期待下篇文章。