共享元素转换在父片段和子片段之间不起作用(嵌套片段)

Posted

技术标签:

【中文标题】共享元素转换在父片段和子片段之间不起作用(嵌套片段)【英文标题】:Shared Element Transition Not Working Between Parent and Child Fragments (Nested Fragments) 【发布时间】:2020-04-13 08:01:19 【问题描述】:

在主活动中,我有BottomNavigationView,其中有 3 个不同的父片段。父片段有recyclerview 并且在recyclerview 的项目单击上,我正在启动子片段以获取有关该项目的更多详细信息。我正在尝试在我的两个片段(父母和孩子)之间实现Shared Element Transition,但它没有发生。

启动子片段没有问题,我也检查了转换名称,它在我分配给适配器中的项目的子片段中是相同的。我正在使用Random 类将转换名称分配给项目,因为在单个父片段中我有很多回收站视图。这是我的代码:

适配器

    final String transition;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) 
        transition = "transition" + new Random().nextInt(9999999);
        viewHolder.image.setTransitionName(transition);
    
    viewHolder.container.setOnClickListener(new View.OnClickListener() 
        @Override
        public void onClick(View view) 
            mCallback.itemClicked(i, book, viewHolder.image, transition);
        
    );

父片段

@Override
public void itemClicked(int pos, Book book, View view, String transition) 
    MainActivity activity = (MainActivity) getActivity();
    ChildFragment myFragment = new ChildFragment();
    Bundle bundle = new Bundle();
    bundle.putString(IntentExtraKeys.TRANSITION_NAME, transition);
    myFragment.setArguments(bundle);
    activity.showFragmentWithTransition(this, myFragment, ChildFragment.class.getName(), view, transition);

活动

public void showFragmentWithTransition(Fragment current, Fragment newFragment, String tag, View sharedView, String sharedElementName) 

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) 
        current.setSharedElementReturnTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
        current.setExitTransition(TransitionInflater.from(this).inflateTransition(android.R.transition.no_transition));
        newFragment.setSharedElementEnterTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
        newFragment.setEnterTransition(TransitionInflater.from(this).inflateTransition(android.R.transition.no_transition));
    

    FragmentManager manager = current.getChildFragmentManager();
    FragmentTransaction transaction = manager.beginTransaction();
    transaction.replace(R.id.child_fragment, newFragment, tag);
    transaction.addToBackStack(tag);
    transaction.addSharedElement(sharedView, sharedElementName);
    transaction.commit();

default_transition

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

子片段

    Bundle b = getArguments();
    if (b != null) 
        String transitionName = b.getString(IntentExtraKeys.TRANSITION_NAME);
        Logger.info("opening bundle book fragment:" + transitionName);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) 
            image.setTransitionName(transitionName);
        
    

这是问题的示例项目: https://gitlab.com/iskfaisal/transition-issue

【问题讨论】:

为什么要使用子片段来获取详细信息? @toffor 因为我不想隐藏底部导航视图。我其实没看懂你的评论。 你有 3 个底部选项卡并且有片段为什么不使用活动片段管理器来显示详细信息为什么需要子片段? © 将为您提供一个示例。 实际上,当我使用活动片段管理器时,只要应用程序进入后台,子片段就会自动销毁。这里有更多细节***.com/q/59374926/4291272。但是,这个问题与子片段无关,因为我在使用活动片段管理器时遇到了同样的问题。 【参考方案1】:

我正在尝试在我的两个片段(父和子)之间实现共享元素转换,但它没有发生。

什么都没有发生,或者至少看起来什么都没有发生,因为您在 default_transition.xml 中只使用了&lt;changeTransform/&gt;&lt;changeBounds /&gt;。如果Fragments 的边界重合,则没有什么可以“过境”。

但是,如果您向文件中添加额外的动画元素,则过渡实际上是可见的(即使边界重合)。

简介

我创建了一个与您类似的示例项目 - BottomNavigationView,其中 NavHostFragment 包含 2 父级 Fragments - DashboardFragmentHomeFragment

最初,DashboardFragment 加载由一个简单的RecyclerView 组成的DashboardListFragment。如果单击任何RecyclerView 项目,则DashboardFragment 加载DashboardDetailFragmentDashboardDetailFragmentDashboardListFragment 的边界重合)。

过渡行为

然后,我几乎重复使用了您的 showFragmentWithTransition(...) 并尝试单击列表中的每个元素以检查是否有任何过渡可见 - 它不是。

因此,我通过添加一个简单的&lt;slide/&gt; 元素来修改文件,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<transitionSet>
    <slide/>
    <changeTransform />
    <changeBounds />
