1

In my Android app, I am needing to implement a scene transition that involves a change in the elevation (z-index) of one of the elements. As you can see in the image below in the start scene on the left the blue arc is displayed beneath the image of the dog. In the final transition displayed on the right, the image of the dog is displayed underneath the blue arc. My desire is to have a changeBounds transition of the image start first and then slightly later do a changeBounds transition of the arc. At about halfway through the transition, the bottom of the image should be positioned above the arc. At this halfway point I'd like the elevation/z-index of the image to change so that the dog image is displayed underneath the blue arc. before and after images

I currently have the theme of my app configured to use the following transitionSet for this scene change.

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="together"
    android:duration="2000">

    <changeBounds
        android:startDelay="0"
        android:duration="1000"
        android:resizeClip="false"
        >
        <targets>
            <target android:targetId="@id/charlie"/>
        </targets>
    </changeBounds>

    <transition class="com.motb.transitiontest.ElevationTransition">
        android:startDelay="0"
        android:duration="1000"
        <targets>
            <target android:targetId="@id/charlie"/>
            <target android:targetId="@id/arc1"/>
        </targets>
    </transition>


    <changeBounds
        android:startDelay="300"
        android:duration="1000"
        >
        <targets>
            <target android:targetId="@id/arc1"/>
            <target android:targetId="@id/helloText" />
        </targets>
    </changeBounds>

</transitionSet>

I'm attempting to have the elevation change using this custom "ElevationTransition" shown below:

public class ElevationTransition extends Transition {

public static final String TAG = "ElevationTransition" ;

public ElevationTransition(Context context, AttributeSet attributes ) {
    super( context, attributes );
}

private static final String PROPNAME_TRANS_Z = "com.motb:transition:transz";
@Override
public void captureStartValues(TransitionValues transitionValues) {
    float translationZ = transitionValues.view.getTranslationZ() ;
    transitionValues.values.put(PROPNAME_TRANS_Z + "_start" , translationZ );
}

@Override
public void captureEndValues(TransitionValues transitionValues) {
    float translationZ = transitionValues.view.getTranslationZ() ;
    transitionValues.values.put(PROPNAME_TRANS_Z + "_end", translationZ );
}
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
                               TransitionValues endValues) {
    if (startValues == null || endValues == null) {
        return null;
    }
    final View view = endValues.view;
    float startElevation = (Float) startValues.values.get(PROPNAME_TRANS_Z + "_start");
    float endElevation = (Float) endValues.values.get(PROPNAME_TRANS_Z + "_end" );
    if (startElevation != endElevation) {
        view.setTranslationZ(startElevation);
        return ObjectAnimator.ofFloat(view, View.TRANSLATION_Z,
                startElevation, endElevation);
    }
    return null;
}

}

The main activity just displays the blank map using the following layout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.motb.transitiontest.MainActivity"
android:id="@+id/main"
android:onClick="onClick"
android:background="@drawable/treasuremap"
>

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="250dp"
    android:id="@+id/fragmentContainer"
    android:layout_alignParentBottom="true"
    />


</RelativeLayout>

A click on the map replaces the above FrameLayout with a fragment displaying the bottom "card" that uses the following layout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="250dp"
tools:context="com.motb.transitiontest.MainActivity"
android:id="@+id/main"
android:background="@android:color/transparent"
>


<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="160dp"
    android:background="@drawable/arc_bg"
    android:backgroundTint="#ff0000ff"
    android:layout_alignParentBottom="true"
    android:transitionName="rv1"
    android:id="@+id/arc1"
    android:translationZ="20dp"

    >

    <TextView
        android:id="@+id/helloText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World Transition Test"
        android:layout_marginStart="165dp"
        android:layout_marginTop="27dp"
        android:textSize="40dp"
        android:gravity="left"
        android:transitionName="tv1"
        android:textColor="#ffffffff"
        />


</RelativeLayout>

<ImageView
    android:layout_width="300px"
    android:layout_height="300px"
    android:layout_alignParentTop="true"
    android:layout_marginTop="80dp"
    android:layout_marginStart="20dp"
    android:transitionName="iv1"
    android:src="@drawable/charlie"
    android:id="@+id/charlie"
    android:translationZ="30dp"
    android:stateListAnimator="@null"
    />


</RelativeLayout>

A click on the fragment, starts the second activity with the larger image of the dog using the xml below:

