Shared Element Transitions with RecyclerView to ViewPager + 滑动返回

1,254 阅读2分钟
原文链接: mingdroid.github.io

下面介绍的便是RecyclerView 到 ViewPager的Shared Element transition动画,如朋友圈那样的。

SourceActivity(RecyclerView) ---> DestinationActivity(ViewPager)

先阅读官方的Shared Element Transitions文档
总共5个步骤:

  1. Enable window content transitions in your theme.
  2. Specify a shared elements transition in your style.
  3. Define your transition as an XML resource.
  4. Assign a common name to the shared elements in both layouts with the android:transitionName attribute.
  5. Use the ActivityOptions.makeSceneTransitionAnimation() method.

在主题中启动 window content transitions动画, 设置Shared Element transition 动画, 路径 res/values-v21/styles.xml


        < item name="android:windowContentTransitions">true
< item name="android:windowSharedElementEnterTransition">@transition/change_image_transform
< item name="android:windowSharedElementExitTransition">@transition/change_image_transform
    

定义 Shared Element Transition 动画, 路径 res/transition/change_image_transform.xml


        < ?xml version="1.0" encoding="utf-8"?>
< transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    < changeBounds/>
    < changeImageTransform/>
< /transitionSet>

RecyclerView, ViewPager都用到Adpter,自然不能在xml布局中设置 transitionName 了。
利用 View.setTransitionName()动态设置,在Adapter中绑定视图时设置transitionName。


做了初步了解后,应该知道Shared Element transition动画是需要一个一对一的关系来确定动画的。
下面的步骤也不可少:

一、 每个View应该有唯一 transitionName,不做详解。

二、 需要等待 ViewPager 开始显示图片后才启用转场动画。

//简化代码
public class DestinationActivity extends AppCompatActivity {
    protected void onCreate(Bundle savedInstanceState) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            //延迟动画
            postponeEnterTransition();
        }
    }  
    public class PhotoAdapter extends PagerAdapter {
        public Object instantiateItem(ViewGroup container, int position) {
            ImageView imageView = new ImageView(container.getContext());
            //....省略
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                String name = container.getContext()
                        .getString(R.string.transition_name, adapterPosition, position);
                imageView.setTransitionName(name);
                if (position == current) {
                    //初始化当前显示的ImageView时,设置一个callback开启转场动画
                    setStartPostTransition(imageView);
                }
            }
        }
    }
    // @TargetApi(21)
    private void setStartPostTransition(final View sharedView) {
        sharedView.getViewTreeObserver().addOnPreDrawListener(
                new ViewTreeObserver.OnPreDrawListener() {
                    // @Override
                    public boolean onPreDraw() {
                        sharedView.getViewTreeObserver().removeOnPreDrawListener(this);
                        startPostponedEnterTransition();
                        return false;
                    }
                });
    }
}

三、 ViewPager 中左右滑动后返回时要通知 SourceActivity.class。

SourceActivity.onActivityReenter();
startActivityForResult();
setResult(RESULT_OK, intent);
这三个函数配合即可在 SourceActivity.class 中获取到变动通知。

// @Override
public void onActivityReenter(int resultCode, Intent data) {
    super.onActivityReenter(resultCode, data);
    if (resultCode == RESULT_OK && data != null) {
        exitPosition = data.getIntExtra("exit_position", enterPosition);
    }
}

四、 既然有所变动,那么原先一对一的关系有所变动了,下面两个函数可处理这种情况:

public class DestinationActivity extends AppCompatActivity {
     //只有在ViewPager左右滑动后才需要在关闭时设置callback
     //view 为转场对象 ImageView
    //  @TargetApi(21)
     private void setSharedElementCallback(final View view) {
         setEnterSharedElementCallback(new SharedElementCallback() {
            //  @Override
             public void onMapSharedElements(List names, Map sharedElements) {
                 names.clear();
                 sharedElements.clear();
                 names.add(view.getTransitionName());
                 sharedElements.put(view.getTransitionName(), view);
             }
         });
     }   
}
//exitPosition 是在 SourceActivity.onActivityReenter() 取得具体值的。
public class SourceActivity extends AppCompatActivity {
      // @TargetApi(21)
      private void setCallback(final int enterPosition) {
          this.enterPosition = enterPosition;
          setExitSharedElementCallback(new SharedElementCallback() {
              // @Override
              public void onMapSharedElements(List names, Map sharedElements) {
                  if (exitPosition != enterPosition && names.size() > 0 && exitPosition < sharedViews.length) {
                      names.clear();
                      sharedElements.clear();
                      View view = sharedViews[exitPosition];
                      names.add(view.getTransitionName());
                      sharedElements.put(view.getTransitionName(), view);
                  }
                  setExitSharedElementCallback((SharedElementCallback) null);
                  sharedViews = null;
              }
          });
      }
}

滑动返回实现

  1. 为 DestinationActivity.class 设置透明背景主题, 路径 /res/values/styles.xml


< style name="AppTheme.Transparent" parent="AppTheme">
    < item name="android:windowBackground">@android:color/transparent
    < item name="android:windowIsTranslucent">true
< /style>

  1. 定义一个 DismissFrameLayout 用来包裹 ImageView, 实现手势检测, 动态更新 ImageView 位置, 动态设置 DestinationActivity.class 背景透明度。