对之前学习的动画做个简单的总结吧,不是特别深入,只是了解一下用法,看看效果,以后也许有用上的地方。
一、视图动画(View Animation)
记得刚开始学Android那会,接触的就是视图动画了,无非就是四大类:平移(Translate)缩放(Scale)旋转(Rotate)淡入淡出(Alpha)。使用方式也挺简单,可以通过xml文件配置,文件应该存放在res/anim文件夹下,访问时采用R.anim.XXX.xml的方式,也可以直接通过代码设置,这里只贴出TranslateAnimation的代码。
使用时直接通过 translateAnim= AnimationUtils.loadAnimation(this, R.anim.translateanim);
view.startAnimation(translateAnim);
在代码中:注意,数值参数都是相对于View本身而言。
private fun getViewAnimation(): Animation? {
//相对于自身的坐标而言移动(10,20)
// val translateAnimation = TranslateAnimation(0f,10f,0f,20f)
//绝对位置移动(x+10,y+20)
// val translateAnimation = TranslateAnimation(
// Animation.ABSOLUTE,0f,
// Animation.ABSOLUTE,10f,
// Animation.ABSOLUTE,0f,
// Animation.ABSOLUTE,20f)
//以自身为比例移动(x+0.25x,y+0.5y)
// val translateAnimation = TranslateAnimation(
// Animation.RELATIVE_TO_SELF,0f,
// Animation.RELATIVE_TO_SELF,0.25f,
// Animation.RELATIVE_TO_SELF,0f,
// Animation.RELATIVE_TO_SELF,0.5f)
//以父布局为比例移动(x+0.5parentx,y+0.5parenty)
val translateAnimation = TranslateAnimation(
Animation.RELATIVE_TO_PARENT, 0f,
Animation.RELATIVE_TO_PARENT, 0.5f,
Animation.RELATIVE_TO_PARENT, 0f,
Animation.RELATIVE_TO_PARENT, 0.5f)
translateAnimation.duration = 2000
translateAnimation.fillAfter = true
//插值器
translateAnimation.interpolator = AccelerateInterpolator()
translateAnimation.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationEnd(animation: Animation?) {
Toast.makeText(this@AnimationDetailActivity, "onAnimationEnd", Toast.LENGTH_SHORT).show()
}
override fun onAnimationStart(animation: Animation?) {
Toast.makeText(this@AnimationDetailActivity, "onAnimationStart", Toast.LENGTH_SHORT).show()
}
override fun onAnimationRepeat(animation: Animation?) {
//TODO
}
})
return translateAnimation
}
使用的时候直接
img.startAnimation(getViewAnimation())
二、帧动画
这个就用的比较少了,听的最多的就是类似放电影,通过一帧一帧的图片组合而成,这个用起来也挺简单,首先准备好帧动画的图片,放在drawable文件夹下,再新增一个xml文件,如下:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<!--oneshot true 代表只执行一次,false 循环执行。-->
<item android:drawable="@drawable/ic_loading_white_01"
android:duration="100"/>
<item android:drawable="@drawable/ic_loading_white_02"
android:duration="100"/>
<item android:drawable="@drawable/ic_loading_white_03"
android:duration="100"/>
<item android:drawable="@drawable/ic_loading_white_04"
android:duration="100"/>
<item android:drawable="@drawable/ic_loading_white_05"
android:duration="100"/>
<item android:drawable="@drawable/ic_loading_white_06"
android:duration="100"/>
<item android:drawable="@drawable/ic_loading_white_07"
android:duration="100"/>
<item android:drawable="@drawable/ic_loading_white_08"
android:duration="100"/>
<item android:drawable="@drawable/ic_loading_white_09"
android:duration="100"/>
<item android:drawable="@drawable/ic_loading_white_10"
android:duration="100"/>
<item android:drawable="@drawable/ic_loading_white_11"
android:duration="100"/>
<item android:drawable="@drawable/ic_loading_white_12"
android:duration="100"/>
</animation-list>
代表依次执行,并且每个图片执行的时间,在代码中,直接 这样就可以了。
private fun startFrame() {
img.setBackgroundResource(R.drawable.frame)
var frameDrawable = img.background as AnimationDrawable
frameDrawable.start()
}
三、属性动画(Property Animation)
在学习了一段时间的视图动画后,慢慢的了解到了其他的动画,如属性动画,这是在Android3.0之后引入的,给我印象最深的就是对一个View执行属性动画之后,View的位置大小随着动画的变化而变化,这是视图动画所没有的,其中我们开发中主要用到的就是两个类,ValueAnimator和ObjectAnimator,区别就是ValueAnimator可以获取在动画执行的某一时刻对应的值,而ObjectAnimator则执行的是某个对象的某个属性动画,举个例子:
private fun getValueAnimation(): Animator {
var left = img.left
var right = img.right
var top = img.top
var bottom = img.bottom
//可以传入多个值
val valueAnimator = ValueAnimator.ofInt(0,10,50,100)
valueAnimator.duration = 2000
valueAnimator.interpolator = AccelerateInterpolator()
valueAnimator.addUpdateListener { animation ->
val value = animation.animatedValue as Int
img.layout(left+value,top, right+value,bottom)
// img.translationX = value.toFloat()
// img.postInvalidate(); 也可以主动刷新
Log.e(TAG, value.toString())
}
valueAnimator.start()
return valueAnimator
}
下面是使用ObjectAnimator对view设置属性的操作,前提是某个view必须包含该属性的get和set方法,
没有的话可以自己定义一个。等同于img.setTranslationX(0-100)
val objAnimator = ObjectAnimator.ofFloat(img,"translationX",0f,100f)
objAnimator.duration = 2000
objAnimator.interpolator = AccelerateInterpolator()
objAnimator.start()
当然,我们也可以自定义一个差值器,首先看下注释,差值器定义了一个动画的变化率,所有的差值器最终都是继承的这个类,只有一个方法getInterpolation,注释就可以看出,参数input范围在0-1.0,0代表动画开始,1代表动画的结束,返回值也可以大于1.0或者小于0。
/**
* A time interpolator defines the rate of change of an animation. This allows animations
* to have non-linear motion, such as acceleration and deceleration.
*/
public interface TimeInterpolator {
/**
* Maps a value representing the elapsed fraction of an animation to a value that represents
* the interpolated fraction. This interpolated value is then multiplied by the change in
* value of an animation to derive the animated value at the current elapsed animation time.
*
* @param input A value between 0 and 1.0 indicating our current point
* in the animation where 0 represents the start and 1.0 represents
* the end
* @return The interpolation value. This value can be more than 1.0 for
* interpolators which overshoot their targets, or less than 0 for
* interpolators that undershoot their targets.
*/
float getInterpolation(float input);
}
在LinearInterpolator的方法中就直接返回了input,因为是线性的,所以是个均匀变化的数值。
属性动画这里我只是简单的介绍了下使用,里面还有很多深入的内容没有过多的介绍,如Evaluator)、PropertyValuesHolder,KeyFrame等,这些先放一放了。
这里还有一个是LayoutTransition,该类用于当前布局容器中有View添加,删除,隐藏,显示的时候定义布局容器自身的动画和View的动画。也就是说当一个LinerLayout中隐藏一个view的时候,我们可以自定义 整个LinerLayout容器因为隐藏了view而改变的动画,同时还可以自定义被隐藏的view自己消失时候的动画。你可以先new一个LayoutTransition对象,通过setLayoutTransition()方法将对象设置进一个布局容器ViewGroup中去。代码如下:
//这个LayoutTransition会监听ViewGroup的事件
val layoutTransition = LayoutTransition()
//设置一个默认的LayoutTransition
layout_parent.layoutTransition = layoutTransition
这样就给一个ViewGroup设置了一个默认的动画效果,包括四种:LayoutTransition.APPEARING、LayoutTransition.DISAPPEARING、LayoutTransition.CHANGE_APPEARING、LayoutTransition.CHANGE_DISAPPEARING
分别是在显示、消失、显示改变、消失改变时候的动画。我们可以获取了之后进行自定义:
//获取默认出现以及消失的动画
var appear = layoutTransition.getAnimator(LayoutTransition.APPEARING)
var disappear = layoutTransition.getAnimator(LayoutTransition.DISAPPEARING)
var change_appear = layoutTransition.getAnimator(LayoutTransition.CHANGE_APPEARING)
var change_disappear = layoutTransition.getAnimator(LayoutTransition.CHANGE_DISAPPEARING)
//这里只改变一下显示时候的动画
val customAppearingAnim = ObjectAnimator.ofFloat(null, "rotationY", 90f, 0f)
.setDuration(layoutTransition.getDuration(LayoutTransition.APPEARING))
customAppearingAnim.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(anim: Animator) {
val view = (anim as ObjectAnimator).target as View
view.rotationY = 0f
}
})
//不要忘记设置上去
layoutTransition.setAnimator(LayoutTransition.APPEARING,customAppearingAnim)
之后就可以看到添加一个子View时候的效果了。
更简单的做法就是,在xml文件的ViewGroup里,添加这么一句代码就可以轻松的实现默认的动画了。
android:animateLayoutChanges="true"
四、触摸反馈动画(
Ripple Effect)
四、触摸反馈动画(
)
这个是系统在5.0之后提供了一个动画效果,使用也很简单,给某个view设置background之后,在触摸到的时候就会触发这个动画,分为两种:
1.有界水波纹:android:background="?android:attr/selectableItemBackground"
就是水波纹会在TextView所在区域进行绘制。
2.无界水波纹:android:background="?android:attr/selectableItemBackgroundBorderless"
这里所谓的无界并非完全无界,而是以控件宽高中最大的数值作为水波纹效果所在正方形的边界进行绘制。
如果要在设置背景之后再设置这个动画,则需要在drawable-v21(因为是5.0)文件下新建一个xml文件,然后就像这样:
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorAccent">
<!--不设置默认为无界动画 可以这样添加一个item设置为有界动画
动画效果实际为ripple节点的颜色 id 要为系统id
drawable 没什么卵用 但是必须要加上,不然闪退。。。-->
<item android:id="@android:id/mask"
android:drawable="@android:color/black"/>
</ripple>
当然,其他的自定义如设置selector设置drawable和radius也都是可以的。不举例了。
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorAccent">
<item>
<selector>
<item
android:state_pressed="true"
android:drawable="@drawable/ic_launcher"/>
<item
android:state_pressed="false"
android:drawable="@drawable/bg"/>
</selector>
</item>
</ripple>
五、揭露动画(Reveal Effect)
这个动画也是在Android5.0提供的一种动画,加入了一种全新的视觉动画效果,从某一点展开缩起,有的也称之为聚合离散动画,可以在activity里对某个view做该动画,也可以用在activity之间的切换。
具体的创建方式Android已经提供给我们一个工具类 ViewAnimationUtils 来揭露动画。
public static Animator createCircularReveal(
//要执行动画的View
View view,
//相对于视图 View 的坐标系,动画圆的中心的x坐标
int centerX,
//相对于视图 View 的坐标系,动画圆的中心的y坐标
int centerY,
//动画圆的起始半径
float startRadius,
//动画圆的结束半径
float endRadius)
具体的使用也很简单,这里只是简单的对view做了显示与隐藏的动画,代码如下:
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun setRevealAnim(flag: Boolean) {
//先获取要设置动画的View的位置及大小
val w = img.width
val h = img.height
//中心位置
val hypot = Math.sqrt((w * w + h * h).toDouble()) / 2
val animator:Animator
animator = if (flag){
//执行动画从中心位置半径从最大到0 缩小
ViewAnimationUtils.createCircularReveal(
img, w / 2, h / 2, hypot.toFloat(), 0f)
}else{
img.visibility = View.VISIBLE
//执行动画从中心点扩散到最大 放大
ViewAnimationUtils.createCircularReveal(
img, w / 2, h / 2, 0f, hypot.toFloat())
}
animator.interpolator = AccelerateInterpolator()
animator.duration = 2000
animator.addListener(object : Animator.AnimatorListener {
override fun onAnimationEnd(animation: Animator?) {
img.visibility = if (flag) View.INVISIBLE else {
View.VISIBLE
}
this@AnimationDetailActivity.flag = !flag
}
override fun onAnimationCancel(animation: Animator?) {
//TODO
}
override fun onAnimationStart(animation: Animator?) {
//TODO
}
override fun onAnimationRepeat(animation: Animator?) {
//TODO
}
})
animator.start()
}
六、转场动画(TransitionAnimation)
Android5.0之后Activity的出入场动画总体上来说可以分为两种,一种就是分解、滑动进入、淡入淡出,另外一种就是共享元素动画,下面我们分别就这两种动画进行说明:
首先是共享元素动画,顾名思义,共享元素意思是在多个activity中共享这个view,比如我们现在有一个展示显示大图的功能,在前一个页面点击缩略图,跳转到大图页面,此时可以使用这个动画,步骤如下:
val i = Intent(this@ScreenTransitionActivity, DetailActivity::class.java)
i.putExtra("data","这是要传过来的文字")
//新建一个这个玩意,然后传入对应的View及String
//Pair可以传入各种数据类型,这里我们需要传入View,String
//也可以分开传入View与String
//String作共享元素的name传入,因此必须唯一
//这里我们传入了两个共享元素textView imageView
//转为bundle传入
val activityOptions =ActivityOptionsCompat.makeSceneTransitionAnimation(
this@ScreenTransitionActivity,
Pair<View,String>(iv, DetailActivity.VIEW_NAME_HEADER_IMAGE),
Pair<View,String>(tv, DetailActivity.VIEW_NAME_HEADER_TITLE))
ActivityCompat.startActivity(this,i,activityOptions.toBundle())
然后在接收的页面里,通过这样的方式获取一下:
//设置接收到的TransitionName
ViewCompat.setTransitionName(iv, VIEW_NAME_HEADER_IMAGE)
ViewCompat.setTransitionName(tv, VIEW_NAME_HEADER_TITLE)
loadItem()
在Google提供的代码里,这样设置了一下,为了添加监听
fun loadItem(){
tv.text = intent.getStringExtra("data")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && addTransitionListener()) {
// If we're running on Lollipop and we have added a listener to the shared element
// transition, load the thumbnail. The listener will load the full-size image when
// the transition is complete.
//意思是先加载小图,在获取到共享元素的数据之后再加载大图
loadThumbnail()
} else {
// If all other cases we should just load the full-size image now
loadFullSizeImage()
}
}
@RequiresApi(Build.VERSION_CODES.KITKAT)
private fun addTransitionListener(): Boolean {
//获取窗口的共享元素 添加监听 如果没加载完显示小图 加载完显示大图 记得移除监听
val transition = window.sharedElementEnterTransition
transition?.apply {
addListener(object :Transition.TransitionListener{
override fun onTransitionResume(transition: Transition?) {
//TODO
}
override fun onTransitionPause(transition: Transition?) {
//TODO
}
override fun onTransitionCancel(transition: Transition?) {
//TODO
}
override fun onTransitionStart(transition: Transition?) {
//TODO
}
override fun onTransitionEnd(transition: Transition?) {
//TODO
loadFullSizeImage()
removeListener(this)
}
})
return true
}
return false
}
这样操作之后就可以看到显示大图看起来是平滑的过度到第二个页面,看起来很丝滑是不是?
当然,这个动画也可以用在fragment的切换中。
上面我们是通过ActivityOptionsCompat.makeSceneTransitionAnimation构造了一个共享元素动画,其实也有多个其他的方法,如下:
/**
* 和overridePendingTransition类似,设置跳转时候的进入动画和退出动画
*/
public static ActivityOptions makeCustomAnimation(Context context, int enterResId, int exitResId);
/**
* 通过把要进入的Activity通过放大的效果过渡进去
* 举一个简单的例子来理解source=view,startX=view.getWidth(),startY=view.getHeight(),startWidth=0,startHeight=0
* 表明新的Activity从view的中心从无到有慢慢放大的过程
*/
public static ActivityOptions makeScaleUpAnimation(View source, int startX, int startY, int width, int height);
/**
* 通过放大一个图片过渡到新的Activity
*/
public static ActivityOptions makeThumbnailScaleUpAnimation(View source, Bitmap thumbnail, int startX, int startY);
七、Activity切换动画
这是Android5.0之前提供的Activity间的跳转动画。我们都知道跳转到新的activity是通过startActivity,销毁是调用finish方法,因此,这里的动画又分为两种,一种为旧的 Activity 的退出动画和新的 Activity 的显示动画,在返回的时候,就涉及到当前 Activity 的退出动画和前一个 Activity 的显示动画。
使用overridePendingTransition(),下面是一个简单的例子。
btnActivityAnimation.setOnClickListener {
startActivity(Intent(this, AnimationDetailActivity::class.java))
//新启动的activity进入时的动画,当前activity消失时的动画。简单来说看起来就像是在整体平移
//100%-0 0-(-100%)
overridePendingTransition(R.anim.in_right_left,R.anim.out_right_left)
}
这是进入新的activity时的动画,下面是离开当前的activity时候的
override fun finish() {
super.finish()
//新启动的activity进入时的动画,当前activity消失时的动画。简单来说看起来就像是在整体平移
//-100%-0 0-100%
overridePendingTransition(R.anim.in_left_right,R.anim.out_left_right)
}
使用style给activity设置样式:
<style name="MyAnim" parent="AppTheme">
<item name="android:windowAnimationStyle">@style/activityAnim</item>
</style>
<style name="activityAnim">
<item name="android:activityOpenEnterAnimation">@anim/in_right_left</item>
<item name="android:activityOpenExitAnimation">@anim/out_right_left</item>
<item name="android:activityCloseEnterAnimation">@anim/in_left_right</item>
<item name="android:activityCloseExitAnimation">@anim/out_left_right</item>
</style>
<activity android:name=".activity.AnimationActivity"
android:theme="@style/MyAnim"/>
<activity android:name=".activity.AnimationDetailActivity"
android:theme="@style/MyAnim"/>
这里需要注意一下,我的代码是从Main->Anim->Detail如上图这样设置的话,Main->Anim-Detail达到想要的效果,Detail->Anim也有效果,但是Anim->Main就没有效果了,因此为了防止这种现象出现,可以在Application中直接设置,或者再去Main里面设置style。
八、LayoutAnimation
layoutAnimation是API1就有的,主要用于ViewGroup控制childView做一些动画相关的操作,比如listView、RecyclerView等,这个时候运用一些动画就不会显得那么突兀,使用起来也挺简单,如下:
首先在我们要使用的RecyclerView的xml中声明一个layoutAnimation,引用一个item_anim的动画资源
<com.lovejjfg.easyjump.view.WrapperRv
android:id="@+id/rv"
android:layout_width="0dp"
android:layout_height="0dp"
android:layoutAnimation="@anim/item_anim"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
然后在item_anim.xml文件里:注释已经写的挺清楚了。
<?xml version="1.0" encoding="utf-8"?>
//根节点为layoutAnimation
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
//代表延时时间以s为单位
android:delay="30%"
//也可以设置差值器
android:interpolator="@android:anim/accelerate_interpolator"
//子View的显示顺序 正序normal或者逆序reverse或者随机random
android:animationOrder="normal"
//具体使用的动画资源
android:animation="@anim/in_left_right">
</layoutAnimation>
当然也可以通过代码设置:
//如果要这样设置的话,R.anim.in_left_right根节点就不能是layoutAnimation了,和xml不同
var loadAnimation = AnimationUtils.loadAnimation(this, R.anim.in_left_right)
//通过controller设置animation,后面是设置的延时时间
var layoutAnimationController = LayoutAnimationController(loadAnimation,0.3f)
//设置显示顺序
layoutAnimationController.order = LayoutAnimationController.ORDER_RANDOM
rv.layoutAnimation = layoutAnimationController
下面也是一样的效果,不过需要注意的是,上面那种加载的anim根节点是layoutAnimation,下面的根节点是各种动画。
//返回的就是一个animationController
var loadLayoutAnimation = AnimationUtils.loadLayoutAnimation(
this, R.anim.item_anim)
loadLayoutAnimation.delay = 0.3f
loadLayoutAnimation.order = LayoutAnimationController.ORDER_RANDOM
rv.layoutAnimation = loadLayoutAnimation
这里有一个注意的地方,就是通过xml设置layoutAnimation的时候,仅仅在RecyclerView第一次显示的时候动画有效,后续更新数据会没有动画效果,假如想每次数据更新时,都呈现此效果,只能够在每次调用notifyDataSetChanged()之前,重新设定LayoutAnimationController。
动画写到这先简单的告一段落,因为动画类型不止这些,如果深入的写下去的话好像要写挺多。。以后争取再慢慢补上吧。