问题描述
我的 MaterialContainerTransform 转换是从源 -> 目标开始,但不是相反。我的情况非常标准——我试图提供从 RecyclerView 项目(源片段)到“详细信息”片段(目标片段)的简单转换。 RecyclerView 中的项目是 MaterialCardViews,每个项目都有一个 ImageView,在目标 Fragment 中共享。
遵循这些文档 https://material.io/develop/android/theming/motion 似乎相当简单,尽管这些文档是用 Kotlin 编写的,而且我使用的是 Java,所以也许我遗漏了什么?我在每个 RecyclerView 项目中动态设置 ImageView transitionName,并将其传递给目标 Fragment,然后将 transitionName 复制到它自己的 ImageView。通过日志,我可以确认每个片段中共享的transitionName是否匹配。
从源 -> 目标的过渡效果很好,但是当我点击后退按钮时,没有过渡回来。这很奇怪,因为即使是文档状态:
片段能够定义输入和返回共享元素转换。当只设置了一个 enter 共享元素转换时,它会在 Fragment 弹出(返回)时重新使用。
任何想法为什么返回转换(目标 -> 源)不起作用?相关代码如下:
RecyclerView Item (Source) **ImageView 是共享元素
<com.google.android.material.card.MaterialCardView
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/result_layout_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="5dp"
app:cardElevation="4dp"
android:layout_margin="8dp"
android:clickable="true"
android:focusable="true"
android:checkable="true"
app:checkedIconTint="@color/checkedYellow"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_gravity="center">
<ImageView
android:id="@+id/result_image_thumbnail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:contentDescription="@string/thumbnail_content_description"
android:src="@drawable/corolla_preview"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:orientation="vertical">
<TextView
android:id="@+id/result_text_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2018 Toyota Corolla"
style="@style/TextAppearance.MaterialComponents.Subtitle1"
/>
<TextView
android:id="@+id/result_text_stock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#626546"
style="@style/TextAppearance.MaterialComponents.Body2"
/>
<TextView
android:id="@+id/result_text_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="$28,998"
style="@style/TextAppearance.MaterialComponents.Overline"
/>
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
Detail Fragment (destination) ** ImageView 又是共享元素
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<ImageView
android:id="@+id/detail_image_thumbnail"
android:layout_width="match_parent"
android:layout_height="225dp"
android:contentDescription="@string/thumbnail_content_description"/>
</LinearLayout>
</ScrollView>
RecyclerView Holder (source) ** viewResults 方法是我开始片段事务的地方
public class ResultsHolder extends RecyclerView.ViewHolder {
private static final String TAG = ResultsHolder.class.getSimpleName();
private final FragmentManager fragmentManager;
private ResultModel resultModel;
private final MaterialCardView cardView;
private final ImageView thumbnail;
private String searchId;
private ResultsFragment resultsFragment;
private int position;
private View thumbnailView;
public ResultsHolder(View itemView,FragmentManager fragmentManager,String searchId,ResultsFragment resultsFragment) {
super(itemView);
cardView = itemView.findViewById(R.id.result_layout_card);
thumbnail = itemView.findViewById(R.id.result_image_thumbnail);
this.fragmentManager = fragmentManager;
this.searchId = searchId;
this.resultsFragment = resultsFragment;
}
public void bindResult(ResultModel result,int position) {
resultModel = result;
Picasso.get().load(result.getimageUrl()).into(thumbnail);
cardView.setChecked(result.isChecked());
cardView.setonClickListener(cardViewClickListener);
this.position = position;
thumbnail.setTransitionName(result.getVin()); // transition name is unique for each recyclerview
// item
}
private final View.OnClickListener cardViewClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
viewDetails(resultModel,searchId);
}
};
// This is the method where I start the destination fragment
public void viewDetails(ResultModel result,String searchId) {
Bundle args = new Bundle();
args.putParcelable("RESULT",Parcels.wrap(result));
args.putString("SEARCH_ID",searchId);
DetailFragment fragment = new DetailFragment();
// Destination fragment enter transition!
fragment.setSharedElementEnterTransition(new MaterialContainerTransform());
fragment.setArguments(args);
fragmentManager
.beginTransaction()
.setReorderingallowed(true)
.addSharedElement(thumbnail,thumbnail.getTransitionName()) // Shared element!
.replace(R.id.main_fragment_container,fragment,DetailFragment.class.getSimpleName())
.addToBackStack(null)
.commit();
}
详细信息片段(目的地)
public class DetailFragment extends Fragment {
@SuppressWarnings("unused")
private static final String TAG = "DetailFragment";
private ResultModel result;
private String searchId;
ImageView image;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
// Get result and search ID
result = Parcels.unwrap(getArguments().getParcelable("RESULT"));
searchId = getArguments().getString("SEARCH_ID");
}
}
private void instantiateUI(View v) {
TextView vin = v.findViewById(R.id.tv_details_vin);
vin.setText(result.getVin());
image = v.findViewById(R.id.detail_image_thumbnail);
Picasso.get().load(result.getimageUrl()).fit().into(image);
}
@Override
public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_detail,container,false);
// Set transitionName exactly same as the recyclerview item which was clicked
v.findViewById(R.id.detail_image_thumbnail).setTransitionName(result.getVin());
return v;
}
@Override
public void onViewCreated(View view,@Nullable Bundle savedInstanceState) {
if (((AppCompatActivity) getActivity()) != null
&& ((AppCompatActivity) getActivity()).getSupportActionBar() != null) {
Objects.requireNonNull(((AppCompatActivity) getActivity())
.getSupportActionBar()).setTitle("Details");
}
instantiateUI(view);
}
}
解决方法
我已经在我的旧项目中实现了这一点。 检查您从下面缺少什么:
1.在您的 SourceFragment 中添加以下代码行。这里的关键点是在 onViewCreated() 方法中,您必须使用 postponeEnterTransition() 和 startPostponedEnterTransition(),这是在用户返回源片段时正确设置动画所必需的.同样在 onCreate() 方法中设置 Exit Transition 和 ReenterTransition 以使项目列表在退出时向外扩展并在重新进入时返回:
public class SourceFragment extends Fragment {
private RecyclerView recyclerView;
private LinearLayoutManager mLayoutManager;
public static int index = -1;
public static int top = -1;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//set the below transitions so as the source list scale out when exiting and back in when reentering
setExitTransition(new MaterialElevationScale(false));
setReenterTransition(new MaterialElevationScale(true));
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,@Nullable ViewGroup container,@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_source,container,false);
recyclerView = view.findViewById(R.id.recycler_view);
mLayoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.setAdapter(new SourceAdapter(this,getFragmentManager()));
return view;
}
@Override
public void onViewCreated(@NonNull View view,@Nullable Bundle savedInstanceState) {
super.onViewCreated(view,savedInstanceState);
//the below code is required to animate correctly when the user returns to the source fragment
//gives a chance for the layout to be fully laid out before animating it
postponeEnterTransition();
((ViewGroup) view.getParent()).getViewTreeObserver()
.addOnPreDrawListener(() -> {
startPostponedEnterTransition();
return true;
});
}
@Override
public void onPause() {
super.onPause();
//Save the current state of recycle view position
index = mLayoutManager.findFirstVisibleItemPosition();
View startView = recyclerView.getChildAt(0);
top = (startView == null) ? 0 : (startView.getTop() - recyclerView.getPaddingTop());
}
@Override
public void onResume()
{
super.onResume();
//Scrolls the recycler view to the clicked item position when navigating back
if(index != -1) {
mLayoutManager.scrollToPositionWithOffset(index,top);
}
}
}
2.设置转换名称以映射共享元素,例如:图像项(起始视图)到其目标片段图像(结束视图)。在 onBindViewHolder() 的 SourceAdapter 中,为每个图像项设置一个唯一标识符。根据您的代码将是:
thumbnail.setTransitionName(result.getVin());
3.将此唯一标识符传递到您的 DestinationFragment (DetailFragment) 中并在 onCreateView() 中完成映射。根据您的代码将是:
@Override
public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_detail,false);
// Set transitionName exactly same as the recyclerview item which was clicked
v.findViewById(R.id.detail_image_thumbnail).setTransitionName(result.getVin());
return v;
}
4.在你的 DestinationFragment (DetailFragment) 的 onCreate() 方法中添加 enter Transition 如下:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//prepare the EnterTransition
MaterialContainerTransform transform = new MaterialContainerTransform();
transform.setScrimColor(Color.TRANSPARENT);
setSharedElementEnterTransition(transform);
//get your arguments according to your code
if (getArguments() != null) {
result = Parcels.unwrap(getArguments().getParcelable("RESULT"));
searchId = getArguments().getString("SEARCH_ID");
}
}
5.最后根据你的代码从SourceFragment切换到DestinationFragment:
Bundle args = new Bundle();
args.putParcelable("RESULT",Parcels.wrap(result));
args.putString("SEARCH_ID",searchId);
DetailFragment fragment = new DetailFragment();
fragment.setArguments(args);
fragmentManager
.beginTransaction()
.setReorderingAllowed(true)
.addSharedElement(thumbnail,thumbnail.getTransitionName()) // Shared element!
.replace(R.id.main_fragment_container,fragment,DetailFragment.class.getSimpleName())
.addToBackStack(null)
.commit();
,
我没有研究过您的代码,但以下可能可以解决您的问题。来自Material Motion Codelab。提到的“折叠”是从详细视图返回到 RecyclerView 项。
通常,崩溃的第一个问题不起作用是因为当 Android Transition 系统尝试运行您的返回转换时,电子邮件列表尚未膨胀并填充到 RecyclerView 中。在开始转换之前,我们需要一种方法来等待 HomeFragment 列出我们的列表。
Android Transition 系统提供了实现此目的的方法 - 推迟EnterTransition 和startPostponedEnterTransition。如果调用welcomeEnterTransition,则任何要运行的进入转换将被保持,直到调用startPostponedEnterTransition 的结束调用。这让我们有机会“安排”我们的转换,直到 RecyclerView 填充了电子邮件并且转换能够找到您配置的映射。