- 简介:
最近项目中需要展示一个气泡效果,当界面中数量发生变化时,会有一个数量+1的气泡动画,如下图所示:
动效说明:
首先位移起始点是在与数字居中的位置,开始透明度为0%,然后开始向上位移,透明度渐变至100%,并且停留两秒,随即开始下一段位移,下一段位移继续往上,透明度由100%渐变至0%,然后气泡消失。
- 思路:
那让我们一步步来分析
首先要有一个平移效果,平移效果的话还需要分成两部分,首先是先往上平移一部分,然后在第一次位移的基础上再往上平移,这是平移的部分。
然后是渐变,当第一段平移时,渐变是由透明度0%到100%,随后第二段为100%到0%。
ok,拆开来分析之后还是挺简单的,那么就从平移开始搞起吧。
- 平移动画
//构建属性动画-平移动画
val translate1Anim = TranslateAnimation(0.0f, 0.0f, 0.0f, -100.0f)
//动画结束后是否停留在移动后位置,true为停留
animation1Set.fillAfter = true
//动画执行时间(单位:毫秒)
animation1Set.duration = 1000
上面代码还是很好理解的,首先我们创建了一个平移的属性动画对象,构造方法中的四个参数分别表示为:
- float fromXDelta 动画开始的点离当前View X坐标上的差值
- float toXDelta 动画结束的点离当前View X坐标上的差值
- float fromYDelta 动画开始的点离当前View Y坐标上的差值
- float toYDelta 动画开始的点离当前View Y坐标上的差值
在这里我需要让控件往上移动,所以在最后一个参数中传入-100.0f
动画创建好了之后就可以试一下了,这里再贴一下布局,直接使用的CardView来做圆形布局,里面嵌套了一个TextView
<androidx.cardview.widget.CardView
android:id="@+id/animation_card"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginTop="120dp"
app:cardBackgroundColor="#06C584"
app:cardCornerRadius="15dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/animation_tv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:src="@mipmap/ic_launcher_round"
android:text="+12"
android:textColor="@color/white"
android:textSize="10sp" />
</androidx.cardview.widget.CardView>
好了,接下来只需要让cardview来调用startAnimation()方法执行一下动画效果,就可以了,这里就不贴效果图了,可以自己运行试一下;
binding.animationCard.startAnimation(translate1Anim)
然后是第二段位移,第二段是在第一段执行后的基础上,停留2秒再继续位移,停留2秒先不管,其实还是需要再创建一个平移属性动画的对象:
//平移动画第二步,在第一步平移的位置的基础上,再往上位移100
val translate2Anim = TranslateAnimation(0.0f, 0.0f, -100.0f, -200.0f)
//动画结束后回到原位
animation1Set.fillAfter = false
//动画执行时间(单位:毫秒)
animation1Set.duration = 1000
还记得第一段位移填写的最后一个参数吗,第一段位移往Y轴上移动了-100f,并且我们设置其为不回到原位,所以控件现在的位置就是Y轴上的-100f,然后我们还需要它再继续往上平移,所以这个距离要x2,也就是-200f,不理解没关系,等下可以运行看一下效果就明白了;
那么第二段位移动画也写好了,该怎么让这两个动画衔接起来呢,是不是应该有一个回调来告诉我动画执行结束,然后再去做之后的处理呢,这些属性动画已经帮我们想到了,属性动画提供了一个Animation.AnimationListener接口,实现该接口需要重载三个回调方法,分别是动画开始、结束以及重复执行时的生命周期方法,所以我们可以在结束的生命周期方法中让其执行第二段位移:
translate1Anim.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {
//动画开启前布局显示
binding.animationCard.visibility = View.VISIBLE
}
override fun onAnimationEnd(animation: Animation?) {
//第一轮动画结束后,先暂停2秒,然后再开启第二轮动画
GlobalScope.launch(Dispatchers.Main) {
delay(2000)
binding.animationCard.startAnimation(translate2Anim)
}
}
override fun onAnimationRepeat(animation: Animation?) {
}
})
代码中可以看到,开始时我让布局显示出来,然后动画开始执行,执行结束后就会走onAnimationEnd方法,在此方法中去执行第二段位移动画,这里使用了Kotlin协程来做了一个2秒的等待,GlobalScope.launch(Dispatchers.Main){}表示开启协程,注意这里的参数,一定要是Dispatchers.Main在主线程中调用,否则会报错;不了解kotlin或者协程的同学可以百度一下,是一个非常强大的功能,非常非常值得学习~
使用Kotlin协程需要引入以下依赖:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2"
implementation 'androidx.core:core-ktx:1.3.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.32"
到这里我们平移部分的动画就已经做好了,下面看一下效果吧
(图有点太糊了,凑合一下看吧,还是能看出效果的**·—·**)
- 渐变动画
渐变动画其实相对来说比较简单,其实就是从0-1然后再从1-0的一个渐变转换,废话不多说直接贴代码
//渐变动画第一步,从透明转变为实体
val alpha1Anim = AlphaAnimation(0.0f, 1.0f)
//渐变动画第二步,从实体转为透明
val alpha2Anim = AlphaAnimation(1.0f, 0.1f)
ok,这就是两段的渐变动画代码,其实就是实例化了两个对象,构造中的两个参数表示透明度的转换,取值范围为0.0f - 1.0f(0% - 100%)
渐变动画好了,那么这里有一个问题,该怎么让两种动画结合在一起运行?
其实属性动画还有一个类叫AnimationSet,其实就是动画集合的意思,它可以将多种动画效果添加进来然后一起执行,所以这里我们使用AnimationSet来将平移和渐变动画添加进来:
//第一轮动画集,往上平移加渐变为实体颜色,并且设置动画时长为1秒加动画结束后停留在平移位置
val animation1Set = AnimationSet(true)
animation1Set.fillAfter = true
animation1Set.duration = 1000
animation1Set.addAnimation(translate1Anim)
animation1Set.addAnimation(alpha1Anim)
//第二轮动画集,继续往上平移加渐变为透明颜色,并且设置动画时长为1秒加动画结束后回复为原来位置
val animation2Set = AnimationSet(true)
animation2Set.fillAfter = false
animation2Set.duration = 1000
animation2Set.addAnimation(translate2Anim)
animation2Set.addAnimation(alpha2Anim)
然后控件在调用startAnimation()的时候传入的参数不再是平移动画的对象,而是这个动画集
//第一轮动画开启
binding.animationCard.startAnimation(animation1Set)
同理,动画的状态回调接口也是
animation1Set.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {
//动画开启前布局显示
binding.animationCard.visibility = View.VISIBLE
}
override fun onAnimationEnd(animation: Animation?) {
//第一轮动画结束后,先暂停2秒,然后再开启第二轮动画
GlobalScope.launch(Dispatchers.Main) {
delay(2000)
binding.animationCard.startAnimation(animation2Set)
}
}
override fun onAnimationRepeat(animation: Animation?) {
}
})
animation2Set.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {}
override fun onAnimationEnd(animation: Animation?) {
//结束后布局隐藏
binding.animationCard.visibility = View.GONE
}
override fun onAnimationRepeat(animation: Animation?) {}
})
Ok,至此我们整个动画效果就算基本完成了,下面的是完整代码:
private fun startTranslateAnimation() {
//平移动画第一步,从原点往上位移100,
val translate1Anim = TranslateAnimation(0.0f, 0.0f, 0.0f, -100.0f)
//平移动画第二步,在第一步平移的位置的基础上,再往上位移60
val translate2Anim = TranslateAnimation(0.0f, 0.0f, -100.0f, -200.0f)
//渐变动画第一步,从透明转变为实体
val alpha1Anim = AlphaAnimation(0.0f, 1.0f)
//渐变动画第二步,从实体转为透明
val alpha2Anim = AlphaAnimation(1.0f, 0.1f)
//第一轮动画集,往上平移加渐变为实体颜色,并且设置动画时长为1秒加动画结束后停留在平移位置
val animation1Set = AnimationSet(true)
animation1Set.fillAfter = true
animation1Set.duration = 1000
animation1Set.addAnimation(translate1Anim)
animation1Set.addAnimation(alpha1Anim)
//第二轮动画集,继续往上平移加渐变为透明颜色,并且设置动画时长为1秒加动画结束后回复为原来位置
val animation2Set = AnimationSet(true)
animation2Set.fillAfter = false
animation2Set.duration = 1000
animation2Set.addAnimation(translate2Anim)
animation2Set.addAnimation(alpha2Anim)
//第一轮动画开启
binding.animationCard.startAnimation(animation1Set)
animation1Set.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {
//动画开启前布局显示
binding.animationCard.visibility = View.VISIBLE
}
override fun onAnimationEnd(animation: Animation?) {
//第一轮动画结束后,先暂停2秒,然后再开启第二轮动画
GlobalScope.launch(Dispatchers.Main) {
delay(2000)
binding.animationCard.startAnimation(animation2Set)
}
}
override fun onAnimationRepeat(animation: Animation?) {
}
})
animation2Set.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {}
override fun onAnimationEnd(animation: Animation?) {
binding.animationCard.visibility = View.GONE
binding.animationCard.startAnimation(animation1Set)
}
override fun onAnimationRepeat(animation: Animation?) {}
})
}