ViewPager2内部的项目阴影被裁剪

时间:2020-04-23 10:03:44

标签: android android-layout android-recyclerview android-view android-viewpager2

我有一个具有NavHostFragment的活动。该NavHostFragment将包含三个片段,其中两个片段是FragmentAFragmentB。在FragmentB中,我有一个ViewPager2,其中有两个页面:PageAPageB,实际上都是由一个片段FragmentC构成的。在每个PageAPageB中,我有一个RecyclerView

这是FragmentB的布局XML。

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="@dimen/keyline_4"
        android:clipChildren="false"
        android:clipToPadding="false"
        tools:context=".ui.NavigationActivity">

        ...

        <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/frag_course_view_pager"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginTop="@dimen/keyline_4"
            android:clipChildren="false"
            ... />

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/frag_course_tablayout"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/keyline_4"
            ... />


    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

如您所见,我已将片段的根布局的clipChildren设置为false。我还将ViewPager2的{​​{1}}设置为clipChildren。这是falsePageA(即PageB)的布局XML。

FragmentC

如您所见,它只有<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="viewmodel" type="com.mobile.tugasakhir.viewmodels.course.CourseTabViewModel" /> </data> <androidx.recyclerview.widget.RecyclerView android:id="@+id/frag_course_tab_rv" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/offset_bottom_nav_bar_padding" android:clipChildren="false" android:clipToPadding="false" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/> </layout> ,并且我将RecyclerView设置为clipChildren。 RV的ViewHolder的膨胀XML布局如下。

false

剪切的部分是上述<?xml version="1.0" encoding="utf-8"?> <layout 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"> <data> ... </data> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/component_course_container" android:layout_width="match_parent" android:layout_height="@dimen/course_card_height" android:background="@drawable/drawable_rounded_rect" android:backgroundTint="?attr/colorSurface" android:elevation="@dimen/elevation_0"> ... ... </androidx.constraintlayout.widget.ConstraintLayout> </layout> 的XML布局的elevation的阴影。当我将所有父项的所有ViewHolder属性都设置为clipChildren时,阴影不应该被裁剪,但它仍然被裁剪。为什么会这样呢?如何在不更改填充/边距的情况下防止其被剪切

注意:我在false内也有一个RecyclerView,但是区别在于FragmentA内的RecyclerView没有嵌套在FragmentA。按照ViewPager2上的方法(将所有父母的clipChildren设置为false),可以使FragmentA的项目显示其阴影。

这是问题的形象。

Problem


更新

使用布局检查器,似乎在RecyclerView内,有更多的ViewGroup(用红色矩形标记)。我的ViewPager2及其项目被剪切时,用绿色矩形标记。这是布局检查器显示的内容。

Layout Inspector

可以看出,在RecyclerView内有一个ViewPager2,在里面有一个ViewPager2$RecyclerViewImpl(我没有创建这些ViewGroup)。事实证明,即使将FrameLayout的{​​{1}}设置为false,这两个文件的 clipChildren仍设置为true 。我可以像这样在ViewPager2内定位clipChildren

ViewPager2$RecyclerViewImpl

然后我尝试使用类似的方法定位FragmentB

(viewPager.getChildAt(0) as ViewGroup).clipChildren = false

但是,我收到一条错误消息,说第二个FrameLayout返回((viewPager.getChildAt(0) as ViewGroup).getChildAt(0) as ViewGroup).clipChildren = false 。在我的布局检查器中,它清楚地表明RecyclerView之前有一个FrameLayout。该getChildAt(0)的{​​{1}}设置为true。我很确定我必须将null的{​​{1}}设置为FrameLayout,以便不修剪阴影,但我可能是错的。

以下是屏幕截图,显示我设法将clipChildren的{​​{1}}设置为FrameLayout,而未能将clipChildren的{​​{1}}设置为{{1 }}。

Success

Failure

还是有一种更好的方法来消除阴影?

我出于私人原因遮挡了布局预览;这会使布局预览成为白框。


更新2

对于那些想直接查看问题的人,只需运行此Github link中提供的应用程序即可。使用“布局检查器”查看我看到的内容。

3 个答案:

答案 0 :(得分:2)

