Android 创建CircularReveal揭露动画的实现

本文详细介绍Android 5.0及以上版本中的揭露动画特性及其使用方法。揭露动画可通过ViewAnimationUtils类轻松实现,支持从一个点向外扩散或从外向内聚集的效果,适用于View组件的显示或隐藏。文章还提供了具体的代码示例,包括如何设置动画监听器。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在Android 5.0及更高的版本中,加入了一种全新的视觉动画效果,就是揭露动画。揭露动画在系统中很常见,就是类似波纹的效果,从某一个点向四周展开或者从四周向某一点聚合起来,本文实现的效果如下所示,可以用在Activity里面的View动画效果,也可以使用在Activity跳转过渡动画中:
这里写图片描述

使用揭露动画非常简单,Android Sdk中已经帮我们提供了一个工具类ViewAnimationUtils来创建揭露动画。ViewAnimationUtils里面只有两个静态方法createCircularReveal(View view, int centerX, int centerY, float startRadius, float endRadius),返回一个Animator动画对象。

public final class ViewAnimationUtils {
private ViewAnimationUtils() {}
/**
* Returns an Animator which can animate a clipping circle.
*


* Any shadow cast by the View will respect the circular clip from this animator.
*


* Only a single non-rectangular clip can be applied on a View at any time.
* Views clipped by a circular reveal animation take priority over
* {@link View#setClipToOutline(boolean) View Outline clipping}.
*


* Note that the animation returned here is a one-shot animation. It cannot
* be re-used, and once started it cannot be paused or resumed. It is also
* an asynchronous animation that automatically runs off of the UI thread.
* As a result {@link AnimatorListener#onAnimationEnd(Animator)}
* will occur after the animation has ended, but it may be delayed depending
* on thread responsiveness.
*


* Note that if any start delay is set on the reveal animator, the start radius
* will not be applied to the reveal circle until the start delay has passed.
* If it’s desired to set a start radius on the reveal circle during the start
* delay, one workaround could be adding an animator with the same start and
* end radius. For example:
* public static Animator createRevealWithDelay(View view, int centerX, int centerY, float startRadius, float endRadius) {
* Animator delayAnimator = ViewAnimationUtils.createCircularReveal(view, centerX, centerY, startRadius, startRadius);
* delayAnimator.setDuration(delayTimeMS);
* Animator revealAnimator = ViewAnimationUtils.createCircularReveal(view, centerX, centerY, startRadius, endRadius);
* AnimatorSet set = new AnimatorSet();
* set.playSequentially(delayAnimator, revealAnimator);
* return set;
* }
*
* @param view The View will be clipped to the animating circle.
* @param centerX The x coordinate of the center of the animating circle, relative to
* view.
* @param centerY The y coordinate of the center of the animating circle, relative to
* view.
* @param startRadius The starting radius of the animating circle.
* @param endRadius The ending radius of the animating circle.
*/
public static Animator createCircularReveal(View view,
int centerX, int centerY, float startRadius, float endRadius) {
return new RevealAnimator(view, centerX, centerY, startRadius, endRadius);
}
}

ViewAnimationUtils.createCircularReveal() 方法能够为裁剪区域添加动画以揭露或隐藏视图。我们主要使用createCircularReveal 方法,该方法有四个参数,第一个参数是执行揭露动画的View视图,第二个参数是相对于视图View的坐标系,动画圆的中心的x坐标,第三个参数是相对于视图View的坐标系,动画圆的中心的y坐标,第四个参数是动画圆的起始半径,第五个参数动画圆的结束半径。如下图所示:
这里写图片描述

