CoordinatorLayout 的内容子视图覆盖了 BottomNavigationView

22

我正在尝试使用CoordinatorLayoutBottomNavigationViewAppBarLayoutViewPager。这是我的布局:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context=".MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:layout_scrollFlags="enterAlways|scroll"
            app:popupTheme="@style/AppTheme.PopupOverlay"/>

    </android.support.design.widget.AppBarLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

    <android.support.design.widget.BottomNavigationView
        android:id="@+id/navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:background="?android:attr/windowBackground"
        app:itemIconTint="?colorPrimaryDark"
        app:itemTextColor="?colorPrimaryDark"
        app:menu="@menu/navigation"/>
</android.support.design.widget.CoordinatorLayout>

问题在于CoordinatorLayoutViewPager放置在屏幕底部,导致BottomNavigationView遮挡了底部,如下所示:

bounds of ViewPager

即使CoordinatorLayout本身没有延伸那么远,这种情况仍然会发生:

bounds of CoordinatorLayout

我尝试将app:layout_insetEdge="bottom"添加到BottomNavigationView,并将app:layout_dodgeInsetEdges="bottom"添加到ViewPager,但这会导致另一个问题:它将ViewPager的底部向上移动,但保持相同的高度,因此顶部被削掉了:

shifted ViewPager bounds

我尝试了另外两个实验。首先,我尝试将BottomNavigationViewCoordinatorLayout中删除,并使它们成为垂直的LinearLayout中的同级元素。其次,我将ViewPagerBottomNavigationView放在一个LinearLayout下,希望它们能正确布局。但两者都没有起到作用:在第一种情况下,CoordinatorLayout仍然将ViewPager的大小与整个屏幕相关联,而要么隐藏部分ViewPagerBottomNavigationView后面,要么截掉顶部。在第二种情况下,用户需要滚动才能看到BottomNavigationView
如何使布局正确?
附言:当我尝试使用@AnoopSS建议的布局(将CoordinatorLayoutBottomNavigationView作为同级元素放在一个RelativeLayout下)时,我得到了以下结果(ViewPager仍然向下延伸到BottomNavigationView后面):

result of Anoop's layout

与之前一样,CoordinatorView 本身仅延伸到 BottomNavigationView 的顶部。

请检查此处的答案,它在这里运行良好。 - Jafar Sadiq SH
我已经在https://issuetracker.google.com/issues/116541304上创建了有关此问题的问题。 - Daniele Segato
7个回答

15

我想到了一种不同的方法(虽然还没有经过实战测试):

我创建了一个子类,继承自AppBarLayout.ScrollingViewBehavior,根据BottomNavigationView的高度(如果存在)来调整内容视图的底边距。这样就可以保证未来的可靠性,如果由于任何原因导致BottomNavigationView的高度发生变化。

子类(Kotlin):

class ScrollingViewWithBottomNavigationBehavior(context: Context, attrs: AttributeSet) : AppBarLayout.ScrollingViewBehavior(context, attrs) {
    // We add a bottom margin to avoid the bottom navigation bar
    private var bottomMargin = 0

    override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
        return super.layoutDependsOn(parent, child, dependency) || dependency is BottomNavigationView
    }

    override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
        val result = super.onDependentViewChanged(parent, child, dependency)

        if(dependency is BottomNavigationView && dependency.height != bottomMargin) {
            bottomMargin = dependency.height
            val layout = child.layoutParams as CoordinatorLayout.LayoutParams
            layout.bottomMargin = bottomMargin
            child.requestLayout()
            return true
        } else {
            return result
        }
    }
}

然后在布局的XML中放置:

app:layout_behavior=".ScrollingViewWithBottomNavigationBehavior"

取代

app:layout_behavior="@string/appbar_scrolling_view_behavior"

非常好的解决方案,谢谢!这是我找到的唯一一个利用CoordinatorLayout强大的Behavior系统的解决方案。注意:我的子视图是Fragment/FrameLayout,所以我不得不将其更新为使用padding而不是margin,它运行得非常好。 - Luke S.
啊,老兄。如果早点找到这个,就可以省下我几个小时的尝试了 :) 谢谢! - kazume
最佳解决方案。非常感谢。 - Ridcully
这个很好用。应该标记为被接受的解决方案。 - Mike Walker

1
基本上,您需要创建一个RelativeLayout作为父级,并将BottomNavigationView和CoordinatorLayout作为子级。然后将BottomNavigationView对齐到底部,并将CoordinatorLayout设置为在其上方。请尝试下面的代码。它可能会有一些属性错误,因为我是在这里编写的。对于混乱的缩进表示抱歉。
<?xml version="1.0" encoding="utf-8"?>

    <RelativeLayout
        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"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/navigation"
        >

        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fitsSystemWindows="true"
            android:theme="@style/AppTheme.AppBarOverlay">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_scrollFlags="enterAlways|scroll"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>

        </android.support.design.widget.AppBarLayout>

        <android.support.v4.view.ViewPager
            android:id="@+id/pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

    </android.support.design.widget.CoordinatorLayout>

