title: Android Shared Element Transitions between two ViewPagers
body: <p>I'm trying to achieve the following:</p>
  
  <p>I have an item view that displays a <code>ViewPager</code> with images of that item and some other information. When the user taps one of the <code>ViewPager</code>'s images, I'd like there to be a transition between the image on the first screen and the same image on another <code>ViewPager</code> in a new <code>Activity</code>.</p>
  
  <p>So far, I've managed to get the basic functionality working but there are a couple of key things that do not work as expected:</p>
  
  <ol>
  <li>The transition from <code>ViewPager</code> A to the second <code>Activity</code> with <code>ViewPager</code> B only works when tapping the image at index 0 or 1 in <code>ViewPager</code> A.</li>
  <li>There <em>is</em> a return animation when pressing back from <code>ViewPager</code> B in the new <code>Activity</code> - as long as I do not swipe to another image so the transition is from the full screen mode I'd be displaying in <code>ViewPager</code> B to the same image in <code>ViewPager</code> A. When swiping to another image and pressing back - there's no animation.</li>
  </ol>
  
  <p>Number 1 is happening because the first couple of pages of the <code>ViewPager</code> are instantiated when it's created so the <code>instantiateItem</code> method of the <code>Adapter</code> gets called for each of these and this is where I'm setting the <code>transitionName</code>. 
  This is proven by the fact that calling this on the <code>ViewPager</code> makes that issue go away and the entry animation works on all screens:</p>
  
  <p><code>detailPager.setOffscreenPageLimit(largeNumber);</code></p>
  
  <p>Obviously this is unsustainable, I'd much rather <em>not</em> have such a high off screen limit.</p>
  
  <p>My question is twofold:</p>
  
  <p><strong>How do I achieve the animation for each <code>ViewPager</code> item without keeping all the pages in memory via the above hack?</strong></p>
  
  <p><strong>How can I ensure that a return transition takes place when swiping to another page in <code>ViewPager</code> B?</strong></p>
  
  <p>I've included my code below:</p>
  
  <p><strong>ItemActivity</strong></p>
  
  <pre><code>public class ItemActivity extends AppCompatActivity {
  
  private static final String ITEM_TAG = "item_tag";
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_with_fragment_container);
      ButterKnife.bind(this);
      if (savedInstanceState == null) {
          attachFragment();
      }
  }
  
  private void attachFragment() {
      getSupportFragmentManager().beginTransaction()
              .replace(R.id.fragment_container, ItemFragment.newInstance(), ITEM_TAG)
              .commit();
  }
  
  @Override
  public void onActivityReenter(int resultCode, Intent data) {
      super.onActivityReenter(resultCode, data);
      ItemFragment fragment = (ItemFragment) getSupportFragmentManager().findFragmentByTag(ITEM_TAG);
      if (fragment != null) {
          fragment.onReenter(data);
      }
  }}
  </code></pre>
  
  <p><strong>ItemFragment - This is where the first ViewPager is</strong></p>
  
  <pre><code>public class ItemFragment extends Fragment implements MyAdapter.MyListener {
  
  public static final String EXTRA_STARTING_ALBUM_POSITION = "extra_starting_item_position";
  public static final String EXTRA_CURRENT_ALBUM_POSITION = "extra_current_item_position";
  public static final String[] IMAGE_NAMES = {"One", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"};
  private static final int FULL_SCREEN_CODE = 1234;
  
  private Unbinder unbinder;
  private Bundle tempReenterState;
  private final SharedElementCallback callback = new SharedElementCallback() {
      @Override
      public void onMapSharedElements(List&lt;String&gt; names, Map&lt;String, View&gt; sharedElements) {
          if (tempReenterState != null) {
              int startingPosition = tempReenterState.getInt(EXTRA_STARTING_ALBUM_POSITION);
              int currentPosition = tempReenterState.getInt(EXTRA_CURRENT_ALBUM_POSITION);
              if (startingPosition != currentPosition) {
  
                  String newTransitionName = IMAGE_NAMES[currentPosition];
                  View newSharedElement = detailPager.findViewWithTag(newTransitionName);
                  if (newSharedElement != null) {
                      names.clear();
                      names.add(newTransitionName);
                      sharedElements.clear();
                      sharedElements.put(newTransitionName, newSharedElement);
                  }
              }
  
              tempReenterState = null;
          } else {
              View navigationBar = getActivity().findViewById(android.R.id.navigationBarBackground);
              View statusBar = getActivity().findViewById(android.R.id.statusBarBackground);
              if (navigationBar != null) {
                  names.add(navigationBar.getTransitionName());
                  sharedElements.put(navigationBar.getTransitionName(), navigationBar);
              }
              if (statusBar != null) {
                  names.add(statusBar.getTransitionName());
                  sharedElements.put(statusBar.getTransitionName(), statusBar);
              }
          }
      }
  };
  
  private List&lt;String&gt; images = Arrays.asList("http://wowslider.com/sliders/demo-9/data/images/1293441583_nature_forest_morning_in_the_forest_015232_.jpg",
          "http://wowslider.com/sliders/demo-18/data1/images/hongkong1081704.jpg",
          "http://www.irishtimes.com/polopoly_fs/1.2614603.1461003507!/image/image.jpg_gen/derivatives/box_620_330/image.jpg",
          "http://weknowyourdreams.com/images/sky/sky-05.jpg");
  
  @BindView(R.id.detail_pager)
  ViewPager detailPager;
  
  public static ItemFragment newInstance() {
      return new ItemFragment();
  }
  
  @Nullable
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
      View view = inflater.inflate(R.layout.fragment_image_detail, container, false);
      unbinder = ButterKnife.bind(this, view);
      return view;
  }
  
  @Override
  public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
      super.onViewCreated(view, savedInstanceState);
      ActivityCompat.setExitSharedElementCallback(getActivity(), callback);
      detailPager.setAdapter(new MyAdapter(getActivity(), images, this));
  
  }
  
  @Override
  public void goFullScreen(final int position, View view) {
      Intent intent = FullScreenActivity.newIntent(getActivity(), position, images);
      startActivityForResult(intent, FULL_SCREEN_CODE, ActivityOptions.makeSceneTransitionAnimation(getActivity(), view, view.getTransitionName()).toBundle());
  }
  
  public void onReenter(Intent data) {
      tempReenterState = new Bundle(data.getExtras());
      int startingPosition = tempReenterState.getInt(EXTRA_STARTING_ALBUM_POSITION);
      int currentPosition = tempReenterState.getInt(EXTRA_CURRENT_ALBUM_POSITION);
      if (startingPosition != currentPosition) {
          detailPager.setCurrentItem(currentPosition, false);
      }
      ActivityCompat.postponeEnterTransition(getActivity());
      detailPager.post(new Runnable() {
          @Override
          public void run() {
              ActivityCompat.startPostponedEnterTransition(getActivity());
          }
      });
  }
  
  @Override
  public void onDestroyView() {
      super.onDestroyView();
      if (unbinder != null) {
          unbinder.unbind();
      }
  }
  </code></pre>
  
  <p>}</p>
  
  <p><strong>FullScreenActivity - This is where the second ViewPager is housed</strong></p>
  
  <pre><code>public class FullScreenActivity extends AppCompatActivity {
  
  private final SharedElementCallback callback = new SharedElementCallback() {
      @Override
      public void onMapSharedElements(List&lt;String&gt; names, Map&lt;String, View&gt; sharedElements) {
          if (mIsReturning) {
              if (currentImage == 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 (selectedIndex != 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(currentImage.getTransitionName());
                  sharedElements.clear();
                  sharedElements.put(currentImage.getTransitionName(), currentImage);
              }
          }
      }
  };
  
  private boolean mIsReturning;
  private int mCurrentPosition;
  private int selectedIndex;
  
  
  private static final String ARG_PRESELECTED_INDEX = "arg_preselected_index";
  private static final String ARG_GALLERY_IMAGES = "arg_gallery_images";
  
  public static final String KEY_SELECTED_IMAGE_INDEX = "key_selected_image_index";
  
  public static final String KEY_RETAINED_IMAGES = "key_retained_images";
  private static final int DEFAULT_SELECTED_INDEX = 0;
  
  private List&lt;String&gt; images;
  private ImageAdapter adapter;
  private ImageView currentImage;
  
  @BindView(R.id.full_screen_pager)
  ViewPager viewPager;
  
  public static Intent newIntent(@NonNull final Context context, final int selectedIndex, @NonNull final List&lt;String&gt; images) {
      Intent intent = new Intent(context, FullScreenActivity.class);
      intent.putExtra(ARG_PRESELECTED_INDEX, selectedIndex);
      intent.putStringArrayListExtra(ARG_GALLERY_IMAGES, new ArrayList&lt;&gt;(images));
      return intent;
  }
  
  @CallSuper
  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.layout_full_screen);
      ButterKnife.bind(this);
      ActivityCompat.postponeEnterTransition(this);
      ActivityCompat.setExitSharedElementCallback(this, callback);
  
      if (savedInstanceState == null) {
          selectedIndex = getIntent().getIntExtra(ARG_PRESELECTED_INDEX, 0);
          mCurrentPosition = selectedIndex;
          images = getIntent().getStringArrayListExtra(ARG_GALLERY_IMAGES);
      } else {
          selectedIndex = savedInstanceState.getInt(KEY_SELECTED_IMAGE_INDEX);
          images = savedInstanceState.getStringArrayList(KEY_RETAINED_IMAGES);
      }
      setupViewPager(selectedIndex, images);
  }
  
  private void setupViewPager(final int selectedIndex, List&lt;String&gt; images) {
      adapter = new ImageAdapter(this, images);
      viewPager.post(new Runnable() {
          @Override
          public void run() {
              viewPager.setAdapter(adapter);
              viewPager.setCurrentItem(selectedIndex);
              viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
                  @Override
                  public void onPageSelected(int position) {
                      mCurrentPosition = position;
                  }
              });
              ActivityCompat.startPostponedEnterTransition(FullScreenActivity.this);
          }
      });
  }
  
  @Override
  public void finishAfterTransition() {
      mIsReturning = true;
      Intent data = new Intent();
      data.putExtra(EXTRA_STARTING_ALBUM_POSITION, selectedIndex);
      data.putExtra(EXTRA_CURRENT_ALBUM_POSITION, viewPager.getCurrentItem());
      setResult(RESULT_OK, data);
      super.finishAfterTransition();
  }
  
  private class ImageAdapter extends PagerAdapter {
  
      private final LayoutInflater layoutInflater;
      private final List&lt;String&gt; images;
      private ImageLoader&lt;ImageView&gt; imageLoader;
  
      public ImageAdapter(Context context, List&lt;String&gt; images) {
          this.imageLoader = new PicassoImageLoader(context);
          this.images = images;
          this.layoutInflater = LayoutInflater.from(context);
      }
  
      @Override
      public int getCount() {
          return images.size();
      }
  
      @Override
      public Object instantiateItem(ViewGroup container, int position) {
          final ImageView imageView = (ImageView) layoutInflater.inflate(R.layout.full_image, container, false);
          imageView.setTransitionName(IMAGE_NAMES[position]);
          imageView.setTag(IMAGE_NAMES[position]);
          imageLoader.loadImage(images.get(position), imageView);
          container.addView(imageView);
          return imageView;
      }
  
      @Override
      public void destroyItem(ViewGroup container, int position, Object object) {
          container.removeView((ImageView) object);
      }
  
      @Override
      public void setPrimaryItem(ViewGroup container, int position, Object object) {
          super.setPrimaryItem(container, position, object);
          currentImage = (ImageView) object;
      }
  
      @Override
      public boolean isViewFromObject(View view, Object object) {
          return view == object;
      }
  }
  </code></pre>
  
  <p>}</p>
  
  <p><strong>MyAdapter</strong></p>
  
  <pre><code>public class MyAdapter extends PagerAdapter {
  
  private final LayoutInflater layoutInflater;
  private final List&lt;String&gt; images;
  private final MyListener listener;
  
  private ImageLoader&lt;ImageView&gt; imageLoader;
  
  public interface MyListener {
      void goFullScreen(final int position, View selected);
  }
  
  public MyAdapter(Context context, List&lt;String&gt; images, MyListener listener) {
      this.imageLoader = new PicassoImageLoader(context);
      this.layoutInflater = LayoutInflater.from(context);
      this.images = images;
      this.listener = listener;
  }
  
  @Override
  public int getCount() {
      return images.size();
  }
  
  @Override
  public Object instantiateItem(ViewGroup container, final int position) {
      final ImageView imageView = (ImageView) layoutInflater.inflate(R.layout.pager_item_image_thing, container, false);
      imageView.setTransitionName(IMAGE_NAMES[position]);
      imageView.setTag(IMAGE_NAMES[position]);
      if (listener != null) {
          imageView.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  listener.goFullScreen(position, imageView);
              }
          });
      }
      imageLoader.loadImage(images.get(position), imageView);
      container.addView(imageView);
      return imageView;
  }
  
  @Override
  public void destroyItem(ViewGroup container, int position, Object object) {
      container.removeView((ImageView) object);
  }
  
  @Override
  public boolean isViewFromObject(View view, Object object) {
      return view == object;
  }
  </code></pre>
  
  <p>}</p>
  
  <p><strong>ImageLoader</strong></p>
  
  <pre><code>public interface ImageLoader&lt;T extends ImageView&gt; {
  
  void loadImage(@NonNull final Uri imageSource, @NonNull final T imageView);
  
  void loadImage(@NonNull final String imageSource, @NonNull final T imageView);
  </code></pre>
  
  <p>}</p>
  
  <p><strong>PicassoImageLoader</strong></p>
  
  <pre><code>public class PicassoImageLoader implements ImageLoader {
  
  private final Context context;
  
  public PicassoImageLoader(@NonNull final Context context) {
      this.context = context;
  }
  
  @Override
  public void loadImage(@NonNull Uri imageSource, @NonNull ImageView imageView) {
      Picasso.with(context).load(imageSource).into(imageView);
  }
  
  @Override
  public void loadImage(@NonNull String imageSource, @NonNull ImageView imageView) {
      Picasso.with(context).load(imageSource).into(imageView);
  }
  </code></pre>
  
  <p>}</p>
  
  <p><strong>XML Layouts</strong></p>
  
  <p>fragment_image_detail.xml</p>
  
  <pre><code> &lt;?xml version="1.0" encoding="utf-8"?&gt;
      &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
      android:orientation="vertical"&gt;
  
      &lt;android.support.v4.view.ViewPager
          android:id="@+id/detail_pager"
          android:layout_width="match_parent"
          android:layout_height="390dp" /&gt;
  
      &lt;TextView
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="This is the title"/&gt;
  
      &lt;TextView
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="Some other descriptive text about things"/&gt;
  
  &lt;/LinearLayout&gt;
  </code></pre>
  
  <p>layout_full_screen.xml</p>
  
  <pre><code> &lt;?xml version="1.0" encoding="utf-8"?&gt;
     &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:orientation="vertical"&gt;
  
          &lt;android.support.v4.view.ViewPager
              android:id="@+id/full_screen_pager"
              android:layout_width="match_parent"
              android:layout_height="match_parent" /&gt;
  
      &lt;/LinearLayout&gt;
  </code></pre>
  
  <p>pager_item_thing.xml</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;ImageView xmlns:android="http://schemas.android.com/apk/res/android"
      android:id="@+id/pager_item_image"
      android:layout_width="match_parent"
      android:layout_height="200dp"
      android:foreground="?android:attr/selectableItemBackgroundBorderless"
      android:layout_marginBottom="16dp" /&gt;
  </code></pre>
  
  <p>full_image.xml</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;ImageView xmlns:android="http://schemas.android.com/apk/res/android"
      android:id="@+id/full_image_view"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:orientation="vertical" /&gt;
  </code></pre>