如何修改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
相关产品推荐
相关产品推荐