<android.support.design.widget.BottomNavigationView
            android:id="@+id/navigation"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:background="?android:attr/windowBackground"
            app:itemIconTint="?colorPrimaryDark"
            app:itemTextColor="?colorPrimaryDark"
            app:menu="@menu/navigation"/>

    </RelativeLayout>

我不得不将 BottomNavigationView 移动到 CoordinatorLayout 的同级而不是子级。虽然这仍然没有完全解决问题,但情况有所改善。ViewPager 仍然延伸到 BottomNavigationView 的后面,但现在不会延伸到系统导航栏的后面了。(CoordinatorLayout 本身并没有延伸到 BottomNavigationView 的顶部以下,但与之前一样,它将 ViewPager 布局得比自己大。) - Ted Hopp
我不得不将BottomNavigationView从子项移动到CoordinatorLayout的同级位置。是的,抱歉,那就是我的本意,但放错了位置。已更新答案。你现在遇到了什么问题,我没有正确理解。如果可能的话,请发布您的布局设计截图并在问题中更新布局代码。 - Anoop
我更新了我的答案,并附上了结果的截图。 - Ted Hopp
你在CoordinatorLayout中添加了android:layout_above="@+id/navigation"吗?你能检查一下吗?然后pager应该只到达BottomNavigationView的顶部。 - Anoop
很奇怪。一个ViewGroup怎么可能把它的子元素放在边界之外呢? - Anoop
显示剩余2条评论

1
这是由于在您的ViewPager中使用了app:layout_behavior="@string/appbar_scrolling_view_behavior"所导致的。如果删除此行,您将看到它现在适合CoordinatorLayout容器(不幸的是,这意味着现在在工具栏下面)。
我发现把CoordinatorLayout视为一个FrameLayout,并使用一些额外的技巧会有所帮助。上面的app:layout_behavior属性是必需的,以使工具栏看起来可以滚动进入和退出......实际上,布局通过将链接到折叠工具栏的视图(在您的情况下,您的ViewPager)比较工具栏的高度大,超出边界。向上滚动将视图向上移至边界内的底部,并将工具栏向上推出超出边界。向下滚动则反之。
现在,进入BottomNavigationView!如果像我一样,您希望BottomNavigationView始终可见,则将其移动到CoordinatorLayout之外,如Anoop所说。仅将CoordinatorLayout用于需要协调的事物,其他所有内容都在外面。我碰巧使用ConstraintLayout作为父视图(您可以使用RelativeLayout或任何适合您的东西)。对于ConstraintLayout,对于您而言,它将如下所示:
 <android.support.constraint.ConstraintLayout
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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">


<android.support.design.widget.CoordinatorLayout
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toTopOf="@id/navigation"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    tools:context=".MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:layout_scrollFlags="enterAlways|scroll"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</android.support.design.widget.CoordinatorLayout>

<android.support.design.widget.BottomNavigationView
    android:id="@+id/navigation"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:background="?android:attr/windowBackground"
    app:itemIconTint="?colorPrimaryDark"
    app:itemTextColor="?colorPrimaryDark"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:menu="@menu/navigation" />

 </android.support.constraint.ConstraintLayout>

在Android Studio的设计视图中,您仍然会看到ViewPager似乎比容器更大(可能看起来仍然在底部导航栏后面)。但是没关系,当您滚动到ViewPager内容的底部时,它将显示出来(即不会在底部导航栏后面)。这种设计视图中的怪癖只是CoordinatorLayout使工具栏显示/隐藏的方式,如前所述。

问题是我需要ViewPager的内容完全可见,而无需滚动,所以这种方法行不通。我希望找到一种方法,使CoordinatorLayout在缩小工具栏的同时动态改变ViewPager的大小。我考虑过开发自己的Behavior,但我没有找到使用它实现我的目标的方法。 - Ted Hopp
协调者并不是在缩小工具栏,而是将其移出视图(超出边界)。您需要将视图分页器上的垂直滚动转换为使视图分页器变大的操作,以达到推动工具栏退出视图的相同效果......不确定该如何实现。顺便问一下,您的ViewPager页面/片段中的内容是否基本上完全可见,无论工具栏是否显示?我很好奇为什么您需要一个折叠式工具栏的行为和所有内容都可见。 - Vin Norman
ViewPager的内容大多数都是可见的。然而,一些片段(而不是全部)需要一个浮动操作按钮或其他小部件,它们靠近底部固定,并且必须始终可见。我认为我不能只创建一个顶层FAB并在片段之间共享它,因为FAB需要锚定到不同的视图,并具有不同的外观,这取决于当前在ViewPager中显示哪个片段。 - Ted Hopp
有道理,我也遇到了浮动操作按钮的这个问题,正如你所提到的,我的解决方案是一个顶级 FAB(例如,在你的情况下,是 ViewPager 的同级元素,在 Coordinator 布局中)。我按照你所说的做法,与子片段共享了这个 FAB。我能够通过 fab.setImageResource(R.drawable.my_drawable) 更改 fabs 的外观、不同的图标等,但可能会在将其锚定到视图中出现困难! - Vin Norman
我能想到的唯一一件事是,我还没有用这个测试过有效的解决方案,就是尝试在页面片段中的不同视图中加入'app:layout_behavior =“@string / appbar_scrolling_view_behavior”,并将其从viewPager中取出。 我认为你会遇到ViewPager出现在工具栏下方的问题,不确定设置边距是否能解决该问题,并且当工具栏折叠时,它是否能很好地发挥作用... - Vin Norman
显示剩余2条评论

