Android 创建CircularReveal揭露动画的实现

来源:互联网 发布:影响因素分析模型知乎 编辑:程序博客网 时间:2024/05/20 17:40

在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=1000class 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() {            }        })    }}