如何将BottomAppBar和FAB与BottomNavigationView结合使用

39

我想在BottomNavigationView的顶部,使用带有锚定在BottomAppBar上的行为的FloatingActionButton

我想到了一个非常"hacky"的技巧,只需将BottomNavigationView放在BottomAppBar上方而不提供背景,从而使其透明。

这似乎一开始很有效,但我发现只有在触摸按钮的上半部分(即没有透明的BottomNavigationView)时,才能单击fab按钮。

截图

<androidx.constraintlayout.widget.ConstraintLayout 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">

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:layout_gravity="bottom"
        app:layout_constraintBottom_toBottomOf="parent">

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:clickable="true"
            android:focusable="true"
            app:layout_anchor="@id/bar" />

        <com.google.android.material.bottomappbar.BottomAppBar
            android:id="@+id/bar"
            android:layout_width="match_parent"
            android:layout_height="58dp"
            android:layout_gravity="bottom"
            android:backgroundTint="@color/colorPrimaryDark" />

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottomNavigation"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            app:itemIconTint="@android:color/darker_gray"
            app:itemTextColor="@android:color/white"
            app:labelVisibilityMode="labeled"
            app:menu="@menu/navigation" />

    </androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

有没有一种实现这个想法的方法,我可以完全点击 FloatingActionButton


为什么不在点击FAB图标时调用的同时,也在点击中心底部导航视图按钮时调用相同的函数呢? - hsm59
由于中间导航项是不可见的,这也是我考虑的一个可能的解决方案。FAB是可点击/可聚焦的,因此在完美的情况下,当在其下面单击按钮时,我不希望出现涟漪效果。 - Caspar Geerlings
这种hacky方法的原因是为了保持FAB在BottomAppBar中的行为(例如动画)。也许有人对我有一个有用的解决方案,否则我应该使用自己的按钮。 - Caspar Geerlings
你为什么不按照这里的指南 https://material.io/design/components/app-bars-bottom.html#anatomy 进行操作呢? - Zun
@ZUNJAE 是的,我想要一个包含图标和标题的导航菜单。 - Caspar Geerlings
显示剩余3条评论
6个回答

29

有一种更简单的方法来组合这3个小部件。我们可以这样做:

<androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/coordinator"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.fragment.app.FragmentContainerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="?attr/actionBarSize" />

    <com.google.android.material.bottomappbar.BottomAppBar
        android:id="@+id/bottomBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        app:backgroundTint="?attr/colorPrimary"
        app:contentInsetEnd="0dp"
        app:contentInsetStart="0dp"
        app:fabAlignmentMode="end">

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottomNavigation"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:backgroundTint="@android:color/transparent"
            app:elevation="0dp"
            android:layout_marginEnd="100dp"
            app:itemIconTint="@android:color/white"
            app:itemRippleColor="@android:color/white"
            app:itemTextColor="@android:color/white"
            app:menu="@menu/menu_activity_home_bottom_navigation" />

    </com.google.android.material.bottomappbar.BottomAppBar>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:src="@drawable/ic_add_white_24dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_anchor="@id/bottomBar" />


</androidx.coordinatorlayout.widget.CoordinatorLayout>

就是这样。

根据 FAB(悬浮操作按钮)的锚定位置(CENTER | END),菜单图标将放置在左侧或中间:

对于此示例,结果可能类似于: enter image description here

如果您认为有更好的方法,请随意编辑/评论,以便我可以修复帖子 :)


2
当FAB锚定在中心时,它无法工作。 - Shefchenko
2
@Shefchenko,使用居中模式可以正常工作。只需使用app:fabAlignmentMode="center"即可。 - Pavlo28
@Shefchenko 要在中间工作,只需像GrafOrlov所说的那样添加 app:fabAlignmentMode="center" 并且还要删除 android:layout_marginEnd="100dp" - lujop
这在 API 21 上似乎无法正常工作,在我运行最新版本的 Android 的手机上完美运行,但在 API 21 的模拟器上运行时,BottomAppBar 显示为两倍大小,而 BottomNavigationView 停留在顶部,就像它有一个 margin_bottom。 - Tiago Oliveira
你如何处理 FAB 操作,使其像底部导航栏上的其他项目一样? - kinsley kajiva