</transitionSet>

滑动过渡就在那里。

我还尝试了其他元素,例如 &lt;fade/&gt;&lt;explode/&gt; 或其他元素 - 它们都工作得很好。

结论

即使过渡不可见,也不意味着它没有发生。您应该尝试其他动画元素才能看到它的效果。

更新

由于您提供了指向您的 github 代码的链接,因此我偷看了它并把它带到了工作状态。我会留给你进一步改进它。

所以,基本上,您的HomeFragment 布局文件同时包含RecyclerView 和您孩子的占位符Fragment。相反,我将您的 HomeFragment 拆分为 2 实体 - 一个用于您的父 Fragment HomeFragment,其中仅包含任何子 FragmentHomeFragmentList 的占位符,其中包含您以前的父逻辑。

Then, when a picture is selected, HomeFragment replaces your HomeFragmentList with a ChildFragment.而且...有效

这样,您无需使用您的Activity 来创建子Fragment,而且更加独立。

下面,我提供了需要修改的相关代码。

代码

HomeFragment(新父级)

public class HomeFragment extends Fragment 

    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
        return inflater.inflate(R.layout.fragment_home, container, false);
    

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) 
        super.onViewCreated(view, savedInstanceState);

        HomeFragmentList homeFragmentList = new HomeFragmentList();
        FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.child_fragment, homeFragmentList, homeFragmentList.getClass().getName());
        fragmentTransaction.commit();
    

HomeFragmentList(旧父)

public class HomeFragmentList extends Fragment implements AdapterListener 
    RecyclerView recyclerView;
    private ArrayList<String> images = new ArrayList<>();

    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 

        View root = inflater.inflate(R.layout.fragment_home_list, container, false);

        recyclerView = root.findViewById(R.id.recycler_view);
        getImages();
        LinearLayoutManager manager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
        recyclerView = root.findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(manager);
        AdapterClass adapter = new AdapterClass(this, images);
        recyclerView.setAdapter(adapter);

        return root;
    

    void getImages() 
        images.add("https://rukminim1.flixcart.com/image/832/832/book/0/1/9/rich-dad-poor-dad-original-imadat2a4f5vwgzn.jpeg?q=70");
        images.add("https://www.seeken.in/wp-content/uploads/2017/06/The-4-Hour-Work-Week.jpeg");
        images.add("https://www.seeken.in/wp-content/uploads/2017/06/Managing-Oneself.jpeg");
        images.add("https://www.seeken.in/wp-content/uploads/2017/07/How-to-Win-Friends-and-Influence-People.jpeg");
        images.add("https://www.seeken.in/wp-content/uploads/2017/07/THINK-LIKE-DA-VINCI-7-Easy-Steps-to-Boosting-your-Everyday-Genius.jpeg");
        images.add("https://www.seeken.in/wp-content/uploads/2017/07/How-To-Stop-Worrying-And-Start-Living.jpg");
        images.add("https://www.seeken.in/wp-content/uploads/2017/08/THE-INTELLIGENT-INVESTOR.jpeg");
        images.add("https://www.seeken.in/wp-content/uploads/2017/08/Awaken-the-Giant-within-How-to-Take-Immediate-Control-of-Your-Mental-Emotional-Physical-and-Financial-Life.jpg");
        images.add("https://www.seeken.in/wp-content/uploads/2017/08/E-MYTH-REVISITED.jpeg");
        images.add("https://images-na.ssl-images-amazon.com/images/I/41axGE4CehL._SX353_BO1,204,203,200_.jpg");
        images.add("https://rukminim1.flixcart.com/image/832/832/book/0/1/9/rich-dad-poor-dad-original-imadat2a4f5vwgzn.jpeg?q=70");

    

    @Override
    public void itemClicked(int pos, ModelClass object, View view, String transition) 
        MainActivity activity = (MainActivity) getActivity();
        ChildFragment myFragment = new ChildFragment();
        Bundle bundle = new Bundle();
        bundle.putString("IMAGE_URL", object.getItem(pos));
        bundle.putInt("POSITION", pos);
        bundle.putString("TRANSITION_NAME", transition);
        myFragment.setArguments(bundle);
        Log.i("HOME FRAGMENT-DEBUG", transition + "/" + view.getTransitionName());
        showFragmentWithTransition(getParentFragment(), myFragment, ChildFragment.class.getName(), view, transition);
    

    public void showFragmentWithTransition(Fragment current, Fragment _new, String tag, View sharedView, String sharedElementName) 

        FragmentManager manager = current.getChildFragmentManager();
        FragmentTransaction transaction = manager.beginTransaction();
        if (sharedView != null && sharedElementName != null) 
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) 
                current.setSharedElementReturnTransition(TransitionInflater.from(getContext()).inflateTransition(R.transition.default_transition));
                current.setExitTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.no_transition));
                _new.setSharedElementEnterTransition(TransitionInflater.from(getContext()).inflateTransition(R.transition.default_transition));
                _new.setEnterTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.no_transition));
                transaction.addSharedElement(sharedView, sharedElementName);
                Log.i("ACTIVITY-DEBUG", sharedElementName + "/" + sharedView.getTransitionName());
            
        
        transaction.replace(R.id.child_fragment, _new, tag);
        transaction.addToBackStack(tag);
        transaction.commit();
    

