片段共享元素转换不适用于 ViewPager

Fragment shared element transitions don't work with ViewPager

我的应用程序包含一个视图,该视图由一个包含少数片段的 ViewPager 组成。当您单击其中一个片段中的某个项目时,预期的行为是共享元素(在本例中为图像)过渡到显示有关所单击内容的更多信息的片段。

这是一个非常简单的视频,它应该是什么样子:

https://dl.dropboxusercontent.com/u/97787025/device-2015-06-03-114842.mp4

这只是使用 Fragment->Fragment 转换。

当您将起始片段放在 ViewPager 中时会出现问题。我怀疑这是因为 ViewPager 使用其父片段的子片段管理器,这与处理片段事务的 activity 的片段管理器不同。这是发生了什么的视频:

https://dl.dropboxusercontent.com/u/97787025/device-2015-06-03-120029.mp4

我很确定这里的问题正如我上面解释的那样是子片段管理器与 activity 的片段管理器。以下是我进行转换的方式:

SimpleFragment fragment = new SimpleFragment();

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.am_list_pane, fragment, fragment.getClass().getSimpleName());

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    TransitionSet enterTransition = new TransitionSet();
    enterTransition.addTransition(new ChangeBounds());
    enterTransition.addTransition(new ChangeClipBounds());
    enterTransition.addTransition(new ChangeImageTransform());
    enterTransition.addTransition(new ChangeTransform());

    TransitionSet returnTransition = new TransitionSet();
    returnTransition.addTransition(new ChangeBounds());
    returnTransition.addTransition(new ChangeClipBounds());
    returnTransition.addTransition(new ChangeImageTransform());
    returnTransition.addTransition(new ChangeTransform());

    fragment.setSharedElementEnterTransition(enterTransition);
    fragment.setSharedElementReturnTransition(returnTransition);

    transaction.addSharedElement(iv, iv.getTransitionName());
}

transaction.addToBackStack(fragment.getClass().getName());

transaction.commit();

当两个片段都由 activity 的片段管理器管理时,这工作正常,但是当我像这样加载 ViewPager 时:

ViewPager pager = (ViewPager) view.findViewById(R.id.pager);
pager.setAdapter(new Adapter(getChildFragmentManager()));

ViewPager 的子项不受 activity 管理,它不再起作用。

这是 Android 团队的疏忽吗?有什么办法可以解决这个问题吗?谢谢

在activity supportPostponeEnterTransition();

并且当您的片段被加载时(尝试同步它们,可能使用 EventBus 或其他)

startPostponedEnterTransition();

参考这个样本

http://www.androiddesignpatterns.com/2015/03/activity-postponed-shared-element-transitions-part3b.html

可能您已经找到了这个问题的答案,但如果您还没有,下面是我经过几个小时的摸索之后修复它的方法。

我认为这个问题是两个因素的结合:

  • ViewPager 中的 Fragments 加载有延迟,这意味着 activity returns 比其包含在其中的片段快很多ViewPager

  • 如果您像我一样,您的 ViewPager 的子片段很可能是同一类型。这意味着,它们都共享相同的过渡名称(如果您已在 xml 布局中设置它们),除非您在代码中设置它们并且仅在可见片段上设置一次.

为了解决这两个问题,我是这样做的:

1.修复延迟加载问题:

在您的 activity(包含 ViewPager 的那个)中,在 super.onCreate() 之后和 setContentView() 之前添加此行:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ActivityCompat.postponeEnterTransition(this); // This is the line you need to add

    setContentView(R.layout.feeds_content_list_activity);
    ...
}

2。解决具有相同过渡名称的多个片段的问题:

现在有很多方法可以做到这一点,但这就是我在 "detail" activity 中最终得到的,即 activity 包含 ViewPager(在 onCreate() 但你真的可以在任何地方做):

_viewPager.setAdapter(_sectionsPagerAdapter);
_viewPager.setCurrentItem(position);
...
...
_pagerAdapter.getItem(position).setTransitionName(getResources().getString(R.string.transition_contenet_topic));

您需要小心,因为 Activity 可能尚未附加到您的 ViewPager 片段,因此只需将过渡名称从 activity 传递到片段会更容易如果您从资源中加载它

实际实现和你想象的一样简单:

public void setTransitionName(String transitionName) {
    _transitionName = transitionName;
}

然后在片段的 onViewCreated() 中,添加此行:

public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    ...
    if (_transitionName != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
        setTransitionNameLollipop();
    }
    ...
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setTransitionNamesLollipop() {
    _imgTopic.setTransitionName(_transitionName);
}

最后一块拼图是找出您的片段何时完全加载然后调用 ActivityCompat.startPostponedEnterTransition(getActivity());

在我的例子中,我的片段直到后来才完全加载,因为我正在从 UI 线程加载大部分内容,这意味着我必须想出一种方法来在加载所有内容时调用它,但是如果那不是你的情况,你可以在调用 setTransitionNameLollipop().

之后立即调用它

这种方法的问题是退出过渡可能无法工作,除非您非常小心并在 exit [=94= 之前重置 "visible" 片段上的过渡名称] 导航回来。这可以像这样轻松完成:

  1. 在您的 ViewPager
  2. 上收听页面更改
  3. 一旦您的片段不在视图中,请删除转换名称
  4. 在可见片段上设置转换名称。
  5. 不调用 finish(),而是调用 ActivityCompat.finishAfterTransition(activity);

如果您向后过渡需要过渡到 RecyclerView,这很快就会变得非常复杂,这就是我的情况。为此,@Alex Lockwood 在这里提供了一个更好的答案:ViewPager Fragments – Shared Element Transitions which has a very well written example code (albeit a lot more complicated than what I just wrote) here: https://github.com/alexjlockwood/activity-transitions/tree/master/app/src/main/java/com/alexjlockwood/activity/transitions

就我而言,我不必执行他的解决方案,我发布的上述解决方案适用于我的案例。

如果您有多个共享元素,我相信您可以想出如何扩展这些方法以满足它们的需求。

最近我一直在用这个把头撞在墙上。我想要的只是 ViewPager 中的一个 Fragment,以启动另一个具有良好的扩展卡类型共享元素转换的 Fragment。 None 上面的建议对我有用,所以我决定尝试启动一个 Activity 风格的对话框:

<style name="AppTheme.CustomDialog" parent="MyTheme">
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowContentOverlay">@null</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:backgroundDimEnabled">true</item>
</style>

然后从片段启动 Activity:

Intent intent = new Intent(getContext(), MyDialogActivity.class);
ActivityOptionsCompat options = ActivityOptionsCompat
                        .makeSceneTransitionAnimation(getActivity(),
                                Pair.create(sharedView, "target_transition"));
ActivityCompat.startActivity(getActivity(), intent, options.toBundle());

在 Fragment 布局中将 android:transition_name 放在 sharedView 上,在 Activity 布局中有 android:transition_name="target_transition"(与 Pair.create() 相同第二个参数)。