将 Snackbar 移动到底部栏之上

77
我遇到了新底部栏的一些问题。 我不能强制将 snackbar 移动到底部栏上方(设计指南告诉我应该这样做 https://www.google.com/design/spec/components/bottom-navigation.html#bottom-navigation-specs)。
这是我的 activity_main.xml。
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout 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:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">

<include
    layout="@layout/app_bar_main_activity"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

<android.support.design.widget.NavigationView
    android:id="@+id/nav_view"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:fitsSystemWindows="true"
    app:headerLayout="@layout/nav_header_main_activity"
    app:menu="@menu/activity_main_drawer" />

</android.support.v4.widget.DrawerLayout>

这是我的app_bar_main_activity.xml文件

<?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:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="test.tab_activity">

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="@dimen/appbar_padding_top"
    android:theme="@style/MyAppTheme.NoActionBar.AppBarOverlay">

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

    </android.support.v7.widget.Toolbar>

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



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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v4.view.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|bottom"
        android:layout_margin="@dimen/fab_margin"
        android:src="@drawable/ic_add_white_24dp" />

    <android.support.design.widget.TabLayout
        android:id="@+id/tab_layout"
        style="@style/AppTabLayout"
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:background="?attr/colorPrimary"
        />

</LinearLayout>

main_activity.java 中的 snackbar 看起来像这样

FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Snackbar.make(findViewById(R.id.main_content), "Replace with your own action", Snackbar.LENGTH_LONG)
                    .setAction("Action", null).show();
        }
    });

错误...snackbar 应该位于底部栏之上


请分享您的完整布局文件。您的布局文件不完整。 - Zahidul Islam
修改了我的问题。认为不需要。 - Wladislaw
1
你想把 Snackbar 放在 BottomBar 上面吗? - Nidhi
是的,就像谷歌的设计指南一样。 - Wladislaw
2
注意:如果您在Fragment中显示Snackbar,请确保不要使用com.android.support:design:23.0.1。此版本存在错误,例如使Snackbar在横向模式下重叠黑色导航栏;或将Snackbar显示为半透明(https://github.com/mozilla-mobile/focus-android/issues/1124)。 - Mr-IDE
它正在工作。谢谢。 - stay_hungry
13个回答

80

通过使用 Material 组件库,您可以使用 setAnchorView 方法,使得一个 Snackbar 出现在特定视图的上方。

如果你正在使用 BottomAppBar 和 fab,则应该在 setAnchorView 中定义 fab 。类似于这样:

FloatingActionButton fab = findViewById(R.id.fab);
Snackbar snackbar = Snackbar.make(view, "Snackbar over BottomAppBar", Snackbar.LENGTH_LONG);
snackbar.setAnchorView(fab);
结果:

结果:

enter image description here

使用 BottomNavigationView,您可以将其定义为锚定视图:

    Snackbar snackbar = Snackbar.make(view,"Snackbar over BottomNav",Snackbar.LENGTH_INDEFINITE);
    snackbar.setAnchorView(bottomNavigationView);
    snackbar.show();

结果:

输入图像描述


1
很酷。为我工作。 - Long Pham
5
请问您能否将这篇回答标记为被接受的答案?这是正确的做法。谢谢Gabriele! - Toufic Batache
4
这是所有答案中最好的一个。 - DIRTY DAVE
2
最佳答案。我找了一天才找到! - kalan nawarathne
3
当 bottomNavigationBar 不在创建 SnackBar 的类中时,我该如何使用此方法?我使用单个 Activity 多个 Fragment 的方法,bottonNaviationBar 在单个 Activity 的视图绑定中,而 Snackbar 是从 Fragment 中调用的(通过一个普通的 Java 类,不是 View 类,因此我无法使用 findViewByID)。 - VanessaF
能否将 Snackbar 放在菜单后面?给人的感觉好像是从菜单顶部开始出现的? - Vinithius

47

通过更改 Snackbar 的边距,您可以以编程方式完成此操作,而无需在 xml 中添加额外的 CoordinatorLayout。

Java 示例:

Snackbar snack = Snackbar.make(findViewById(R.id.coordinatorLayout), 
    "Your message", Snackbar.LENGTH_LONG);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) 
    snack.getView().getLayoutParams();
params.setMargins(leftMargin, topMargin, rightMargin, bottomBar.height);
snack.getView().setLayoutParams(params);
snack.show();

Kotlin单行代码:

Snackbar.make(coordinatorLayout, "Your message", Snackbar.LENGTH_LONG).apply {view.layoutParams = (view.layoutParams as CoordinatorLayout.LayoutParams).apply {setMargins(leftMargin, topMargin, rightMargin, bottomBar.height)}}.show()

