4

I'm trying to do an Android 5.0 shared element transition in a Master Detail situation from a ListView. I'm sending an Image.

I've run into a glitch on the enter transition where the Image doesn't move at all while the Master activity transitions out. Once the Detail activity is loaded, the Image starts in the top-left of the screen and animates into it's final location. The return transition works perfectly - the image animates from it's Detail position to the proper position in Master.

Here's what it looks like: https://www.youtube.com/watch?v=AzyA8i27qWc

I've set up the permissions correctly I believe, and specified the transition:

<item name="android:windowContentTransitions">true</item>        
<item name="android:windowAllowEnterTransitionOverlap">true</item>
<item name="android:windowAllowReturnTransitionOverlap">true</item>

<!-- specify shared element transitions -->
<item name="android:windowSharedElementEnterTransition">
    @transition/change_image_transform</item>
<item name="android:windowSharedElementExitTransition">
    @transition/change_image_transform</item>

That transition is defined as:

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <changeImageTransform/>
    <changeBounds/>
    <changeTransform/>
</transitionSet>

I've given the two views the same unique transitionName attribute, based on their position in the list.

I'm using makeSceneTransitionAnimation to do the animation:

    View photo = (View) adapter.getView(position, null, listView).findViewById(R.id.photo);
    View navigationBar = findViewById(android.R.id.navigationBarBackground);

    ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this,
            Pair.create(photo, photo.getTransitionName()),
            Pair.create(navigationBar, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME));

    Bundle extras = new Bundle();
    Patient selectedPatient = patientList.get(position);
    extras.putParcelable("patient", selectedPatient);
    extras.putInt("position", position);

    Intent intent = new Intent(PatientListActivity.this, PatientActivity_.class);
    intent.putExtras(extras);

    startActivity(intent, options.toBundle());

(I'm also specifying the navigationBar as shared to avoid it fading out, which works fine)

I don't understand why the return transition works fine but the enter transition doesn't. I've seen other questions where the solution was to delay the enter transition, which I have tried doing but the problem remains. Here's what I added to onCreate in my Detail activity:

super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_patient);

    patient = getIntent().getExtras().getParcelable("patient");
    patient = app.getPatientByName(patient.getName());

    patientPhotoView.setImageDrawable(patient.getPhoto());

    /* Set up transition functionality */
    position = getIntent().getExtras().getInt("position");
    patientPhotoView.setTransitionName("patientPhoto" + position);
    patientNameView.setTransitionName("patientName" + position);

    postponeEnterTransition();
    final ViewTreeObserver observer = getWindow().getDecorView().getViewTreeObserver();
    observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            final ViewTreeObserver observer = getWindow().getDecorView().getViewTreeObserver();
            observer.removeOnPreDrawListener(this);
            startPostponedEnterTransition();
            return true;
        }
    });

In my Detail Activity, there is no transitionName element, but I am setting it in code. The relevant part of the incoming activity (the Detail) is:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".PatientActivity">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="200dip"
            android:orientation="vertical"
            android:id="@+id/patient_top_layout">
            <ImageView
                android:id="@+id/patientPhoto"
                android:layout_width="92dip"
                android:layout_height="92dip"
                android:layout_below="@+id/connectionBar"
                android:layout_marginRight="12dip"
                android:layout_marginTop="12dip"
                android:contentDescription="Patient photo"
                android:src="@drawable/ic_profile_default" />
         </RelativeLayout>
    </LinearLayout>
</RelativeLayout

Happy to post other relevant code. Any ideas?

SOLVED

The user Chris P in the Android Development Google+ page figured this out for me. In my Master Activity in the onClick method, I had:

View photo = (View) adapter.getView(position, null, listView).findViewById(R.id.photo);

The solution was to replace that line with:

View photo = (View) listView.getChildAt(position).findViewById(R.id.photo);