28

第一种方式

试试这个,你可以创建一个CustomBottomNavigationView

这是一个关于CustomBottomNavigationView的好文章。

如何在BottomNavigationView中绘制自定义形状,这是一篇不错的文章。

示例代码:

import android.content.Context;
import android.graphics.*;
import android.support.design.widget.BottomNavigationView;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;

public class CustomBottomNavigationView extends BottomNavigationView {

    private Path mPath;
    private Paint mPaint;

    /** the CURVE_CIRCLE_RADIUS represent the radius of the fab button */
    private final int CURVE_CIRCLE_RADIUS = 128 / 2;
    // the coordinates of the first curve
    private Point mFirstCurveStartPoint = new Point();
    private Point mFirstCurveEndPoint = new Point();
    private Point mFirstCurveControlPoint1 = new Point();
    private Point mFirstCurveControlPoint2 = new Point();

    //the coordinates of the second curve
    @SuppressWarnings("FieldCanBeLocal")
    private Point mSecondCurveStartPoint = new Point();
    private Point mSecondCurveEndPoint = new Point();
    private Point mSecondCurveControlPoint1 = new Point();
    private Point mSecondCurveControlPoint2 = new Point();
    private int mNavigationBarWidth;
    private int mNavigationBarHeight;

    public CustomBottomNavigationView(Context context) {
        super(context);
        init();
    }

    public CustomBottomNavigationView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CustomBottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPath = new Path();
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mPaint.setColor(ContextCompat.getColor(getContext(),R.color.colorAccent));
        setBackgroundColor(Color.TRANSPARENT);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // get width and height of navigation bar
        // Navigation bar bounds (width & height)
        mNavigationBarWidth = getWidth();
        mNavigationBarHeight = getHeight();
        // the coordinates (x,y) of the start point before curve
        mFirstCurveStartPoint.set((mNavigationBarWidth / 2) - (CURVE_CIRCLE_RADIUS * 2) - (CURVE_CIRCLE_RADIUS / 3), 0);
        // the coordinates (x,y) of the end point after curve
        mFirstCurveEndPoint.set(mNavigationBarWidth / 2, CURVE_CIRCLE_RADIUS + (CURVE_CIRCLE_RADIUS / 4));
        // same thing for the second curve
        mSecondCurveStartPoint = mFirstCurveEndPoint;
        mSecondCurveEndPoint.set((mNavigationBarWidth / 2) + (CURVE_CIRCLE_RADIUS * 2) + (CURVE_CIRCLE_RADIUS / 3), 0);

        // the coordinates (x,y)  of the 1st control point on a cubic curve
        mFirstCurveControlPoint1.set(mFirstCurveStartPoint.x + CURVE_CIRCLE_RADIUS + (CURVE_CIRCLE_RADIUS / 4), mFirstCurveStartPoint.y);
        // the coordinates (x,y)  of the 2nd control point on a cubic curve
        mFirstCurveControlPoint2.set(mFirstCurveEndPoint.x - (CURVE_CIRCLE_RADIUS * 2) + CURVE_CIRCLE_RADIUS, mFirstCurveEndPoint.y);

        mSecondCurveControlPoint1.set(mSecondCurveStartPoint.x + (CURVE_CIRCLE_RADIUS * 2) - CURVE_CIRCLE_RADIUS, mSecondCurveStartPoint.y);
        mSecondCurveControlPoint2.set(mSecondCurveEndPoint.x - (CURVE_CIRCLE_RADIUS + (CURVE_CIRCLE_RADIUS / 4)), mSecondCurveEndPoint.y);

        mPath.reset();
        mPath.moveTo(0, 0);
        mPath.lineTo(mFirstCurveStartPoint.x, mFirstCurveStartPoint.y);

