Transitioning a scene with a nested common element

I am trying to use the transition APIs to animate a common element between two groups of views. The goal is that the green view moves "from the parent" to a new position.

enter image description here enter image description here enter image description here

I have the following layouts:

first.xml :

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:layout_width="200dp" android:layout_height="200dp" android:background="#f00" /> <FrameLayout android:layout_width="200dp" android:layout_height="200dp" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:background="#00f"> <View android:id="@+id/myview" android:layout_width="100dp" android:layout_height="100dp" android:layout_gravity="center" android:background="#0f0" /> </FrameLayout> </RelativeLayout> 

second.xml :

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:layout_width="200dp" android:layout_height="200dp" android:background="#f00"> <View android:id="@+id/myview" android:layout_width="100dp" android:layout_height="100dp" android:layout_gravity="center" android:background="#0f0" /> </FrameLayout> <FrameLayout android:layout_width="200dp" android:layout_height="200dp" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:background="#00f" /> </RelativeLayout> 

However, I cannot get this to work correctly. By default, the transition simply disappears, and the ChangeBounds transition ChangeBounds nothing, and ChangeTransform doesn't look right either.

The code I'm using is:

 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final ViewGroup root = (ViewGroup) findViewById(android.R.id.content); setContentView(R.layout.first); View myView1 = findViewById(R.id.myview); myView1.setTransitionName("MYVIEW"); new Handler().postDelayed(new Runnable() { @Override public void run() { View second = LayoutInflater.from(MainActivity.this).inflate(R.layout.second, root, false); View myView2 = second.findViewById(R.id.myview); myView2.setTransitionName("MYVIEW"); Scene scene = new Scene(root, second); Transition transition = new ChangeTransform(); transition.addTarget("MYVIEW"); transition.setDuration(3000); TransitionManager.go(scene, transition); } }, 2000); } 

Now I can do it manually by creating the animation myself using ViewOverlay . However, I am looking for a solution using the transition API. Is it possible?

In addition, I do not want to โ€œsmooth outโ€ the hierarchy. I intentionally insert a View to accommodate more complex use cases.

+6
source share
3 answers

Yes, rethinking is possible using the Android transition API. There is one main limitation to consider when re-viewing views:

Usually for each child view it is allowed to draw only inside it. This will lead to the disappearance of the idea when he reaches the boundary of his original parent and reappears in the new parent after some time.

Disable children until your view hierarchies for the respective scenes have been set by setting android:clipChildren="false" for all matching parents.

In more complex hierarchies, such as adapter-supported views, more dynamically switch using detailed clipping using the TransitionListener .

You will also need to use ChangeTransform to re-view the view, instead of ChangeBounds or add it to the TransitionSet .

There is no need for a transition name or goal at this transition level, since the structure will determine this for itself. If the situation gets complicated, you will need to either

  • mapping resource identifiers or
  • match name matching

for submissions involved in the transition.

+1
source

The ChangeBounds class has an obsolete setReparent(Boolean) method that refers to ChangeTransform "to handle transitions between different parents." This ChangeTransform class ChangeTransform not produce the desired effect at all.

From the ChangeBounds source, we can see that if reparent set to true (plus some other conditions), the sceneroot overlay is used to add the overlay. For some reason this does not work: perhaps because it is out of date.

I managed to get around this by expanding ChangeBounds to use the overlay whenever I want (in Kotlin):

 class OverlayChangeBounds : ChangeBounds() { override fun createAnimator(sceneRoot: ViewGroup, startValues: TransitionValues?, endValues: TransitionValues?): Animator? { if (startValues == null || endValues == null) return super.createAnimator(sceneRoot, startValues, endValues) val startView = startValues.view val endView = endValues.view if (endView.id in targetIds || targetNames?.contains(endView.transitionName) == true) { startView.visibility = View.INVISIBLE endView.visibility = View.INVISIBLE endValues.view = startValues.view.toImageView() sceneRoot.overlay.add(endValues.view) return super.createAnimator(sceneRoot, startValues, endValues)?.apply { addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { endView.visibility = View.VISIBLE sceneRoot.overlay.remove(endValues.view) } }) } } return super.createAnimator(sceneRoot, startValues, endValues) } } private fun View.toImageView(): ImageView { val v = this val drawable = toBitmapDrawable() return ImageView(context).apply { setImageDrawable(drawable) scaleType = ImageView.ScaleType.CENTER_CROP layout(v.left, v.top, v.right, v.bottom) } } private fun View.toBitmapDrawable(): BitmapDrawable { val b = Bitmap.createBitmap(layoutParams.width, layoutParams.height, Bitmap.Config.ARGB_8888); draw(Canvas(b)); return BitmapDrawable(resources, b) } 
0
source

here is my workaround for such a case (it's just part of ChangeBounds in a separate class)

 import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.annotation.TargetApi; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Path; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.transition.Transition; import android.transition.TransitionValues; import android.util.Property; import android.view.View; import android.view.ViewGroup; @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class AbsoluteChangeBounds extends Transition { private static final String PROPNAME_WINDOW_X = "android:changeBounds:windowX"; private static final String PROPNAME_WINDOW_Y = "android:changeBounds:windowY"; private int[] tempLocation = new int[2]; private static final Property<Drawable, PointF> DRAWABLE_ORIGIN_PROPERTY = new Property<Drawable, PointF>(PointF.class, "boundsOrigin") { private Rect mBounds = new Rect(); @Override public void set(Drawable object, PointF value) { object.copyBounds(mBounds); mBounds.offsetTo(Math.round(value.x), Math.round(value.y)); object.setBounds(mBounds); } @Override public PointF get(Drawable object) { object.copyBounds(mBounds); return new PointF(mBounds.left, mBounds.top); } }; @Override public void captureStartValues(TransitionValues transitionValues) { captureValues(transitionValues); } @Override public void captureEndValues(TransitionValues transitionValues) { captureValues(transitionValues); } private void captureValues(TransitionValues values) { View view = values.view; if (view.isLaidOut() || view.getWidth() != 0 || view.getHeight() != 0) { values.view.getLocationInWindow(tempLocation); values.values.put(PROPNAME_WINDOW_X, tempLocation[0]); values.values.put(PROPNAME_WINDOW_Y, tempLocation[1]); } } @Override public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { if (startValues == null || endValues == null) { return null; } final View view = endValues.view; sceneRoot.getLocationInWindow(tempLocation); int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X) - tempLocation[0]; int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y) - tempLocation[1]; int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X) - tempLocation[0]; int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y) - tempLocation[1]; // TODO: also handle size changes: check bounds and animate size changes if (startX != endX || startY != endY) { final int width = view.getWidth(); final int height = view.getHeight(); Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); view.draw(canvas); final BitmapDrawable drawable = new BitmapDrawable(bitmap); view.setAlpha(0); drawable.setBounds(startX, startY, startX + width, startY + height); sceneRoot.getOverlay().add(drawable); Path topLeftPath = getPathMotion().getPath(startX, startY, endX, endY); PropertyValuesHolder origin = PropertyValuesHolder.ofObject( DRAWABLE_ORIGIN_PROPERTY, null, topLeftPath); ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, origin); anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { sceneRoot.getOverlay().remove(drawable); view.setAlpha(1); } }); return anim; } return null; } } 
0
source

All Articles