Activity (RecyclerView) to Activity(FragmentViewPager) Shared Element Transition.
class FullScreenPagerAdapter(val paths: ArrayList<String>,
val activity: Activity,
val callback: (Any?) -> Unit,
val onClick: () -> Unit) : PagerAdapter() {
private var layoutInflater: LayoutInflater? = null
private var imageViewPreview: ImageView? = null
// Called when picture is ready to be displayed
val mPicassoCallback = object: Callback {
override fun onSuccess() {
mStartPostponedEnterTransition(imageViewPreview)
}
override fun onError() {
mStartPostponedEnterTransition(imageViewPreview)
}
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
layoutInflater = container.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view = layoutInflater!!.inflate(R.layout.picture_fullscreen_preview, container, false)
imageViewPreview = view.findViewById(R.id.imagePreview) as ImageView
val path = paths[position]
Picasso.with(view.context)
.load(File(path))
.into(imageViewPreview!!, mPicassoCallback)
container.addView(view)
imageViewPreview?.setOnClickListener { onClick() }
ViewCompat.setTransitionName(imageViewPreview, position.toString() + "_image")
return view
}
override fun getCount(): Int {
return paths.size
}
override fun isViewFromObject(view: View, obj: Any): Boolean {
return view === obj as View
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
container.removeView(`object` as View)
}
// The correct way of retrieving currently displayed fragment of ViewPager
override fun setPrimaryItem(container: ViewGroup?, position: Int, `object`: Any?) {
super.setPrimaryItem(container, position, `object`)
callback(`object`)
}
private fun mStartPostponedEnterTransition(view: View?) {
view?.viewTreeObserver?.addOnPreDrawListener(object: ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
view.viewTreeObserver.removeOnPreDrawListener(this)
ActivityCompat.startPostponedEnterTransition(activity)
return true
}
})
}
}
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.SharedElementCallback;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import com.squareup.picasso.Picasso;
import java.util.List;
import java.util.Map;
import static com.alexjlockwood.activity.transitions.Constants.ALBUM_IMAGE_URLS;
import static com.alexjlockwood.activity.transitions.Constants.ALBUM_NAMES;
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
private static final boolean DEBUG = false;
static final String EXTRA_STARTING_ALBUM_POSITION = "extra_starting_item_position";
static final String EXTRA_CURRENT_ALBUM_POSITION = "extra_current_item_position";
private RecyclerView mRecyclerView;
private Bundle mTmpReenterState;
private boolean mIsDetailsActivityStarted;
private final SharedElementCallback mCallback = new SharedElementCallback() {
@Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
if (mTmpReenterState != null) {
int startingPosition = mTmpReenterState.getInt(EXTRA_STARTING_ALBUM_POSITION);
int currentPosition = mTmpReenterState.getInt(EXTRA_CURRENT_ALBUM_POSITION);
if (startingPosition != currentPosition) {
// If startingPosition != currentPosition the user must have swiped to a
// different page in the DetailsActivity. We must update the shared element
// so that the correct one falls into place.
String newTransitionName = ALBUM_NAMES[currentPosition];
View newSharedElement = mRecyclerView.findViewWithTag(newTransitionName);
if (newSharedElement != null) {
names.clear();
names.add(newTransitionName);
sharedElements.clear();
sharedElements.put(newTransitionName, newSharedElement);
}
}
mTmpReenterState = null;
} else {
// If mTmpReenterState is null, then the activity is exiting.
View navigationBar = findViewById(android.R.id.navigationBarBackground);
View statusBar = findViewById(android.R.id.statusBarBackground);
if (navigationBar != null) {
names.add(ViewCompat.getTransitionName(navigationBar));
sharedElements.put(navigationBar.getTransitionName(), navigationBar);
}
if (statusBar != null) {
names.add(ViewCompat.getTransitionName(statusBar));
sharedElements.put(ViewCompat.getTransitionName(statusBar), statusBar);
}
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActivityCompat.setExitSharedElementCallback(this, mCallback);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(new GridLayoutManager(this,
getResources().getInteger(R.integer.activity_main_num_grid_columns)));
mRecyclerView.setAdapter(new CardAdapter());
}
@Override
protected void onResume() {
super.onResume();
mIsDetailsActivityStarted = false;
}
@Override
public void onActivityReenter(int requestCode, Intent data) {
super.onActivityReenter(requestCode, data);
mTmpReenterState = new Bundle(data.getExtras());
int startingPosition = mTmpReenterState.getInt(EXTRA_STARTING_ALBUM_POSITION);
int currentPosition = mTmpReenterState.getInt(EXTRA_CURRENT_ALBUM_POSITION);
if (startingPosition != currentPosition) {
mRecyclerView.scrollToPosition(currentPosition);
}
ActivityCompat.postponeEnterTransition(this);
mRecyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mRecyclerView.getViewTreeObserver().removeOnPreDrawListener(this);
// TODO: figure out why it is necessary to request layout here in order to get a smooth transition.
mRecyclerView.requestLayout();
ActivityCompat.startPostponedEnterTransition(MainActivity.this);
return true;
}
});
}
private class CardAdapter extends RecyclerView.Adapter<CardHolder> {
private final LayoutInflater mInflater;
public CardAdapter() {
mInflater = LayoutInflater.from(MainActivity.this);
}
@Override
public CardHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
return new CardHolder(mInflater.inflate(R.layout.album_image_card, viewGroup, false));
}
@Override
public void onBindViewHolder(CardHolder holder, int position) {
holder.bind(position);
}
@Override
public int getItemCount() {
return ALBUM_IMAGE_URLS.length;
}
}
private class CardHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private final ImageView mAlbumImage;
private int mAlbumPosition;
public CardHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
mAlbumImage = (ImageView) itemView.findViewById(R.id.main_card_album_image);
}
public void bind(int position) {
Picasso.with(MainActivity.this).load(ALBUM_IMAGE_URLS[position]).into(mAlbumImage);
ViewCompat.setTransitionName(mAlbumImage, Constants.ALBUM_NAMES[position])
mAlbumImage.setTag(ALBUM_NAMES[position]);
mAlbumPosition = position;
}
@Override
public void onClick(View v) {
// TODO: is there a way to prevent user from double clicking and starting activity twice?
Intent intent = new Intent(MainActivity.this, DetailsActivity.class);
intent.putExtra(EXTRA_STARTING_ALBUM_POSITION, mAlbumPosition);
if (!mIsDetailsActivityStarted) {
mIsDetailsActivityStarted = true;
startActivity(intent, ActivityOptionsCompat.makeSceneTransitionAnimation(MainActivity.this,
mAlbumImage, ViewCompat.getTransitionName(mAlbumImage)).toBundle());
}
}
}
}
import android.app.Fragment;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.transition.Transition;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.RequestCreator;
import static com.alexjlockwood.activity.transitions.Constants.ALBUM_IMAGE_URLS;
import static com.alexjlockwood.activity.transitions.Constants.ALBUM_NAMES;
import static com.alexjlockwood.activity.transitions.Constants.BACKGROUND_IMAGE_URLS;
public class DetailsFragment extends Fragment {
private static final String TAG = DetailsFragment.class.getSimpleName();
private static final boolean DEBUG = false;
private static final String ARG_ALBUM_IMAGE_POSITION = "arg_album_image_position";
private static final String ARG_STARTING_ALBUM_IMAGE_POSITION = "arg_starting_album_image_position";
private final Callback mImageCallback = new Callback() {
@Override
public void onSuccess() {
startPostponedEnterTransition();
}
@Override
public void onError() {
startPostponedEnterTransition();
}
};
private ImageView mAlbumImage;
private int mStartingPosition;
private int mAlbumPosition;
private boolean mIsTransitioning;
private long mBackgroundImageFadeMillis;
public static DetailsFragment newInstance(int position, int startingPosition) {
Bundle args = new Bundle();
args.putInt(ARG_ALBUM_IMAGE_POSITION, position);
args.putInt(ARG_STARTING_ALBUM_IMAGE_POSITION, startingPosition);
DetailsFragment fragment = new DetailsFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mStartingPosition = getArguments().getInt(ARG_STARTING_ALBUM_IMAGE_POSITION);
mAlbumPosition = getArguments().getInt(ARG_ALBUM_IMAGE_POSITION);
mIsTransitioning = savedInstanceState == null && mStartingPosition == mAlbumPosition;
mBackgroundImageFadeMillis = getResources().getInteger(
R.integer.fragment_details_background_image_fade_millis);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, final Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_details, container, false);
mAlbumImage = (ImageView) rootView.findViewById(R.id.details_album_image);
final ImageView backgroundImage = (ImageView) rootView.findViewById(R.id.details_background_image);
View textContainer = rootView.findViewById(R.id.details_text_container);
TextView albumTitleText = (TextView) textContainer.findViewById(R.id.details_album_title);
String albumImageUrl = ALBUM_IMAGE_URLS[mAlbumPosition];
String backgroundImageUrl = BACKGROUND_IMAGE_URLS[mAlbumPosition];
String albumName = ALBUM_NAMES[mAlbumPosition];
albumTitleText.setText(albumName);
ViewCompat.setTransitionName(mAlbumImage, albumName)
RequestCreator albumImageRequest = Picasso.with(getActivity()).load(albumImageUrl);
RequestCreator backgroundImageRequest = Picasso.with(getActivity()).load(backgroundImageUrl).fit().centerCrop();
if (mIsTransitioning) {
albumImageRequest.noFade();
backgroundImageRequest.noFade();
backgroundImage.setAlpha(0f);
getActivity().getWindow().getSharedElementEnterTransition().addListener(new TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
backgroundImage.animate().setDuration(mBackgroundImageFadeMillis).alpha(1f);
}
});
}
albumImageRequest.into(mAlbumImage, mImageCallback);
backgroundImageRequest.into(backgroundImage);
return rootView;
}
private void startPostponedEnterTransition() {
if (mAlbumPosition == mStartingPosition) {
mAlbumImage.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mAlbumImage.getViewTreeObserver().removeOnPreDrawListener(this);
ActivityCompat.startPostponedEnterTransition(getActivity())
return true;
}
});
}
}
/**
* Returns the shared element that should be transitioned back to the previous Activity,
* or null if the view is not visible on the screen.
*/
@Nullable
ImageView getAlbumImage() {
if (isViewInBounds(getActivity().getWindow().getDecorView(), mAlbumImage)) {
return mAlbumImage;
}
return null;
}
/**
* Returns true if {@param view} is contained within {@param container}'s bounds.
*/
private static boolean isViewInBounds(@NonNull View container, @NonNull View view) {
Rect containerBounds = new Rect();
container.getHitRect(containerBounds);
return view.getLocalVisibleRect(containerBounds);
}
}
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.SharedElementCallback;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v13.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import java.util.List;
import java.util.Map;
import static com.alexjlockwood.activity.transitions.Constants.ALBUM_IMAGE_URLS;
import static com.alexjlockwood.activity.transitions.MainActivity.EXTRA_CURRENT_ALBUM_POSITION;
import static com.alexjlockwood.activity.transitions.MainActivity.EXTRA_STARTING_ALBUM_POSITION;
public class DetailsActivity extends Activity {
private static final String TAG = DetailsActivity.class.getSimpleName();
private static final boolean DEBUG = false;
private static final String STATE_CURRENT_PAGE_POSITION = "state_current_page_position";
private final SharedElementCallback mCallback = new SharedElementCallback() {
@Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
if (mIsReturning) {
ImageView sharedElement = mCurrentDetailsFragment.getAlbumImage();
if (sharedElement == null) {
// If shared element is null, then it has been scrolled off screen and
// no longer visible. In this case we cancel the shared element transition by
// removing the shared element from the shared elements map.
names.clear();
sharedElements.clear();
} else if (mStartingPosition != mCurrentPosition) {
// If the user has swiped to a different ViewPager page, then we need to
// remove the old shared element and replace it with the new shared element
// that should be transitioned instead.
names.clear();
names.add(ViewCompat.getTransitionName(sharedElement));
sharedElements.clear();
sharedElements.put(ViewCompat.getTransitionName(sharedElement), sharedElement);
}
}
}
};
private DetailsFragment mCurrentDetailsFragment;
private int mCurrentPosition;
private int mStartingPosition;
private boolean mIsReturning;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_details);
ActivityCompat.postponeEnterTransition(this)
ActivityCompat.setEnterSharedElementCallback(this, mCallback)
mStartingPosition = getIntent().getIntExtra(EXTRA_STARTING_ALBUM_POSITION, 0);
if (savedInstanceState == null) {
mCurrentPosition = mStartingPosition;
} else {
mCurrentPosition = savedInstanceState.getInt(STATE_CURRENT_PAGE_POSITION);
}
ViewPager pager = (ViewPager) findViewById(R.id.pager);
pager.setAdapter(new DetailsFragmentPagerAdapter(getFragmentManager()));
pager.setCurrentItem(mCurrentPosition);
pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
mCurrentPosition = position;
}
});
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(STATE_CURRENT_PAGE_POSITION, mCurrentPosition);
}
@Override
public void finishAfterTransition() {
mIsReturning = true;
Intent data = new Intent();
data.putExtra(EXTRA_STARTING_ALBUM_POSITION, mStartingPosition);
data.putExtra(EXTRA_CURRENT_ALBUM_POSITION, mCurrentPosition);
setResult(RESULT_OK, data);
super.finishAfterTransition();
}
private class DetailsFragmentPagerAdapter extends FragmentStatePagerAdapter {
public DetailsFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
return DetailsFragment.newInstance(position, mStartingPosition);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
mCurrentDetailsFragment = (DetailsFragment) object;
}
@Override
public int getCount() {
return ALBUM_IMAGE_URLS.length;
}
}
}