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

Posted

技术标签:

【中文标题】如何将 BottomAppBar + FAB 与 BottomNavigationView 结合使用【英文标题】:How to combine BottomAppBar + FAB with BottomNavigationView 【发布时间】:2019-05-20 17:32:11 【问题描述】:

我想使用FloatingActionButton,以及它锚定在BottomAppBar、BottomNavigationView顶部时的行为。

我想出了一个相当“hacky”的技巧,只需将 BottomNavigationView 放在 BottomAppBar 的顶部,而不提供背景,从而使其透明。

乍一看似乎效果很好,但我发现只有在触摸按钮的上半部分时才能点击 fab 按钮(所以顶部没有透明的BottomNavigationView)。

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_
    android:layout_>

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_
        android:layout_
        android:layout_gravity="bottom"
        app:layout_constraintBottom_toBottomOf="parent">

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

        <com.google.android.material.bottomappbar.BottomAppBar
            android:id="@+id/bar"
            android:layout_
            android:layout_
            android:layout_gravity="bottom"
            android:backgroundTint="@color/colorPrimaryDark" />

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottomNavigation"
            android:layout_
            android:layout_
            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 图标时调用的中心底部导航视图按钮时不调用相同的函数? 由于中间导航项是不可见的,这也是我想到的一个可能的解决方案。 FAB 是可点击/可聚焦的,因此在完美的场景中,我不希望在点击下方的按钮时产生涟漪效应。 BottomAppBar 只是一个专门的Toolbar。您是否考虑过尝试将自己的标签按钮直接添加到其中?似乎比覆盖BottomNavigationView 少一些hacky。 这种hacky方法的原因是将FAB的行为保留在BottomAppBar中。 (例如动画)。也许有人对我有一个有用的解决方案,否则我应该使用我自己的按钮。 不,我的意思是,保留BottomAppBar(连同它的FloatingActionButton)并向其添加按钮,而不是覆盖BottomNavigationView。它不应该影响 FAB。 【参考方案1】:

第一道

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

这是CustomBottomNavigationView的好文章

How I draw custom shapes in 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_
    android:layout_
    android:orientation="vertical">

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_
        android:layout_
        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_
        android:layout_
        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_
    android:layout_
    android:layout_gravity="bottom">

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

    <com.google.android.material.bottomappbar.BottomAppBar
        android:id="@+id/bar"
        android:layout_
        android:layout_
        android:layout_gravity="bottom"
        android:backgroundTint="@color/colorPrimaryDark">

        <LinearLayout
            android:layout_
            android:layout_
            android:orientation="horizontal">

            <TextView
                style="?android:attr/borderlessButtonStyle"
                android:layout_
                android:layout_
                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_
                android:layout_
                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_
                android:layout_
                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_
                android:layout_
                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_
                android:layout_
                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 欢迎让我知道是否需要任何帮助 @NileshRathod 我发现“第二种方式”可能是最接近我希望的方式。再次,非常感谢! 我尝试了它的第一种工作方式,我想在工厂的两边只放一个项目,我该怎么做 @NileshRathod 在第二种方法中,BottomAppBar 中的第一个 textView 之前留有一些空间,请您帮忙删除它吗? 对于第二种解决方案,任何人都知道如何设置 textview 的样式,以便在单击时显示为活动状态?【参考方案2】:

有一种更简单的方法可以组合这 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_
    android:layout_>

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

    <com.google.android.material.bottomappbar.BottomAppBar
        android:id="@+id/bottomBar"
        android:layout_
        android:layout_
        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_
            android:layout_
            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_
        android:layout_
        app:layout_anchor="@id/bottomBar" />


</androidx.coordinatorlayout.widget.CoordinatorLayout>

就是这样。

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

对于此示例,结果将类似于:

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

【讨论】:

FAB 锚定在 CENTER 时不起作用。 @Shefchenko 它适用于中心模式。只需使用app:fabAlignmentMode="center" @Shefchenko 要在中间工作,只需像 GrafOrlov 所说的那样添加 app:fabAlignmentMode="center" 并删除 android:layout_marginEnd="100dp" 这似乎不适用于 API 21,在我的手机上运行最新版本的 Android 运行完美,但在 API 21 上的模拟器中运行它会显示 BottomAppBar 两倍大并且 BottomNavigationView 保持不变固定在顶部,好像它有一个 margin_bottom 你如何像底部导航中的其他项目一样处理 FAB 操作?【参考方案3】:

我找到了一个更快的解决方案。我将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_
    android:layout_
    android:background="@color/white"
    android:fitsSystemWindows="false">


    <ViewPager
        android:id="@+id/main_pager"
        android:layout_
        android:layout_
        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_
        android:layout_
        android:layout_gravity="bottom"
        android:clickable="false"
        app:fabAlignmentMode="center" />


    <FrameLayout
        android:layout_
        android:layout_
        android:layout_gravity="bottom">

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


    </FrameLayout>


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


</androidx.coordinatorlayout.widget.CoordinatorLayout>

【讨论】:

完美运行,尤其是在使用 BottomAppBar 添加 FAB 时。过去几个小时一直在尝试解决此问题,谢谢! 你在BottomNavigationStyle中定义了什么样的样式 style="@style/BottomNavigationStyle" 你如何像底部导航中的其他项目一样处理 FAB 操作?【参考方案4】:

致所有使用Nilesh Rathod comment 的第二种方法并希望在第一次文本视图之前删除意外空格的人:

只需将app:contentInsetStart="0dp" 设置为BottomAppBar

【讨论】:

【参考方案5】:

我尝试了几天,最后我做了一个带有空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_
    android:layout_>

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

    <FrameLayout
        android:layout_
        android:layout_
        android:layout_gravity="bottom">

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/nav_view"
            android:layout_
            android:layout_
            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_
        android:layout_
        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>

【讨论】:

【参考方案6】:

你可以把BottomAppBar当作工具栏,这意味着,这个类扩展了ViewGroup,所以你可以在BottomAppBar标签内添加相对布局、约束布局等。这是在 BottomAppBar 中显示 2 个按钮的代码片段。

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

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_
            android:layout_>

            <Button
                android:id="@+id/leftbutton"
                android:layout_
                android:layout_
                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_
                android:layout_
                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>

【讨论】:

以上是关于如何将 BottomAppBar + FAB 与 BottomNavigationView 结合使用的主要内容,如果未能解决你的问题,请参考以下文章

右侧的 BottomAppBar 项

在 FAB 单击时导航到片段(导航架构组件)

使用 BottomAppBar 材料设计组件的片段过渡

如何使BottomNavigationView项目背景透明

如何将bottomAppBar放置在布局中

如何在android中实现BottomAppBar?