Android属性动画 使用位移、渐变动画构成气泡效果

  • 简介:
    最近项目中需要展示一个气泡效果,当界面中数量发生变化时,会有一个数量+1的气泡动画,如下图所示:

image.png

动效说明:
首先位移起始点是在与数字居中的位置,开始透明度为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"

到这里我们平移部分的动画就已经做好了,下面看一下效果吧

soogif.gif

(图有点太糊了,凑合一下看吧,还是能看出效果的**·—·**)

  • 渐变动画

渐变动画其实相对来说比较简单,其实就是从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?) {}
    })
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值