Search code examples
androidandroid-5.0-lollipop

How to circular reveal the entire activity


I tried using android:windowEnterTransition and android:windowExitTransition but that seems to animate each view in the activity, i.e. revealing each view separately. How can I animate the whole activity with content on it? There are no shared elements between two activities.


Solution

  • After a lot of research and android source code reading, I figured out how to do this. It's in Scala but you should translate that to Java easily.

    The following are the most important parts.

    CircularRevealActivity.scala:

    override protected def onCreate(savedInstanceState: Bundle) {
      super.onCreate(savedInstanceState)
      val window = getWindow
      val decor = window.getDecorView.asInstanceOf[ViewGroup]
      // prevent fading of background
      decor.setBackgroundColor(android.R.color.transparent)
      if (Build.version >= 21) {
        window.setEnterTransition(circularRevealTransition)
        window.setReturnTransition(circularRevealTransition)
        // decor.setTransitionGroup(true) won't work
        for (i <- 0 until decor.getChildCount) {
          val child = decor.getChildAt(i).asInstanceOf[ViewGroup]
          if (child != null) child.setTransitionGroup(true)
        }
        if (savedInstanceState == null) {
          val intent = getIntent
          val x = intent.getFloatExtra(EXTRA_SPAWN_LOCATION_X, Float.NaN)
          if (!x.isNaN) {
            val y = intent.getFloatExtra(EXTRA_SPAWN_LOCATION_Y, Float.NaN)
            if (!y.isNaN) circularRevealTransition.spawnLocation = (x, y)
          }
        }
      }
    }
    

    CircularReveal.scala:

    @TargetApi(21)
    class CircularReveal(context: Context, attrs: AttributeSet = null)
      extends Visibility(context, attrs) {
      var spawnLocation: (Float, Float) = _
      var stopper: View = _
      private val metrics = new DisplayMetrics
      private lazy val wm = context.getSystemService(Context.WINDOW_SERVICE)
        .asInstanceOf[WindowManager]
      private def getEnclosingCircleRadius(x: Float, y: Float) =
        math.hypot(math.max(x, metrics.widthPixels - x),
                   math.max(y, metrics.widthPixels - y)).toFloat
    
      override def onAppear(sceneRoot: ViewGroup, view: View,
                            startValues: TransitionValues, endValues: TransitionValues) = {
        wm.getDefaultDisplay.getMetrics(metrics)
        val (x, y) = LocationObserver.getRelatedTo(spawnLocation, view)
        new NoPauseAnimator(ViewAnimationUtils
          .createCircularReveal(view, x.toInt, y.toInt, 0,
            getEnclosingCircleRadius(x, y)))
      }
      override def onDisappear(sceneRoot: ViewGroup, view: View,
                               startValues: TransitionValues, endValues: TransitionValues) = {
        wm.getDefaultDisplay.getMetrics(metrics)
        val (x, y) = if (stopper == null)
          LocationObserver.getRelatedTo((metrics.widthPixels * .5F,
            metrics.heightPixels.toFloat), view)
        else LocationObserver.getRelatedTo(stopper, view)
        new NoPauseAnimator(ViewAnimationUtils
          .createCircularReveal(view, x.toInt, y.toInt,
            getEnclosingCircleRadius(x, y), 0))
      }
    }