如何使用从 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?
字符串可以是我想要的吗?
这是我的实现。每个项目上有 FragmentList
和 RecyclerView
以及图像。并且只有大图有 FragmentDetail
。来自 FragmentList
列表项的图像飞到 FragmentDetail
。 FragmentList
和 FragmentDetail
上的动画视图 TransitionName
必须相同。所以我为每个列表项 ImageView:
赋予唯一的过渡名称
imageView.setTransitionName("anyString" + position)
然后我通过 setArguments
将该字符串传递给 FragmentDetail
并将大图像 transitionName
设置为该字符串。我们还应该提供 Transition
来描述视图如何从一个视图层次结构动画到另一个视图层次结构。之后我们应该将 transition
传递给片段。我之前做过 FragmentTransaction
:
detailFragment.setSharedElementEnterTransition(getTransition());
detailFragment.setSharedElementReturnTransition(getTransition());
或者您可以覆盖 Fragment
的 getSharedElementEnterTransition
和 getSharedElementReturnTransition
并在那里声明 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 B
到 Fragment A
的 popbackstack
上,调用了 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;
}
});
}
我看到这个问题在这里被问了很多次而没有得到回答。我希望如果我再问一次,也许有人会好心帮助我。
我正在尝试实现从 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?
字符串可以是我想要的吗?
这是我的实现。每个项目上有 FragmentList
和 RecyclerView
以及图像。并且只有大图有 FragmentDetail
。来自 FragmentList
列表项的图像飞到 FragmentDetail
。 FragmentList
和 FragmentDetail
上的动画视图 TransitionName
必须相同。所以我为每个列表项 ImageView:
imageView.setTransitionName("anyString" + position)
然后我通过 setArguments
将该字符串传递给 FragmentDetail
并将大图像 transitionName
设置为该字符串。我们还应该提供 Transition
来描述视图如何从一个视图层次结构动画到另一个视图层次结构。之后我们应该将 transition
传递给片段。我之前做过 FragmentTransaction
:
detailFragment.setSharedElementEnterTransition(getTransition());
detailFragment.setSharedElementReturnTransition(getTransition());
或者您可以覆盖 Fragment
的 getSharedElementEnterTransition
和 getSharedElementReturnTransition
并在那里声明 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 B
到 Fragment A
的 popbackstack
上,调用了 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;
}
});
}