0%

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

实现朋友圈图片查看的转场动画+滑动返回,RecyclerView to ViewPager。

SharedElementTransitions

RecyclerView to ViewPager 动画实现

下面介绍的便是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

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

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

1
2
3
4
5
<?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 开始显示图片后才启用转场动画。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//简化代码
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 中获取到变动通知。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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<String> names, Map<String, View> 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<String> names, Map<String, View> 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
1
2
3
4
<style name="AppTheme.Transparent" parent="AppTheme">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
</style>
  1. 定义一个 DismissFrameLayout 用来包裹 ImageView, 实现手势检测, 动态更新 ImageView 位置, 动态设置 DestinationActivity.class 背景透明度。

Code tell everything
Demo