androidanimationandroid-activitytransitionshared-element-transition

Android clears Activity to Activity shared element transition exit animation after pressing power button


I'm working on implementing a basic Activity transition animation with a shared element from a RecyclerView with a GridLayoutManager to a full screen details Activity screen. The animation works well under regular circumstances. So when clicking on an image in the grid it scales to the full screen image and on exiting the reverse happens. But if you press the power button and return to the app while the details screen is visible, Android seems to clear all registered shared element/transitions so the full screen image instead of scaling back into the grid it just fades out. I tried registering SharedElementCallbacks in both Activities which are called properly without the power button press but neither gets called after pressing the power button. I'd appreciate any suggestions to help solve this problem.

These are the places that I've added code to support the shared element transition:

public class MyViewHolder extends RecyclerView.ViewHolder {

    @BindView(R.id.imageview) ImageView imageView;

    private Item item;

    public MyViewHolder(@NonNull View itemView) {
        super(itemView);
        ButterKnife.bind(this, itemView);
        itemView.setTag(this);
        itemView.setOnClickListener(onItemClickListener);
    }

    @Override
    public void onBind(int position) {
        super.onBind(position);
        this.item = list.get(position);

        imageView.setTransitionName(item.getId());
        Glide.with(imageView.getContext().getApplicationContext())
                .load(item.getUrl())
                .centerCrop()
                .apply(RequestOptions.placeholderOf(new ColorDrawable(Color.BLACK)))
                .transition(DrawableTransitionOptions.withCrossFade())
                .into(imageView);
    }

    public Item getItem() {
        return item;
    }
}

public class MyActivity extends AppCompatActivity {

    ...

    public void setUp() {
        ...

        adapter.setOnItemClickListener(view -> {
            MyViewHolder viewHolder = (MyViewHolder)view.getTag();
            View view = viewHolder.imageView;

            Intent intent = new Intent(this, DetailsActivity.class);
            intent.putExtra(Item.TAG, viewHolder.getItem());

            ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
                    this,
                    view,
                    view.getTransitionName());

            startActivity(intent, options.toBundle());

        });

        ...
    }
}


public class DetailsActivity extends AppCompatActivity {

    @BindView(R.id.imageview) ImageView imageView;

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

        supportPostponeEnterTransition();

        Bundle bundle = getIntent().getExtras();
        Item item = (Item) bundle.getSerializable(Item.TAG);
        imageView.setTransitionName(item.getId());

        final RequestListener<Drawable> requestListener = new RequestListener<Drawable>() {
            @Override
            public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                supportStartPostponedEnterTransition();
                return false;
            }

            @Override
            public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                supportStartPostponedEnterTransition();
                return false;
            }
        };

        Glide.with(getApplicationContext())
                .load(item.getUrl())
                .centerCrop()
                .addListener(requestListener)
                .into(imageView);

    }
}


Solution

  • Well, I believe this is a bug starting from API 29 in

    1. Activity-2-Activity
    2. Activity-Fragment
    3. Fragment-Activity

    shared element transitions. It works fine in API < 29 and Fragment-Fragment transitions.

    While transitioning from ActivityA to ActivityB and returning back from ActivityB to ActivityA everything works normally unless you are on ActivityB and send your app on background or lock device screen, then you come to the app where ActivityB is opened, now pressing back button or returning to ActivityA loses all shared element reverse transitions even if you have override the sharedElementReturnTransition

    I found a simple hack to fix this issue which is making our transitions to not work normally -

    While sending the ActivityB to background it calls the onPause() > onStop() lifecycle methods of the activity and now inside your onStop() you can check for the following conditions, if the activity is not finished and your API > 29, then pass the current Bundle in callActivityOnSaveInstanceState(),

    Kotlin Code // override this method in your ActivityB

    override fun onStop() {
            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q && !isFinishing) {
                Instrumentation().callActivityOnSaveInstanceState(this, Bundle())
            }
            super.onStop()
        }
    

    Java Code // override this lifecycle method in your ActivityB

     @Override
        protected void onStop() {
            if(Build.VERSION.SDK_INT == Build.VERSION_CODES.Q && !isFinishing()){
                new Instrumentation().callActivityOnSaveInstanceState(this, new Bundle());
            }
            super.onStop();
        }
    

    I hope it helps!