        mPath.cubicTo(mFirstCurveControlPoint1.x, mFirstCurveControlPoint1.y,
                mFirstCurveControlPoint2.x, mFirstCurveControlPoint2.y,
                mFirstCurveEndPoint.x, mFirstCurveEndPoint.y);

        mPath.cubicTo(mSecondCurveControlPoint1.x, mSecondCurveControlPoint1.y,
                mSecondCurveControlPoint2.x, mSecondCurveControlPoint2.y,
                mSecondCurveEndPoint.x, mSecondCurveEndPoint.y);

        mPath.lineTo(mNavigationBarWidth, 0);
        mPath.lineTo(mNavigationBarWidth, mNavigationBarHeight);
        mPath.lineTo(0, mNavigationBarHeight);
        mPath.close();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawPath(mPath, mPaint);
    }
}

现在这样使用

<?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"
    android:id="@+id/coordinatorlayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerInParent="true"
        android:layout_marginBottom="30dp"
        android:clickable="true"
        android:focusable="true" />

    <neel.com.demo.CustomBottomNavigationView
        android:id="@+id/customBottomBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="@color/colorAccent"
        app:labelVisibilityMode="labeled" />


</RelativeLayout>

活动

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        CustomBottomNavigationView curvedBottomNavigationView = findViewById(R.id.customBottomBar);
        curvedBottomNavigationView.inflateMenu(R.menu.bottom_menu);
    }
}

输出

这里输入图片描述

第二种方法

    <androidx.coordinatorlayout.widget.CoordinatorLayout 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="120dp"
    android:layout_gravity="bottom">

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="true"
        android:focusable="true"
        app:layout_anchor="@id/bar" />

    <com.google.android.material.bottomappbar.BottomAppBar
        android:id="@+id/bar"
        android:layout_width="match_parent"
        android:layout_height="58dp"
        android:layout_gravity="bottom"
        android:backgroundTint="@color/colorPrimaryDark">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <TextView
                style="?android:attr/borderlessButtonStyle"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:background="?android:attr/selectableItemBackground"
                android:drawableTop="@drawable/ic_favorite"
                android:gravity="center"
                android:orientation="vertical"
                android:text="Personal"
                android:textColor="#FFFFFF">

            </TextView>

            <TextView
                style="?android:attr/borderlessButtonStyle"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:background="?android:attr/selectableItemBackground"
                android:drawableTop="@drawable/ic_favorite"
                android:gravity="center"
                android:orientation="vertical"
                android:text="Personal"
                android:textColor="#FFFFFF">

            </TextView>

            <TextView
                style="?android:attr/borderlessButtonStyle"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:background="?android:attr/selectableItemBackground"
                android:drawableTop="@drawable/ic_favorite"
                android:gravity="center"
                android:orientation="vertical"
                android:textColor="#FFFFFF"
                android:visibility="invisible">

            </TextView>

            <TextView
                style="?android:attr/borderlessButtonStyle"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:background="?android:attr/selectableItemBackground"
                android:drawableTop="@drawable/ic_favorite"
                android:gravity="center"
                android:orientation="vertical"
                android:text="Personal"
                android:textColor="#FFFFFF">

            </TextView>

            <TextView
                style="?android:attr/borderlessButtonStyle"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:background="?android:attr/selectableItemBackground"
                android:drawableTop="@drawable/ic_favorite"
                android:gravity="center"
                android:orientation="vertical"
                android:text="Personal"
                android:textColor="#FFFFFF">

            </TextView>

        </LinearLayout>

    </com.google.android.material.bottomappbar.BottomAppBar>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

输出

这是图片的描述


@CasparGeerlings 欢迎,如果需要任何帮助,请告诉我。 - AskNilesh
@NileshRathod 我发现"第二种方式"可能是我所期望的最接近的方法。再次非常感谢! - Caspar Geerlings
1
对于第二种解决方案,有人知道如何样式化TextView,以便在单击时显示为活动状态吗? - Dante Gulapa
嗨!第一个好使,但我无法打开您提供的链接。那么,在第一个曲线的情况下,如何调整大小?我的意思是,您如何知道fab的大小? - Sharif Rafid Ur Rahman
你如何处理 FAB 操作,使其像底部导航栏上的其他项目一样? - kinsley kajiva
显示剩余3条评论