我有类似的方法,只需使用Snackbar布局参数的bottomMargin属性。https://dev59.com/jl0a5IYBdhLWcg3wNWXA#40686399 - Cheng
1
在我的情况下它可以工作,但是小吃滑动到底部栏上方,而不是在其后面。 - Carlos Hernández Gil
2
@CarlosHernándezGil,它会滑动是因为您底部的视图具有较低的高程/ Z属性。 Snackbar的高程为6dp。通过在XML或stateListAnimator中将高程属性设置为大于6dp来增加您的视图的高程。有关使用属性值动画的工作,请参见Android文档,并访问Material Design指南以获取高程信息。 - Chad Mx
这个对我有用。谢谢。应该被接受的答案。 - Chad Mx

45

假设你正在使用CoordinatorLayout,你可以在调用show()之前修改Snackbar的布局参数。通过设置anchorId和anchorGravity,SnackBar将显示在底部导航栏上方:

val layoutParams = snackbar.view.layoutParams as CoordinatorLayout.LayoutParams
layoutParams.anchorId = R.id.navigation //Id for your bottomNavBar or TabLayout
layoutParams.anchorGravity = Gravity.TOP
layoutParams.gravity = Gravity.TOP
snackbar.view.layoutParams = layoutParams

3
我认为这应该是被接受的答案,它是一个非常优雅的解决方案。 - RobertoAllende
3
这是Google在Material.io上推荐的解决方案。 - Joshua King
这是一个优雅的解决方案。我同意它应该成为被接受的答案。 - Richard
上述推荐解决方案确实建议使用.setAnchorView(...) - Ryan W

34

替换您的xml ->

<?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:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="test.tab_activity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
            android:id="@+id/main_activity_toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways">

        </android.support.v7.widget.Toolbar>

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



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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <android.support.design.widget.CoordinatorLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:id="@+id/placeSnackBar">

            <android.support.v4.view.ViewPager
                android:id="@+id/view_pager"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1" />

            <android.support.design.widget.FloatingActionButton
                android:id="@+id/fab"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="end|bottom"
                android:layout_margin="@dimen/fab_margin"
                android:src="@drawable/ic_menu_gallery" />

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

        <android.support.design.widget.TabLayout
            android:id="@+id/tab_layout"
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:background="?attr/colorPrimary" />

    </LinearLayout>

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

Snackbar代码将是:

Snackbar.make(findViewById(R.id.placeSnackBar), "Replace with your own action", Snackbar.LENGTH_LONG)
            .setAction("Action", null).show();

1
我遇到了一个问题,当我使用你的方法时,我的片段布局被隐藏了。我的布局只是在某个我看不到的地方。有什么想法吗?这可能是一个新问题,但它与你的答案有关。 - Wladislaw
你是指ViewPager吗? - Zahidul Islam
是的,ViewPager已经移动了(可能移到了顶部?)。因此,如果我在那里使用CoordinatorLayout,我就看不到我的布局了。 - Wladislaw
2
对我不起作用。我在根协调器布局中有一个TabLayout,并尝试在底部TabLayout上方显示Snackbar。 - rahul.ramanujam
这确实是一个非常好的答案,只需将嵌套的CoordinatorLayout视图传递给Snackbar的make()方法,Snackbar就会从那里开始显示。 - Ali Ashraf

16

这里有一篇关于如何使用它的好文章HERE。在那里,您将了解如何在BottomNavigationBar上方创建Snackbar

基本上,下面的代码展示了ToolbarBottomNavigationBarFrameLayout作为片段容器的最常见用法

重要提示!请注意

  1. Fab按钮使用anchor来正确放置,并使用useCompactPadding以保留边距

  2. BottomNavigationView使用layout_behaviour来处理滚动和SnackBar位置

<android.support.design.widget.AppBarLayout
    android:id="@+id/myAppBar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:descendantFocusability="beforeDescendants"
    android:focusableInTouchMode="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"
        android:background="?attr/colorPrimary"
        app:contentInsetStart="0dp"
        app:layout_scrollFlags="scroll|enterAlways"
        app:popupTheme="@style/AppTheme.PopupOverlay"/>
</android.support.design.widget.AppBarLayout>

<FrameLayout
    android:id="@+id/fragment_container"
    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_bar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    app:menu="@menu/bottom_navigation"
    app:layout_behavior="murt.shoppinglistapp.ui.BottomNavigationBehavior"
    android:background="?android:attr/windowBackground"
    />

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab_add_shopping_list"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:useCompatPadding="true"
    app:srcCompat="@drawable/ic_add_24"
    app:layout_anchor="@id/navigation_bar"
    app:layout_anchorGravity="top|right"
    android:layout_gravity="top"
    />