此答案直接说明了我们如何将自动生成的有问题的clipChildren的{​​{1}}设置为false;从而消除了物品的阴影。如果您不介意修改当前的XML布局,请签出@Martin Marconcini's equally awesome answer(使用FrameLayout绘制未修剪的阴影)也可以解决此问题。


怀疑,提到的CardView是造成孩子被剪的原因。 FrameLayout由扩展类FrameLayout的类FragmentStateAdapter创建。根据我的收集,这个RecyclerView.Adapter<FragmentViewHolder>的工作原理与普通FragmentViewHolder的视图持有人相似。目前,此RecyclerView的{​​{3}}始终返回一个FragmentViewHolder(它是一个FrameLayout)。视图持有者将始终返回ViewGroup的事实似乎不太可能改变,即使在将来也是如此。


依赖性ViewGroup

如果您使用的是上面的依赖项,则可以看到androidx.viewpager2:viewpager2:1.0.0是在FrameLayout类的onCreateViewHolder函数内部创建的(第160行)。

FragmentStateAdapter

@NonNull @Override public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return FragmentViewHolder.create(parent); } 方法将始终创建一个FragmentViewHolder.create(parent)视图持有人。这将作为参数(FrameLayout)传递到holder的{​​{1}}中。 onBindViewHolder的方法声明如下。

FragmentStateAdapter

FragmentViewHolder.create属性设置为false

我们现在知道@NonNull static FragmentViewHolder create(@NonNull ViewGroup parent) { FrameLayout container = new FrameLayout(parent.getContext()); container.setLayoutParams( new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); container.setId(ViewCompat.generateViewId()); container.setSaveEnabled(false); return new FragmentViewHolder(container); } 将作为参数传递给clipChildren的{​​{1}}方法中,我们可以覆盖holder(在您自己的适配器类中扩展onBindViewHolder),将FragmentStateAdapter的{​​{1}}属性设置为onBindViewHolder,瞧瞧,RecyclerView中的项目将不再被裁剪。

我的适配器类的最终代码如下。

FragmentStateAdapter

层次结构上方具有小于阴影所需区域的边界框的其他父布局也需要将其holder.itemView设置为clipChildren(即false父布局由class PagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) { override fun getItemCount(): Int = 1 override fun createFragment(position: Int): Fragment { return PageFragment().apply { arguments = Bundle() } } // Setting its clipChildren to false override fun onBindViewHolder( holder: FragmentViewHolder, position: Int, payloads: MutableList<Any> ) { (holder.itemView as ViewGroup).clipChildren = false super.onBindViewHolder(holder, position, payloads) } } (如问题中所述)。


更新

我已经更新了之前的Github代码,以便包括最终解决方案,将clipChildrenfalse的{​​{1}}的{​​{1}}设置为false RecyclerViewImplViewPager2。这是链接:itemView

答案 1 :(得分:1)

在更详细地研究了您的问题之后,我相信问题是使用Drawable作为背景的ViewHolder XML试图模拟阴影。

结果证明,Android上的阴影不是“真实阴影”,而是框架伪造的绘制元素。这些阴影在某些情况下会使用框架添加的额外填充;再次。

CardViews具有内部机制来处理此问题(尤其是在没有提升概念的平台上)。

由于这个原因,我建议您将ViewHOlder更改为包含CardView。在这种情况下,它看起来像:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/component_course_container"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:backgroundTint="#efefef">

    <androidx.cardview.widget.CardView
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:cardCornerRadius="8dp"
        app:cardUseCompatPadding="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:text="Your Content Goes Here"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.cardview.widget.CardView>

</androidx.constraintlayout.widget.ConstraintLayout>

然后...删除 all 的clipChildren / CliptoPadding等。这将使您比解决方案更头痛,并且会降低性能(该框架现在需要绘制其他内容,否则本来不会因为已被覆盖而被渲染)。

希望对您有帮助。

关于FrameLayout,这是VP的“东西”,我希望Google在最新的AppCompat实现中将其替换为新的“ FragmentContainer”。

祝你好运。

这是现在的样子:

enter image description here

答案 2 :(得分:-1)

clipToPadding="false"添加到为ViewHolder夸大的xml文件的根ConstraintLayout中。