`
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.motb.transitiontest.Activity2"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
>


  <RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.motb.transitiontest.Activity2"
    android:layout_marginTop="200dp"
    android:background="@drawable/arc_bg"
    android:layout_alignParentBottom="true"
    android:transitionName="rv1"
    android:id="@+id/arc1"
    android:backgroundTint="#ff0000ff"
    android:translationZ="40dp"
    >


    <TextView
        android:id="@+id/helloText"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:layout_marginLeft="50dp"
        android:layout_marginRight="50dp"
        android:text="Hello World Transition Test"
        android:textSize="40dp"
        android:textAlignment="center"
        android:transitionName="tv1"
        android:textColor="#ffffffff"
        />


</RelativeLayout>


<ImageView
    android:id="@+id/charlie"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:src="@drawable/charlie"
    android:transitionName="iv1"
    android:scaleType="centerCrop"
    android:translationZ="0dp"
    android:stateListAnimator="@null"
    />



</RelativeLayout>`

Currently, what I see is that the elevation instantly changes at the start of the transition resulting in the dog image to be obscured by the arc.

John Weidner
  • 2,055
  • 3
  • 18
  • 31
  • A more elegant and efficient solution will be to write your custom class to draw the image with progressive z index and you can use valueAnimator to give smoothness to your animation – sumit Jun 12 '17 at 15:42
  • @sumit Please provide more details on why you think a valueAnimator is better than an ObjectAnimator in this case. What makes it more elegant and efficient? – John Weidner Jun 12 '17 at 17:54
  • Can you also post the view hierarchy? – azizbekian Jun 15 '17 at 06:58
  • @azizbekian - I added the xml layouts to the bottom of the question. – John Weidner Jun 15 '17 at 18:47

1 Answers1

3

I checked the github. It's a bug.

I can categorically run the transition between scenes using Transtions and TransitionManager.go(). But, when you run the transition between the fragment and activity it always instantly changes the fade animation. There's a couple fade bugs fixed in Support 26.0.0 beta-1

  • When a Fade transition is interrupted and reversed, the View starts the animation from the beginning. (Fix ported from Android Framework.)
  • Transition.Fade ignores initial alpha of View (AOSP issue 221820)

But, ask yourself, with all your fiddling did you ever see it do an actual fade transition? -- No. Like ignore all the moving and whatever, can you get it to do a fade at all? Because that's all my suggestion needed and I can't get it running the way you have it running. But, obviously all the moving of the elements does work so it's a damned bug.


You can totally fake it too. If you had fades, you could just invoke it and get it to work. You get the correct animation if you change the onCreate() in the Activity2 to be:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);

    setContentView(R.layout.activity2);
    findViewById(R.id.arc1).setVisibility(View.VISIBLE);
    findViewById(R.id.arc2).setAlpha(0);
    findViewById(R.id.arc2).animate().alpha(1).setDuration(2000).start();
}

I only set the visibility of that arc1 to the correct one because you had it wrong. That's the one that is always visible.

I think then add an animation for the same time period as the other one to phase it in, and boom. It works. It's a stupid bug. You can't get fades between activity for no good reason at all.


I discovered the alpha one myself. The first one might be what's happening. Or it could be a different class of bug altogether. But, I can assure you. I checked and did everything right. And it still failed. Obviously the work around would be to use a scene transition where they work. Perhaps not using a new activity as such at all.

There's a bunch of workarounds you could obviously come up with. Just launch the activity with the layout it's supposed to have then setup a scene to transition to the proper layout. Add in another transition to the layout yourself in the first activity. Or whatnot. But, it doesn't work. You do that transition from the fragment to the activity and the stuff will move but the fade always throws a conniption and refuses. It instantly takes on the visibility of the other view and without alpha working either, there's no way to rectify that easily either.

My original code that had something working was using scenes:

boolean to = true;
public void onClick(View view) {
    Scene scene;
    ViewGroup mSceneRoot = (ViewGroup) findViewById(R.id.container);
    if (to) {
        Transition mFadeTransition =
                TransitionInflater.from(this).
                        inflateTransition(R.transition.transitionset);
        scene = Scene.getSceneForLayout(mSceneRoot, R.layout.fragment_replacement, this);

        TransitionManager.go(scene, mFadeTransition);
    }
    else {
        Transition mFadeTransition =
                TransitionInflater.from(this).
                        inflateTransition(R.transition.backwardstransitionset);
        scene = Scene.getSceneForLayout(mSceneRoot, R.layout.fragment_rep, this);
        TransitionManager.go(scene, mFadeTransition);
    }
    to = !to;
}