实现行为时不要犹豫使用!它易于操作且友好;)(滚动)

  • class BottomNavigationBehavior : CoordinatorLayout.Behavior<BottomNavigationView> {
    
        constructor(): super()
    
        constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
    
        override fun layoutDependsOn(parent: CoordinatorLayout, child: BottomNavigationView,
                                     dependency: View): Boolean {
            if (dependency is Snackbar.SnackbarLayout) {
                updateSnackbar(child, dependency)
            }
            return super.layoutDependsOn(parent, child, dependency)
        }
    
        private fun updateSnackbar(child: View, snackbarLayout: Snackbar.SnackbarLayout) {
            if (snackbarLayout.layoutParams is CoordinatorLayout.LayoutParams) {
                val params = snackbarLayout.layoutParams as CoordinatorLayout.LayoutParams
    
                params.anchorId = child.id
                params.anchorGravity = Gravity.TOP
                params.gravity = Gravity.TOP
                snackbarLayout.layoutParams = params
            }
        }
    
        override fun onStartNestedScroll(
            coordinatorLayout: CoordinatorLayout,
            child: BottomNavigationView,
            directTargetChild: View,
            target: View,
            nestedScrollAxes: Int
        ): Boolean {
            return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
        }
    
        override fun onNestedPreScroll(
            coordinatorLayout: CoordinatorLayout,
            child: BottomNavigationView,
            target: View,
            dx: Int,
            dy: Int,
            consumed: IntArray
        ) {
            if (dy < 0) {
                showBottomNavigationView(child)
            } else if (dy > 0) {
                hideBottomNavigationView(child)
            }
        }
    
        private fun hideBottomNavigationView(view: BottomNavigationView) {
            view.animate().translationY(view.height.toFloat())
        }
    
        private fun showBottomNavigationView(view: BottomNavigationView) {
            view.animate().translationY(0f)
        }
    }
    

    3
    这个答案被低估了。这就是如何做到的! - Ghedeon

    16

    有一个简单的方法,使用新的Material Design:

    Snackbar snackbar= Snackbar.make(view, text, duration);
    snackbar.setAnchorView(bottomBar);
    
    因此,Snackbar将显示在BottomNavigationView上方。

    我已经完成了这个功能,但是 Snackbar 现在遮挡了 fab。 - jajube
    @jajube 为了避免这种情况发生...你应该将fab放置在Coordinator布局中,而且传递给make()函数的view参数应该是与你放置fab的Coordinator布局相同的布局。希望这能帮到你 :)...告诉我结果如何。 - Ansshkki

    8

    不必通过编程方式操作CoordinatorLayout参数,我发现你可以放置一个GuideLine (或者一个View,如果你没有使用ConstraintLayout),使得Snackbar可以放置在上方。这里我用1来表示100%,即父ConstraintLayout的底部:

    <androidx.constraintlayout.widget.Guideline
    android:id="@+id/snackbar_anchor"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    app:layout_constraintGuide_percent="1" />
    

    使用Viewbinding和以下代码将Snackbar放置在指南上方:
    Snackbar
            .make(containerView, alert.description, Snackbar.LENGTH_LONG).
            .setAnchorView(binding?.snackbarAnchor)
            .show()
    

    6

    受Gabriele Mariotti在这里的回答的启发,这是我的Kotlin解决方案:

    Snackbar.make(show_snack_btn, "Yeeeaaay!", Snackbar.LENGTH_LONG).also {
        it.anchorView = fab
    }.show()
    

    6
    如果父布局为Coordinator layout,那么这个操作非常简单。
    CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams)
                    snackbar.getView().getLayoutParams();
            params.setAnchorId(R.id.navigation); //id of the bottom navigation view
            params.gravity = Gravity.TOP;
            params.anchorGravity = Gravity.TOP;
            snackbar.getView().setLayoutParams(params);
    

    2
    这对底部导航+浮动操作按钮+FAB +滚动隐藏行为完美运作。 - Florian Walther
    如果您在主题问题上遇到了困难,无法正确设置颜色,而且 Snackbar 显示的行与底部导航栏相同,则这就是答案。 - JPM

    3
    为了实现这一点,您必须确保向 Snackbar 提供的 ViewGroup 是 CoordinatorLayout,否则 Snackbar 将不会显示在底部导航菜单之上。

    这个答案是不正确的。我已经将一个 CoordinatorLayout 的引用作为 Snackbar.make() 的第一个参数传递,但它并没有在底部视图页脚上方呈现。 - Mark Lapasa

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