微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

MaterialContainerTransform 转换不适用于返回

如何解决MaterialContainerTransform 转换不适用于返回

我的 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 填充了电子邮件并且转换能够找到您配置的映射。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。