You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修改Bottom Navigation Bar实现弧形/凸起包裹中间主按钮

实现带中间凸起主按钮的Bottom Navigation Bar

嘿,这个需求我之前做过类似的,给你两个实用的方案,看你更倾向哪种:

方案一:自定义BottomNavigationView(完全自定义导航栏形态)

如果你想完全通过扩展BottomNavigationView来实现,核心是重写它的绘制逻辑和布局调整,让导航栏背景呈现弧形凸起,同时把中间按钮放在凸起位置。

步骤1:实现弧形背景Drawable

首先我们需要一个能画出中间凸起弧形的背景Drawable:

class CurvedBottomBgDrawable @JvmOverloads constructor(
    private val curveHeight: Float = 32f,
    private val bgColor: Int = Color.WHITE
) : Drawable() {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = bgColor
        style = Paint.Style.FILL
    }
    private val path = Path()

    override fun draw(canvas: Canvas) {
        val width = bounds.width().toFloat()
        val height = bounds.height().toFloat()

        path.reset()
        // 从左上角开始绘制路径
        path.moveTo(0f, 0f)
        // 直线到弧形左侧起点
        path.lineTo(width / 2 - curveHeight * 2, 0f)
        // 贝塞尔曲线绘制中间凸起:控制点在(width/2, -curveHeight),终点在弧形右侧起点
        path.quadTo(width / 2, -curveHeight, width / 2 + curveHeight * 2, 0f)
        // 直线到右上角
        path.lineTo(width, 0f)
        // 闭合路径到右下角、左下角
        path.lineTo(width, height)
        path.lineTo(0f, height)
        path.close()

        canvas.drawPath(path, paint)
    }

    override fun setAlpha(alpha: Int) {
        paint.alpha = alpha
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        paint.colorFilter = colorFilter
    }

    override fun getOpacity(): Int {
        return PixelFormat.TRANSLUCENT
    }
}

步骤2:扩展BottomNavigationView

接下来我们扩展BottomNavigationView,设置自定义背景,并调整中间按钮的位置,让它适配凸起:

class CurvedBottomNavigationView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = R.attr.bottomNavigationStyle
) : BottomNavigationView(context, attrs, defStyleAttr) {

    private val curveHeight = resources.getDimension(R.dimen.curve_height)

    init {
        // 设置自定义弧形背景
        background = CurvedBottomBgDrawable(curveHeight, ContextCompat.getColor(context, R.color.bottom_nav_bg))
        // 初始化时调整中间按钮位置
        adjustMiddleItemPosition()
    }

    private fun adjustMiddleItemPosition() {
        val menu = menu
        // 假设中间按钮是第3个item(索引为2,从0开始计数)
        if (menu.size() >= 3) {
            val middleItemView = getChildAt(0) as? BottomNavigationMenuView ?: return
            val itemView = middleItemView.getChildAt(2) as BottomNavigationItemView
            // 让中间按钮向上偏移,对齐弧形凸起
            val params = itemView.layoutParams as ViewGroup.MarginLayoutParams
            params.topMargin = (-curveHeight / 2).toInt()
            itemView.layoutParams = params
        }
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        // 布局变化时重新调整按钮位置,避免错位
        adjustMiddleItemPosition()
    }
}

步骤3:在布局中使用

<com.yourpackage.CurvedBottomNavigationView
    android:id="@+id/curvedBottomNav"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    app:menu="@menu/bottom_nav_menu" />

方案二:组合布局(更简单易维护)

如果你不想自定义View,用ConstraintLayout组合BottomNavigationView和FloatingActionButton,配合挖空背景也能实现类似效果,这种方式更快捷:

步骤1:创建挖空背景Drawable

我们给BottomNavigationView做一个中间挖空的背景,让FAB能嵌入进去:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 底层是带圆角的导航栏背景 -->
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/bottom_nav_bg" />
            <corners android:topLeftRadius="16dp" android:topRightRadius="16dp" />
        </shape>
    </item>
    <!-- 上层是圆形挖空,颜色和页面背景一致,模拟导航栏中间的凸起包裹效果 -->
    <item
        android:width="64dp"
        android:height="64dp"
        android:gravity="top|center_horizontal">
        <shape android:shape="oval">
            <solid android:color="@color/page_background" />
        </shape>
    </item>
</layer-list>

步骤2:布局中组合组件

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

    <!-- 页面其他内容 -->

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNav"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/bottom_nav_hollow_bg"
        app:menu="@menu/bottom_nav_menu"
        app:layout_constraintBottom_toBottomOf="parent" />

    <!-- 中间主操作按钮,嵌入导航栏的挖空位置 -->
    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/mainActionFab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_add"
        app:fabSize="normal"
        app:layout_constraintBottom_toBottomOf="@id/bottomNav"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@id/bottomNav" />

</androidx.constraintlayout.widget.ConstraintLayout>

两种方案各有优劣:方案一完全自定义导航栏,形态更统一;方案二不用写自定义View,开发更快,适合快速迭代。你可以根据自己的项目需求选择~

内容的提问来源于stack exchange,提问作者Kshitij Aggarwal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:43:15