1

我有一个与OP非常相似的布局问题,使用了一个ViewPager和3个页面,但只有第2页和第3页应受appbar_scrolling_view_behavior影响。

在探索死胡同的可能解决方案(layout_dodgeInsetEdges、Window insets、尝试修改ViewPager页面的测量大小、android:clipChildren、fitSystemWindows等)数小时后,我终于找到了下面详细说明的简单解决方案。

正如Vin Norman所解释的那样,ViewPager重叠BottomNavigation完全是由于将appbar_scrolling_view_behavior设置在ViewPager上造成的。AppBarLayout将使具有appbar_scrolling_view_behavior的同级元素全屏显示。这就是它的工作原理。

如果您只需要在某些ViewPager页面上使用此行为,则可以在ViewPager的OnPageChangeListener上应用简单的修复方法来动态更改Behavior并添加/删除所需的填充:

public class MyOnPageChangeListener extends  ViewPager.SimpleOnPageChangeListener {

        @Override
        public void onPageSelected(int position) {

           ...

           CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) _viewPager.getLayoutParams();

        if(position == 0) {
            params.setBehavior(null);
            params.setMargins(params.leftMargin, _appBarLayoutViewPagerMarginTopPx, 
                    params.rightMargin, _appBarLayoutViewPagerMarginBottomPx);
        } else {
            params.setBehavior(_appBarLayoutViewPagerBehavior);
            params.setMargins(params.leftMargin, 0, params.rightMargin, 0);
        }

        _viewPager.requestLayout();

        }

}

对于位置为0的页面(我们希望ViewPager在Toolbar正下方、BottomNavigationView正上方延伸),它会移除行为并分别添加顶部和底部填充,即_appBarLayoutViewPagerMarginTopPx和_appBarLayoutViewPagerMarginBottomPx。这些常量可以预先轻松计算(分别为R.attr.actionbarSize的像素值和NavigationBottomView的高度。通常都为56dp)。
对于所有需要appbar_scrolling_view_behavior的其他页面,我们恢复相关的滚动行为(事先存储在_appBarLayoutViewPagerBehavior中),并移除顶部和底部填充。
我测试了这个解决方案,没有任何问题。

0

如果有人仍在寻找此问题的解决方案:

问题的原因是CoordinatorLayout没有正确计算AppBarLayout的大小,因为它具有带有app:layout_scrollFlags="enterAlways|scroll"设置的工具栏。它认为当滚动时工具栏将隐藏,因此它会将所有可用空间留给ViewPager,但实际上发生的是工具栏显示,因此ViewPager向下移动,在NavigationBar后面。

最简单的解决方法就是向AppBarLayout添加android:minHeight="?attr/actionBarSize"(或您正在使用的任何工具栏高度)。这样,CoordinatorLayout将正确知道需要为ViewPager留多少空间。


0

如果对某人仍然有意义:

在Anoop SS的答案中,尝试将RelativeLayout替换为LinearLayout。还要将CoordinatorLayoutlayout_height设置为0dp,并将layout_weight设置为1。

我几乎遇到了同样的问题...只是我想在底部放置一个静态的AdView而不是BottomNavigationView。尝试Anoop SS的建议,起初,我得到了与OP相同的行为:ViewPager扩展到了AdView后面。但是然后我按照自己的建议做了一些事情,一切都正常了。

Android布局的行为方式很奇怪,或者可能是缺乏良好的文档或我们缺乏知识...但大多数时候制作布局太烦人了。


-1
如果你正在使用 Androidx,请尝试这个。
<RelativeLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">

<androidx.coordinatorlayout.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:layout_above="@+id/bottomNavView">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </com.google.android.material.appbar.AppBarLayout>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <fragment
            android:id="@+id/nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:navGraph="@navigation/mobile_navigation" />
    </FrameLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

<com.google.android.material.bottomnavigation.BottomNavigationView
    android:id="@+id/bottomNavView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:background="?android:attr/windowBackground"
    app:menu="@menu/bottom_nav" />


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接