如何使用从 RecyclerView 到 Fragment 的共享转换

How to used a shared transition from RecyclerView to a Fragment

我看到这个问题在这里被问了很多次而没有得到回答。我希望如果我再问一次,也许有人会好心帮助我。

我正在尝试实现从 Recyclerview 中的项目到片段的共享转换。

我目前在适配器的 onBindView 方法中有我的片段事务。

public void onClick(View v) {
    ...
    activity.getSupportFragmentMnager().beginTransaction()
            .addSharedElement(v, "SharedString")
            .replace(R.id.container, fragment2)
            .addToBackStack(null)
            .commit();
}

android docs 中,addSharedElement(view and string) 把我弄糊涂了。我如何给视图一个唯一的 ID,我是否应该在这里使用 v?

字符串可以是我想要的吗?

这是我的实现。每个项目上有 FragmentListRecyclerView 以及图像。并且只有大图有 FragmentDetail。来自 FragmentList 列表项的图像飞到 FragmentDetailFragmentListFragmentDetail 上的动画视图 TransitionName 必须相同。所以我为每个列表项 ImageView:

赋予唯一的过渡名称

imageView.setTransitionName("anyString" + position)

然后我通过 setArguments 将该字符串传递给 FragmentDetail 并将大图像 transitionName 设置为该字符串。我们还应该提供 Transition 来描述视图如何从一个视图层次结构动画到另一个视图层次结构。之后我们应该将 transition 传递给片段。我之前做过 FragmentTransaction:

 detailFragment.setSharedElementEnterTransition(getTransition());
 detailFragment.setSharedElementReturnTransition(getTransition());

或者您可以覆盖 FragmentgetSharedElementEnterTransitiongetSharedElementReturnTransition 并在那里声明 transition。这是完整的代码:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if(savedInstanceState == null) {
            getSupportFragmentManager()
                    .beginTransaction()
                    .replace(R.id.fragment_container, new FragmentList())
                    .commit();
        }
    }

    public void showDetail(View view) {
        String transitionName = view.getTransitionName();

        FragmentDetail fragment = new FragmentDetail();
        fragment.setArguments(FragmentDetail.getBundle(transitionName));
        fragment.setSharedElementEnterTransition(getTransition());
        fragment.setSharedElementReturnTransition(getTransition());

        getSupportFragmentManager()
                .beginTransaction()
                .addSharedElement(view, transitionName)
                .replace(R.id.fragment_container, fragment)
                .addToBackStack(null)
                .commit();
    }

    private Transition getTransition() {
        TransitionSet set = new TransitionSet();
        set.setOrdering(TransitionSet.ORDERING_TOGETHER);
        set.addTransition(new ChangeBounds());
        set.addTransition(new ChangeImageTransform());
        set.addTransition(new ChangeTransform());
        return set;
    }
}

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" />

片段列表:

public class FragmentList extends Fragment {

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = LayoutInflater.from(getContext()).inflate(R.layout.fragment_list, container, false);
        RecyclerView rv = view.findViewById(R.id.recyclerview);
        rv.setAdapter(new ListAdapter());
        return view;
    }

    private class ListAdapter extends RecyclerView.Adapter {
        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
            return new Holder(view);
        }

        @Override
        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
            Holder hold = (Holder) holder;
            hold.itemView.setOnClickListener(v -> {
                ((MainActivity) getActivity()).showDetail(hold.imageView);
            });
            //unique string for each list item
            hold.imageView.setTransitionName("anyString" + position);
        }

        @Override
        public int getItemCount() {
            return 10;
        }

        private class Holder extends ViewHolder {
            ImageView imageView;

            public Holder(View view) {
                super(view);
                imageView = view.findViewById(R.id.image);
            }
        }
    }
}

fragment_list.xml

<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/recyclerview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

片段详细信息:

public class FragmentDetail extends Fragment {
    private static final String NAME_KEY = "key";

    public static Bundle getBundle(String transitionName) {
        Bundle args = new Bundle();
        args.putString(NAME_KEY, transitionName);
        return args;
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = LayoutInflater.from(getContext()).inflate(R.layout.fragment_detail, container, false);
        String transitionName = getArguments().getString(NAME_KEY);
        ImageView imageView = view.findViewById(R.id.image);
        imageView.setTransitionName(transitionName);
        return view;
    }
}

fragment_detail.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center">

    <ImageView
        android:id="@+id/image"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:src="@drawable/cat"/>
</LinearLayout>

item.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:gravity="center">

    <ImageView
        android:id="@+id/image"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@drawable/cat"/>
</LinearLayout>

这是结果:

TransitionName 可以是任何你想要的字符串。 如果您需要支持 Lollipop 之前的设备,有 ViewCompat.setTransitionName

添加到 :

问题

Return Transition was not working!

原因: 在从 Fragment BFragment Apopbackstack 上,调用了 Fragment A's onCreateView,如果您的数据仍在加载,那么 return 转换将不适用于仅 ashakirov 的代码.

解决方案

Postpone the transition in your fragments

fragment A:

中加载开始的地方添加这一行
postponeEnterTransition();

加载数据或设置适配器后,写入:

startPostponedEnterTransition();

这确保您的转换仅在您的数据加载后发生。 另外,请确保即使在 'data failed to load scenarios'.

中也开始转换

如果您想推迟 fragment B 中从 A to B 开始的过渡,请将此添加到您的 fragment B:

@Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        //hold the transition
        postponeEnterTransition();
        //when the views are available then start the transition
        view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                view.getViewTreeObserver().removeOnPreDrawListener(this);
                startPostponedEnterTransition();
                return true;
            }
        });
    }