2

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.

Mygod
  • 2,077
  • 1
  • 19
  • 43

2 Answers2

2

There are a couple of ways to animate the entire Activity. The most efficient mechanism is using Window Transitions. These operate against the Window so the content does not need to be redrawn on each frame. The down side is that the operations are limited to the older Animation Framework.

Typically, you'd specify the window animations using a style. You can see how it is done here: Start Activity with an animation

You can also use overridePendingTransition or ActivityOptions.makeCustomAnimation

If you want to use the lollipop Activity Transitions framework, you can use windowEnterTransition. If you want just your content to be operated on, set the outermost ViewGroup to have:

<WhateverViewGroup ... android:transitionGroup="true"/>

You may want to give your view group a name or id and use it in the enter transition so that it targets only that group. Otherwise it will target things like the status bar background also.

If you want it to operate on the entire Window contents:

getWindow().getDecorView().setTransitionGroup(true)

This will force the window contents to act as a unit.

Community
  • 1
  • 1
George Mount
  • 20,708
  • 2
  • 73
  • 61
  • 1
    `getDecorView()` returns `View`, so it should be `((ViewGroup) getWindow().getDecorView()).setTransitionGroup(true)`. But unfortunately this doesn't seem to work. And `android:transitionGroup="true"` can't handle status bar and navigation bar correctly. :-( – Mygod Apr 11 '15 at 02:05
  • Here's the error message: `java.lang.ClassCastException: android.view.ViewRootImpl cannot be cast to android.view.ViewGroup at android.app.ActivityTransitionCoordinator.setSharedElementMatrices(ActivityTransitionCoordinator.java:503)`. – Mygod Jul 05 '16 at 23:43
  • The error message above is produced by setting it as a shared element. Here's the reason why your solution won't work: https://android.googlesource.com/platform/frameworks/base/+/fc7476d/core/java/com/android/internal/policy/PhoneWindow.java#2872 `@Override public boolean isTransitionGroup() { return false; }` – Mygod Jul 06 '16 at 01:46
-1

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))
  }
}
Mygod
  • 2,077
  • 1
  • 19
  • 43