Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to restore element in Activity Transition Animation to different position?

I have LinearLayout in a vertical ScrollView with multiple ImageViews, when I click on one image view I do a Transition Animation from (Activity 1) to a Full Screen Gallery (Activity 2), the gallery has view pager where user can swipe right / left .. now when user swipes to a different Image in the gallery and presses back the current visible Image in (Activity 2) is restored back (again using transition animation) to the starting position in Activity 1, the problem is that the image is restored until it reaches the old position and then disappears and reveals a different image (since this is already too complicated to discuss) I attached an image.

enter image description here

Activity1 Layout:

<LinearLayout>
   <ImageView />
   <ImageView />
   <ImageView />
   <ImageView />
   <ImageView />
   <ImageView />
</LinearLayout>

Acivity2 (Gallery) Layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent">

    <android.support.v7.widget.AppCompatImageView
        android:id="@+id/animation_image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/transparent"
        android:scaleType="fitCenter"
        android:src="@drawable/placeholder"
        android:transitionName="@string/transition_article_image_gallery"
        android:layout_centerInParent="true"
        />

    <ViewPager
        android:id="@+id/gallery_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

On click on any image from Activity 1:

public void openGallery(int position, ArrayList<String> images, View view)
    {
        Intent intent = new Intent(getActivity(), GalleryActivity.class);
        intent.putStringArrayListExtra(GalleryActivity.EXTRA_GALLERY_IMAGES, images);
        intent.putExtra(GalleryActivity.EXTRA_POSITION, position);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
                getActivity(),
                new Pair<>(view, TARGET_ELEMENT_TRANSITION_NAME_ATTRIB_VALUE)
            );
            ActivityCompat.startActivity(getActivity(), intent, options.toBundle());
        } else {
            getActivity().startActivity(intent);
        }
    }

UPDATE The code from George Mount, worked but there's one trick, it's mainly that this callback is called 2 times when the transition starts and on the way back, so it's important to reset the position and check it on the way back as follows:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        getDetailsActivity().setExitSharedElementCallback(new SharedElementCallback()
        {
            @TargetApi(Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void onMapSharedElements(List<String> names, Map<String, View> sharedElements)
            {
                if (position != -1) {                        sharedElements.put(getString(R.string.transition_article_image_gallery), articleImagesHolder.getChildAt(position));
                }
            }
        });
    }

and

    public void openGallery(int pos, ArrayList<String> images, View view)
    {
        // reset the return position
        position = -1; 
    }

and the following in my fragment:

public void onActivityReenter(int resultCode, Intent data)
{
    if (resultCode == Activity.RESULT_OK) {
        int pos = data.getIntExtra(EXTRA_IMAGE_POSITION, -1);
        if (pos != -1) {
            position = pos;
        }
    }
}

and the following in my (Activity1):

 @Override
public void onActivityReenter(int resultCode, Intent data)
{
    ArticleDetailsFragment fragment = (ArticleDetailsFragment) getSupportFragmentManager().findFragmentByTag(ArticleDetailsFragment.class.getSimpleName());
    if (fragment != null) {
        fragment.onActivityReenter(resultCode, data);
    }
}

Update 2 It seems that George already wrote some articles about Transitions, but they're not popular nor showing in Google Search Results, so I thought that it'd be useful to share them here:

https://halfthought.wordpress.com/2014/12/

https://halfthought.wordpress.com/2014/11/

https://halfthought.wordpress.com/2015/06/

like image 305
Shehabic Avatar asked Feb 07 '23 05:02

Shehabic


1 Answers

First, you're using the transition names wrong in your call. The name in the ActivityOptions.makeSceneTransitionAnimation is the name of the view in Activity 2. You can use any name you want in Activity 1 and the framework will map the names. This way you can do a many-to-one mapping. For example, if you have a list view of images and you click on one of them, it should be able to transition to a single image in Activity 2. In your case:

<LinearLayout>
   <ImageView android:transitionName="image1" />
   <ImageView android:transitionName="image2" />
   <ImageView android:transitionName="image3" />
   <ImageView android:transitionName="image4" />
   <ImageView android:transitionName="image5" />
   <ImageView android:transitionName="image6" />
</LinearLayout>

and when you make the call:

 ActivityOptionsCompat options =
     ActivityOptionsCompat.makeSceneTransitionAnimation(
            getActivity(), view, Activity2.IMAGE_VIEW_NAME);

And in your Activity 2, you should have an ImageView with the transitionName of Activity2.IMAGE_VIEW_NAME.

But that's not what you were asking about.

Because you're sharing a different element on the way back, you're going to have to override the shared element mappings. You can do this in a couple of ways in your circumstances. The best way requires you to change the mapping of shared elements in Activity 1.

In Activity1, set a SharedElementCallback:

setExitSharedElementCallback(new SharedElementCallback() {
    @Override
    public void onMapSharedElements(List<String> names,
        Map<String, View> sharedElements) {
        sharedElements.put(Activity2.IMAGE_VIEW_NAME, newSharedElement);
    }
}

So, how do you know which view it should return to? You should use startActivityForResult and so that the called Activity can return the correct view to you. Here, I assume you use the same EXTRA_POSITION field when you call setResult with an Intent. You can then override onActivityReenter to do what you need:

@Override
public void onActivityReenter(int resultCode, Intent data) {
    int position = data.getIntExtra(GalleryActivity.EXTRA_POSITION, -1);
    if (position != -1) {
        // I'm assuming child index is the same as position here.
        newSharedElement = mLinearLayout.getChildAt(position);
    }
}

The other thing you can do at this point is scroll the view to a position such that the view is visible. If you're using a recycling container like ListView or RecyclerView, you'll need to use postponeEnterTransition and startPostponedEnterTransition to ensure that the views are laid out prior to starting the transition.

like image 133
George Mount Avatar answered Apr 30 '23 12:04

George Mount