揭露动画有两种效果,一种是显示一组UI元素,另一种是隐藏一组UI元素:

  • 以中心点为轴点,当开始半径小于结束半径时,从开始半径处向外扩大到结束半径处显示View
  • 以中心点为轴点,当开始半径大于结束半径时,从开始半径处向内缩小到结束半径处隐藏View
  • 注意:揭露动画对象只能使用一次,不能被重新使用,也就是说每次使用揭露动画都要调用ViewAnimationUtils.createCircularReveal()返回一个揭露动画对象使用,同时一旦开始了动画就不能暂停或重新开始。揭露动画是一种异步动画,可以自动运行在UI线程上。当揭露动画结束后,如果设置了Animator.AnimatorListener监听器,那么监听器的onAnimationEnd(Animator) 方法会被调用,但可能会被延迟调用,这取决于线程的响应能力。

    首先我们可以创建一个工具类:

    val Duration:Long=1000
    
    class CircularRevealUtil {
        var animationListener: CircularRevealAnimationListener? = null
    
        interface CircularRevealAnimationListener{
            fun onShowAnimationEnd()
            fun onHideAnimationEnd()
        }
    
        fun setCircularRevealAnimationListener(animationListener: CircularRevealAnimationListener){
            this.animationListener=animationListener
        }
    
        fun showTargetView(view: View){
            val centerX:Int=(view.left+view.right)/2
            val centerY:Int=(view.top+view.bottom)/2
            val finalRadius:Float=Math.max(view.width,view.height).toFloat()
            val anim: Animator = ViewAnimationUtils.createCircularReveal(view,centerX,centerY,0f,finalRadius)
            anim.duration=Duration
            view.clearAnimation()
            anim.addListener(object : AnimatorListenerAdapter(){
                override fun onAnimationStart(animation: Animator?) {
                    super.onAnimationStart(animation)
                    view.visibility= View.VISIBLE
                }
    
                override fun onAnimationEnd(animation: Animator?) {
                    super.onAnimationEnd(animation)
                    if(animationListener!=null){
                        animationListener!!.onShowAnimationEnd()//!!断言 当为空会抛出异常
                    }
                }
            })
            anim.start()
        }
    
        fun hideTargetView(view: View){
            val centerX:Int=(view.left+view.right)/2
            val centerY:Int=(view.top+view.bottom)/2
            val initRadius:Float=Math.max(view.width,view.height).toFloat()
            val anim: Animator = ViewAnimationUtils.createCircularReveal(view,centerX,centerY,initRadius,0f)
            anim.duration=Duration
            view.clearAnimation()
            anim.addListener(object: AnimatorListenerAdapter(){
                override fun onAnimationEnd(animation: Animator?) {
                    super.onAnimationEnd(animation)
                    view.visibility= View.GONE
                    if(animationListener!=null){
                        animationListener!!.onHideAnimationEnd()
                    }
                }
            })
            anim.start()
        }
    }

    CircularRevealAnimationListener接口是用户回调接口,当用户设置了接口回调时就会在揭露显示动画或揭露隐藏动画结束时收到通知从而进行操作。showTargetView方法是从隐藏到完全显示一个View,hideTargetView方法是从一个完全显示的View到隐藏。

    然后就可以在MainActivity中显示或隐藏一组View或者做跳转动画了。
    当需要经揭露动画作为跳转Activity动画时,需要设置下系统的默认动画效果。首先需要在styles.xml文件中设置主题,将window窗口的默认跳转动画关闭,将window背景设置为透明,如下所示:

    <resources>
    
        <!-- Base application theme. -->
        <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
            <!-- Customize your theme here. -->
            <item name="colorPrimary">@color/colorPrimary</item>
            <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
            <item name="colorAccent">@color/colorAccent</item>
    
            <item name="android:textAllCaps">false</item>
    
            <item name="android:windowAnimationStyle">@null</item>//设置跳转动画为null,即没有动画
            <item name="android:windowBackground">@android:color/transparent</item>//设置窗口背景为透明
            <!--<item name="android:colorBackgroundCacheHint">@null</item>-->
            <item name="android:windowIsTranslucent">true</item>//设置窗口为透明
        </style>
    
    </resources>

    在MainActivity中:

    
    class MainActivity : BaseActivity() {
        lateinit var imageView:ImageView
        lateinit var button:Button
        lateinit var rootView:ConstraintLayout
        lateinit var toSecondAct:Button
        lateinit var circularRevealAnim:CircularRevealUtil
        var isShow:Boolean=true
    
        override fun setView() {
            setContentView(R.layout.activity_main)
    
            imageView= findViewById(R.id.ImageView) as ImageView
            button= findViewById(R.id.Button) as Button
            rootView= findViewById(R.id.RootView) as ConstraintLayout
            toSecondAct= findViewById(R.id.ToSecondAct) as Button
        }
    
        override fun setData() {
            circularRevealAnim= CircularRevealUtil()
        }
    
        override fun setListener() {
            button.setOnClickListener{_->
                if(isShow){
                    isShow=false
                   button.text="showImage"
                    circularRevealAnim.hideTargetView(imageView)
                }else{
                    isShow=true
                    button.text="hideImage"
                    circularRevealAnim.showTargetView(imageView)
                }
            }
    
            toSecondAct.setOnClickListener{_->
                val intent=Intent(this,SecondActivity::class.java)
                startActivity(intent)
            }
        }
    
    }
    

    在SecondActivity中:

    class SecondActivity:BaseActivity(){
        lateinit var button:Button
        lateinit var imageView:ImageView
        lateinit var circularRevealAnim:CircularRevealUtil
        lateinit var rootView:ConstraintLayout
    
    
        override fun setView() {
            setContentView(R.layout.activity_second)
            button= findViewById(R.id.Second_Button) as Button
            imageView= findViewById(R.id.Second_ImageView) as ImageView
            rootView= findViewById(R.id.Second_RootView) as ConstraintLayout
        }
    
        override fun setData() {
            circularRevealAnim= CircularRevealUtil()
            rootView.post {
                circularRevealAnim.showTargetView(rootView)
            }
    
        }
    
        override fun setListener() {
            button.setOnClickListener{_->
                circularRevealAnim.hideTargetView(rootView)
            }
            circularRevealAnim.setCircularRevealAnimationListener(object:CircularRevealUtil.CircularRevealAnimationListener{
                override fun onHideAnimationEnd() {
                    finish()
                }
    
                override fun onShowAnimationEnd() {
    
                }
    
            })
        }
    
    
    
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值