I guess calling the adapter's getView method was re-creating the view itself - I'm guessing that method should ONLY be called when the list is first created.

Community
  • 1
  • 1
clay_to_n
  • 583
  • 3
  • 15
  • Is there any difference if you change `@transition/change_image_transform` to `@transition/change_image_transform`? Is there any difference if you change `getWindow().getDecorView()` to the shared element `ImageView` object you are using? – Alex Lockwood Mar 05 '15 at 03:20
  • I don't understand your first suggested change. I also added the change_image_transform code to the question. There was no difference when I changed the `getWindow().getDecorView()` to `patientPhotoView` (after also setting the transition name and photo). – clay_to_n Mar 05 '15 at 05:24
  • Oops, sorry. I had a typo. Is there any difference if you change `@transition/change_image_transform` to `@android:transition/move`? – Alex Lockwood Mar 05 '15 at 05:27
  • Wow, you've done your homework! The outgoing Activity is working as it should. It appears that the incoming activity is misbehaving. FWIW, the android:windowContentTransitions isn't the right attribute -- you want android:windowActivityTransitions. Material themes automatically set that to true, so you're ok. Anyhow, can you post your layout for the detail Activity? – George Mount Mar 05 '15 at 16:57
  • I posted the relevant parts of the detail activity's layout. As I mentioned before, the transitionName attribute is being set in onCreate for the detail view (I added that code in to the post). Thanks much for the help! – clay_to_n Mar 05 '15 at 19:25
  • Can you post a video illustrating what the glitchy transition looks like? You can use Android Studio's screen capture feature and upload a short video to YouTube relatively quickly. – Alex Lockwood Mar 13 '15 at 18:27
  • Uploaded a video here: https://www.youtube.com/watch?v=AzyA8i27qWc Still going to try commenting out large sections of my code in order to find what's breaking it. Will update with a resolution if I find one. – clay_to_n Mar 19 '15 at 00:45
  • I've just created a git repo with a very basic example of the issue, which I still haven't solved: https://github.com/clay-to-n/shared-element-transition-glitch – clay_to_n Mar 22 '15 at 06:36
  • Found the solution and added it to the question. – clay_to_n Mar 25 '15 at 19:22

1 Answers1

3

Tried to leave this in comments, but it was too long. I tried out your layout and didn't have any problems. I didn't even need the postponeEnterTransition call. That makes things a little trickier to debug.

The way Transitions work is by capturing the state of things before a change and then after a change and then animating the difference. In Activity Transitions, the "before" change is pulled from screen location and size of the shared element in the calling Activity. It then applies those bounds the current view hierarchy. It then requests a layout and captures the "after" state.

There's a sensitive point between the time the "before" state is captured and the "after" state is captured. If layout changes for any reason in there such that the values are captured in the after state aren't just a relayout of the "before" state, the Transition is going to be thrown off. Also possible is making a change to layout immediately after the Transition has started.

Are you using a delay loading mechanism or are you really fully loading the Patient in the onCreate call? Are you setting all the surrounding values at that time as well? I see that you have:

        <ImageView ...
            android:layout_below="@+id/connectionBar"
            ... />

So you may be getting a relayout after the connectionBar changes.

George Mount
  • 20,708
  • 2
  • 73
  • 61
  • Thanks for this response. I think I'm going to try cutting out sections of my code until I find out what's causing the relayout, assuming there is one. I'll try to detail my findings here to help others once I solve it. – clay_to_n Mar 10 '15 at 05:32
  • I recreated my circumstance from a bare project, and still get the glitch. I must be missing something somewhere - maybe my array adapter is weird? If the problem is a relayout, I don't have a clue where it would occur, since there's nothing else going on. Here it is: https://github.com/clay-to-n/shared-element-transition-glitch – clay_to_n Mar 22 '15 at 06:38
  • Solved the problem and added the solution to the question. Thanks for the help. – clay_to_n Mar 25 '15 at 19:22