我有一个具有NavHostFragment
的活动。该NavHostFragment
将包含三个片段,其中两个片段是FragmentA
和FragmentB
。在FragmentB
中,我有一个ViewPager2
,其中有两个页面:PageA
和PageB
,实际上都是由一个片段FragmentC
构成的。在每个PageA
和PageB
中,我有一个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
。这是false
和PageA
(即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
的项目显示其阴影。
这是问题的形象。
使用布局检查器,似乎在RecyclerView
内,有更多的ViewGroup(用红色矩形标记)。我的ViewPager2
及其项目被剪切时,用绿色矩形标记。这是布局检查器显示的内容。
可以看出,在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 }}。
还是有一种更好的方法来消除阴影?
我出于私人原因遮挡了布局预览;这会使布局预览成为白框。
对于那些想直接查看问题的人,只需运行此Github link中提供的应用程序即可。使用“布局检查器”查看我看到的内容。
答案 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代码,以便包括最终解决方案,将clipChildren
和false
的{{1}}的{{1}}设置为false RecyclerViewImpl
和ViewPager2
。这是链接: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”。
祝你好运。
这是现在的样子:
答案 2 :(得分:-1)
将clipToPadding="false"
添加到为ViewHolder夸大的xml文件的根ConstraintLayout
中。