8
我找到了一个更快的解决方案。我将bottomnavigationview包裹在frameLayout中,一切都按预期工作。你可以试试这个方法:
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.coordinatorlayout.widget.CoordinatorLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/lt_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:fitsSystemWindows="false">


    <ViewPager
        android:id="@+id/main_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/bottom_navigation"
        android:layout_alignParentStart="true"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <com.google.android.material.bottomappbar.BottomAppBar
        android:id="@+id/bottom_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:clickable="false"
        app:fabAlignmentMode="center" />


    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom">

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottom_navigation"
            style="@style/BottomNavigationStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:clickable="false"
            app:menu="@menu/bottom_menu" />


    </FrameLayout>


    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_anchor="@id/bottom_bar" />


</androidx.coordinatorlayout.widget.CoordinatorLayout>

非常完美,特别是在使用带有底部应用栏(BottomAppBar)的 FAB 时。之前花了几个小时来修复它,谢谢! - Bryan W
2
在 BottomNavigationStyle 中您定义了哪些样式?style="@style/BottomNavigationStyle" - Asad Mukhtar
你如何处理 FAB 操作,使其像底部导航栏上的其他项目一样? - kinsley kajiva

5

对于所有使用Nilesh Rathod 评论中提到的第二种方法,并且想要删除第一个文本视图前面的意外空格的人:

只需为BottomAppBar设置app:contentInsetStart =“0dp”


1

我尝试了几天,最终我做出了一个带有空的 BottomAppBar 的 FAB,在其上方叠加了一个透明背景的 BottomNavigationView

在我的情况下,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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">

    <com.google.android.material.bottomappbar.BottomAppBar
        android:id="@+id/bottom_app_bar"
        style="@style/Widget.MaterialComponents.BottomAppBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        app:backgroundTint="@color/colorGray"
        app:fabAlignmentMode="center" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom">

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/nav_view"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:layout_gravity="bottom"
            android:background="#80FFFFFF"
            android:icon="@drawable/bottom_nav_ic_assignment"
            app:itemIconTint="@color/bottom_nav_item_color"
            app:itemTextColor="@color/bottom_nav_item_color"
            app:labelVisibilityMode="selected"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:menu="@menu/bottom_nav_menu" />

    </FrameLayout>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/addFab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:focusable="true"
        android:onClick="onFabClicked"
        android:src="@drawable/ic_add_white"
        app:backgroundTint="@color/colorBlue"
        app:fabSize="auto"
        app:layout_anchor="@+id/bottom_app_bar"
        app:layout_anchorGravity="center|top" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

0

你可以将BottomAppBar视为工具栏,这意味着该类扩展了ViewGroup,因此您可以在BottomAppBar标记内添加相对布局、约束布局等。以下是代码片段,将在BottomAppBar中显示2个按钮。

<com.google.android.material.bottomappbar.BottomAppBar
        android:id="@+id/bar"
        style="@style/Widget.MaterialComponents.BottomAppBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        app:backgroundTint="@color/cardview_dark_background"
        app:fabAlignmentMode="center">

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

            <Button
                android:id="@+id/leftbutton"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_marginRight="32dp"
                android:text="LEFT"
                android:textColor="@android:color/white"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toLeftOf="@+id/rightbutton"
                app:layout_constraintTop_toTopOf="parent" />

            <Button
                android:id="@+id/rightbutton"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_marginLeft="32dp"
                android:layout_marginRight="16dp"
                android:text="RIGHT"
                android:textColor="@android:color/white"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toRightOf="@+id/leftbutton"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
        </androidx.constraintlayout.widget.ConstraintLayout>
    </com.google.android.material.bottomappbar.BottomAppBar>

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