fragment_home.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_
    android:layout_>

    <FrameLayout
        android:id="@+id/child_fragment"
        android:layout_
        android:layout_/>

</LinearLayout>

fragment_home_list.xml

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_
    android:layout_>

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_
        android:layout_
        android:background="#FFF">

        <androidx.core.widget.NestedScrollView
            android:id="@+id/home_container"
            android:layout_
            android:layout_
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <RelativeLayout
                android:layout_
                android:layout_
                android:layout_marginTop="15dp"
                android:layout_marginLeft="9dp"
                android:layout_marginRight="9dp">


                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/recycler_view"
                    android:layout_
                    android:layout_
                    android:layout_marginTop="5dp"
                    android:orientation="vertical">

                </androidx.recyclerview.widget.RecyclerView>

            </RelativeLayout>

        </androidx.core.widget.NestedScrollView>

        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/appbar"
            android:layout_
            android:layout_
            android:background="#FFF">

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/z_toolbar"
                android:layout_
                android:layout_
                android:elevation="4dp">

                <RelativeLayout
                    android:id="@+id/header_container"
                    android:layout_
                    android:layout_>

                    <ImageView
                        android:id="@+id/logo"
                        android:layout_
                        android:layout_
                        android:layout_marginRight="10dp"
                        android:layout_centerVertical="true"
                        android:layout_alignParentLeft="true"
                        android:src="@mipmap/ic_launcher_round"
                        android:contentDescription="@string/app_name" />

                    <TextView
                        android:layout_
                        android:layout_
                        android:layout_centerVertical="true"
                        android:layout_toRightOf="@+id/logo"
                        android:textColor="#000"
                        android:textSize="16sp"
                        android:text="Home" />

                </RelativeLayout>

            </androidx.appcompat.widget.Toolbar>

        </com.google.android.material.appbar.AppBarLayout>

    </androidx.coordinatorlayout.widget.CoordinatorLayout>
</RelativeLayout>

备注

请注意&lt;changeTransform/&gt;&lt;changeBounds /&gt; 的转换是可见的,因为HomeFragmentList 中选定View 的边界与子Fragment 中的边界不同。

【讨论】:

非常感谢您付出了这么多努力。但是,它仍然无法正常工作。我在 git 上做了一个 issue 的样例项目,希望你能对项目做些小改动,问题就会得到解决。链接:gitlab.com/iskfaisal/transition-issue @FaisalShaikh 不客气。我把你的代码带到了工作状态,并用相关的类更新了我的答案。 我遇到了一个真正的问题。谢谢你帮助我。我真的很感激。作为记录,我已经删除了&lt;slide/&gt;,它仍在工作。无论如何,非常感谢。【参考方案2】:

共享元素转换不适用于FragmentTransaction.add 方法,因为父片段仍处于活动状态并已恢复。试试

transaction.replace(R.id.child_fragment, newFragment, tag);

查看this 讨论主题以获取更多信息。

【讨论】:

【参考方案3】:

我不确定您的其余设置,但要使共享元素转换正常工作,您需要将 setReorderingAllowed(true) 添加到您的片段事务中,如下所示:

FragmentTransaction transaction = manager.beginTransaction();
transaction.add(R.id.child_fragment, newFragment, tag);
transaction.addToBackStack(tag);
transaction.addSharedElement(sharedView, sharedElementName);
transaction.setReorderingAllowed(true); // Add this
transaction.commit();

【讨论】:

以上是关于共享元素转换在父片段和子片段之间不起作用(嵌套片段)的主要内容,如果未能解决你的问题,请参考以下文章

共享元素返回过渡不适用于片段中的 recyclerview 和 cardview

共享转换片段到片段不起作用

共享元素转换:活动到嵌套在另一个活动中的片段

共享项目转换在背面的片段中不起作用

不同活动的片段之间的共享元素转换

跨活动的片段之间的共享元素转换不一致