Leaving bits of old answer when I figured it was his fault for being unable to implement my clearly very brilliant idea of how to do the transition. The bug is he has no damned fade that works, and that was always the problem.


You cannot animate that merely with Z order. You change the order and they draw on top of each other rather than the other way around. To do what you're suggesting what you need to do is effectively have both orders at the same time and animate their transition with transparency.

You draw, Arc, Dog, another Arc. Then the variation in the Z-order is literally the transparency of the Arc drawn on top. If it is entirely opaque the Arc is on top of the dog. If it's entirely transparent then the Dog is over the Arc. But, if the topmost Arc is partially transparent, it'll look like the dog is melting through the arc, or arc is melting through the dog, depending on how we adjust the transparency. You can pull the same trick elsewhere. Just put the thing in both places. If it's opaque it's covered, but if it's transparent it only show the lowermost arc, and a mix of the arc with the between the objects if partially transparent. At the end of the animation, just put them in the correct order.

So during the Animation, you move the 2 Arcs to the same position always. You should see them as the same arc. And change the opacity of the Arc on top, and do whatever you're doing with that dog.


Video:

https://youtu.be/zVs3qzPU2FM


Create the layouts. Sandwich the bit you want to change height between two copies of the other object. In the one of the layouts, set the visibility to invisible. So all items are in both layouts in the same order. But, in one, the second copy of the element in question is invisible. Make sure the two copies are in the same position in both. When you change the bounds of one copy, make sure you also change the bounds of the other. Move them as a unit. When you transition the views, fade the item out while moving it around or whatnot. Since there's a second copy it won't look like a fade out, it'll look like a melt-through.


Tatarize
  • 10,238
  • 4
  • 58
  • 64
  • I think I understand what you are suggesting, but still not able to figure out the implementation. Should your suggestion work with ActivityOptions.makeSceneTransitionAnimation? I now have two arcs in my starting fragment's layout and in the final activity's layout. I've added a to my transition.xml. But what I see is the second arc is immediately visible instead of fading in. – John Weidner Jun 16 '17 at 14:58
  • The two arcs must be moved coincident to each other. You should only ever see them as 1 arc. And if the arc is fading in it should start at 0 alpha then transition up to 1. While always being coincident with the other arc at the bottom of the draw pile. – Tatarize Jun 16 '17 at 15:55
  • I always roll up a custom animator so I'm not sure about how you're moving them around. And I find your mock-up confusing (did the dog picture take over for the map picture and get big or something)? – Tatarize Jun 16 '17 at 16:27
  • Yes, the dog enlarges and relocates using the ChangeBounds in the scene transition animation. – John Weidner Jun 16 '17 at 17:49
  • I ended up not figuring out how to do the two arc fade that you suggested. I ended up just moving and resizing the dog image in the scene transition and leaving the arc in the same location. Then in the onCreate of the second activity, I start an animator after 150ms that moves the arc up to the desired location. – John Weidner Jun 16 '17 at 18:03
  • No. Both arcs, moved at the same time. Treat them like 1 arc, When you move one. Move the other. – Tatarize Jun 16 '17 at 20:52
  • Thanks for the video. At least now I know it is possible. I posted my entire project on github as https://github.com/JohnWeidner/TransitionTest.git I still haven't gotten your solution to work. Any chance you can take a peek at my code to see what's wrong with what I'm doing. – John Weidner Jun 19 '17 at 15:58
  • The git repository had two transitions. When you first click anywhere on the map, the MainActivity displays Fragment1 with the arc and dog at the bottom. Then, if you tap that bottom area, the transition to display the image at the top of the screen kicks off by starting Activity2. The transition1.xml file is configured as the enter/exit transitions for the theme associated with Activity2. It's this second transition that I'm trying to get to display correctly. – John Weidner Jun 19 '17 at 22:32
  • Yeah, and it has a bug: Also see: https://stackoverflow.com/questions/38748658/why-fade-transition-doesnt-work-on-shared-element – Tatarize Jun 19 '17 at 23:09
  • It needs to run based on the alpha, and cannot due to the bug. I see how it's working and it should work, but you cannot fade between activities. Without some custom code or work. That bug is fixed in support 26.0.0. – Tatarize Jun 19 '17 at 23:11