saurabh dhillon
saurabh dhillon

Reputation: 808

Shared element transition between fragments of different activities

I am trying to implement shared element transition between fragments of different activites, i have attained shared element enter transition but could not manage return transition on backpressed.

Fragment A is hosted in Activity A, on button click an image is added as shared element and Activity B is launched where Fragment B is hosted which contains the target view for shared element.

Activity A:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_a);

    getSupportFragmentManager()
            .beginTransaction()
            .addToBackStack(null)
            .replace(R.id.content, FragmentA.newInstance())
            .commit();
}

@Override
public void onBackPressed() {
    super.onBackPressed();
    finishAfterTransition();
}

Fragment A:

 @Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    postponeEnterTransition();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        setSharedElementEnterTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move));
        setSharedElementReturnTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move));
    }
}


@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        final ImageView imageView = (ImageView) view.findViewById(R.id.simple_activity_a_imageView);

        Button button = (Button) view.findViewById(R.id.simple_activity_a_btn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getActivity(), ActivityB.class);
                ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
                        getActivity(),
                        imageView,
                        ViewCompat.getTransitionName(imageView));
                startActivity(intent, options.toBundle());
            }
        });

    }

Activity B:

 @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_fragment_to_fragment);

    getSupportFragmentManager()
            .beginTransaction()
            .addToBackStack(null)
            .replace(R.id.content, FragmentB.newInstance())
            .commit();
}

@Override
public void onBackPressed() {
    super.onBackPressed();
    finishAfterTransition();
}

Fragment B:

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    postponeEnterTransition();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        setSharedElementEnterTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move));
        setSharedElementReturnTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move));
    }
}

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    TextView detailTextView = (TextView) view.findViewById(R.id.simple_activity_b_text);
    detailTextView.setText("detail");

    ImageView imageView = (ImageView) view.findViewById(R.id.simple_activity_b_image);
    imageView.setVisibility(View.VISIBLE);
    view.findViewById(R.id.activity_simple_two).setVisibility(View.VISIBLE);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        imageView.setTransitionName(getString(R.string.simple_activity_transition));
    }

    Glide.with(this)
            .load(GlideFragmentA.ARMADILLO_PIC_URL)
            .centerCrop()
            .dontAnimate()
            .listener(new RequestListener<String, GlideDrawable>() {
                @Override
                public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
                    startPostponedEnterTransition();
                    return false;
                }

                @Override
                public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
                    startPostponedEnterTransition();
                    return false;
                }
            })
            .into(imageView);
}

Upvotes: 5

Views: 2428

Answers (2)

Julian Os
Julian Os

Reputation: 281

Strongly simplified, Fragments are basically fancy ViewGroups with their own lifecycle and logic, which are part of an Activity's view tree just as any other ViewGroup would be. Thus, when looking at transitions between two Activities, you are not actually performing a fragment-level shared element transition, but one on the activity level.

It is necessary to be made sure that in the target activity, if the target view (shared element) is part of a fragment added on top, this very target view is ready when the transitions framework starts capturing the transition's end values, so that it can be found in the view hierarchy.

FragmentManager's commit() does not perform the transaction and layout changes instantly, but schedules them for a point soon after, which in your case causes the above not to be the case. Postponing the enter transition in the target activity up until the fragment's views are ready (e.g. first call to onPreDraw() of its root layout) should fix that part. That means, you'll have to call postponeEnterTransition() in Activity B, not Fragment B, and startPostponedEnterTransition() (part of the Glide load listener) on the activity reference, not on Fragment B itself.

Additionally, you need to set shared element transitions (enter, as well as return) on Activity B, because it is the component that actually runs them. With these prerequisites set, your desired transition should take effect.

Upvotes: 2

Santanu Sur
Santanu Sur

Reputation: 11477

This is because imageView is in the fragment... its not Activity B's element.. so in

Activity B you can do something like this and test it..

@Override
protected void onCreate(Bundle savedInstanceState) {

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

   FrameLayout frame = (FrameLayout) findViewById(R.id.content);

   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

          frame 
         .setTransitionName(getString(R.string.simple_activity_transition));
        }


        getSupportFragmentManager()
                 .beginTransaction()
                 .addToBackStack(null)
                 .replace(R.id.content, FragmentB.newInstance())
                 .commit();
     }

     @Override
     public void onBackPressed() {
     super.onBackPressed();
     finishAfterTransition();
   }

Now you can test check that even onBackPressed there will be a transition..

Upvotes: 0

Related Questions