底部导航视图(Android)的自定义形状?

发布于 2025-02-03 01:04:50 字数 868 浏览 5 评论 0原文

如何使底部导航视图变为特定形状?

我想拥有以下形状的底部导航视图:

<com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/navigationBottomView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@drawable/bg_nav_bar"
        app:itemHorizontalTranslationEnabled="true"
        app:itemIconTint="@drawable/bottom_bar_selector"
        app:itemTextColor="@drawable/bottom_bar_selector"
        app:labelVisibilityMode="labeled"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/nav_menu"/>

任何帮助将不胜感激。谢谢!

How to make the bottom navigation view to a specific shape?

I'd like to have a bottom navigation view of this shape:

Shape of my bottom nav view

I have tried setting it as background of my bottom nav view as:

<com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/navigationBottomView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@drawable/bg_nav_bar"
        app:itemHorizontalTranslationEnabled="true"
        app:itemIconTint="@drawable/bottom_bar_selector"
        app:itemTextColor="@drawable/bottom_bar_selector"
        app:labelVisibilityMode="labeled"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/nav_menu"/>

But it doesn't seem to work.

Any help will be appreciated. Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

昔日梦未散 2025-02-10 01:04:50

bottomNavigationView默认情况下具有材料ShapeDrawable的背景,因此您可以通过定义自定义topedge edgetReatment shapeappearancemodel 更改其形状代码>在bottomNavigationView上方绘制半圆。要能够在bottom navigationView上绘制某些内容,您需要拥有一个具有以下属性的父:

android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="35dp"

XML示例将如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black">

    <RelativeLayout
        android:id="@+id/bottomNavigationViewParentRL"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:paddingTop="35dp"
        android:background="@android:color/transparent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottomNavigationView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:backgroundTint="@color/white"
            app:elevation="2dp"
            app:labelVisibilityMode="labeled"
            app:itemIconSize="25dp"
            app:itemIconTint="@color/item_icon_tint_selector"
            app:itemTextColor="@color/item_text_color_selector"
            app:menu="@menu/bottom_nav_menu" />

    </RelativeLayout>

    <fragment
        android:id="@+id/nav_host_fragment_activity_main"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@id/bottomNavigationViewParentRL"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />

</androidx.constraintlayout.widget.ConstraintLayout>

然后绘制以下形状:

val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottomNavigationView)
val materialShapeDrawable = bottomNavigationView.getBackground() as MaterialShapeDrawable
materialShapeDrawable.shapeAppearanceModel = materialShapeDrawable.shapeAppearanceModel
    .toBuilder()
    .setTopEdge(CutoutCircleEdgeTreatment(resources, 70.toFloat(), 10.toFloat()))
    .build()

where code> cutoutcircledgetTearmentedgetReatment的子类,在顶部绘制半圆,这是类似的代码从顶部到底部到底部:

class CutoutCircleEdgeTreatment(res: Resources, circleDiameterDp: Float, circleLeftRightOffsetDp: Float) : EdgeTreatment() {

    private val fabDiameter: Float
    private val offset: Float

    init {
        fabDiameter = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, circleDiameterDp, res.getDisplayMetrics())
        offset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, circleLeftRightOffsetDp, res.getDisplayMetrics())
    }

    override fun getEdgePath(length: Float, center: Float, interpolation: Float, shapePath: ShapePath) {
        if (fabDiameter == 0f) {
            // There is no cutout to draw.
            shapePath.lineTo(length, 0f)
            return
        }
        val fabMargin = 0f
        val cradleDiameter = fabMargin * 2 + fabDiameter
        val cradleRadius = cradleDiameter / 2f
        val roundedCornerRadius = 0f
        val roundedCornerOffset = interpolation * roundedCornerRadius
        val horizontalOffset = 0f
        val middle = center + horizontalOffset

        // The center offset of the cutout tweens between the vertical offset when attached, and the
        // cradleRadius as it becomes detached.
        val cradleVerticalOffset = 0f
        val verticalOffset =
            interpolation * cradleVerticalOffset + (1 - interpolation) * cradleRadius
        val verticalOffsetRatio = verticalOffset / cradleRadius
        if (verticalOffsetRatio >= 1.0f) {
            // Vertical offset is so high that there's no curve to draw in the edge, i.e., the fab is
            // actually above the edge so just draw a straight line.
            shapePath.lineTo(length, 0f)
            return  // Early exit.
        }

        // Calculate the path of the cutout by calculating the location of two adjacent circles. One
        // circle is for the rounded corner. If the rounded corner circle radius is 0 the corner will
        // not be rounded. The other circle is the cutout.

        // Calculate the X distance between the center of the two adjacent circles using pythagorean
        // theorem.
        val fabCornerSize = -1f
        val cornerSize = fabCornerSize * interpolation
        val arcOffset = 0f
        val distanceBetweenCenters = cradleRadius + roundedCornerOffset
        val distanceBetweenCentersSquared = distanceBetweenCenters * distanceBetweenCenters
        val distanceY = verticalOffset + roundedCornerOffset
        val distanceX =
            Math.sqrt((distanceBetweenCentersSquared - distanceY * distanceY).toDouble())
                .toFloat()

        // Calculate the x position of the rounded corner circles.
        val leftRoundedCornerCircleX = middle - distanceX
        val rightRoundedCornerCircleX = middle + distanceX

        // Calculate the arc between the center of the two circles.
        val cornerRadiusArcLength =
            Math.toDegrees(Math.atan((distanceX / distanceY).toDouble())).toFloat()
        val cutoutArcOffset = ARC_QUARTER - cornerRadiusArcLength + arcOffset

        // Draw the starting line up to the left rounded corner.
        shapePath.lineTo( /* x= */leftRoundedCornerCircleX, 0f)

        // Draw the arc for the left rounded corner circle. The bounding box is the area around the
        // circle's center which is at `(leftRoundedCornerCircleX, roundedCornerOffset)`.
        shapePath.addArc( /* left= */
            leftRoundedCornerCircleX - roundedCornerOffset, 0f,  /* right= */
            leftRoundedCornerCircleX + roundedCornerOffset,  /* bottom= */
            roundedCornerOffset * 2,  /* startAngle= */
            ANGLE_UP.toFloat(),  /* sweepAngle= */
            cornerRadiusArcLength
        )

        // Draw the cutout circle.
        shapePath.addArc( /* left= */
            middle - (cradleRadius + offset),  /* top= */
            -cradleRadius - verticalOffset,  /* right= */
            middle + (cradleRadius + offset),  /* bottom= */
            cradleRadius - verticalOffset,  /* startAngle= */
            ANGLE_LEFT - cutoutArcOffset,  /* sweepAngle= */
            cutoutArcOffset * 2 + ARC_HALF
        )

        // Draw an arc for the right rounded corner circle. The bounding box is the area around the
        // circle's center which is at `(rightRoundedCornerCircleX, roundedCornerOffset)`.
        shapePath.addArc( /* left= */
            rightRoundedCornerCircleX - roundedCornerOffset, 0f,  /* right= */
            rightRoundedCornerCircleX + roundedCornerOffset,  /* bottom= */
            roundedCornerOffset * 2,  /* startAngle= */
            ANGLE_UP - cornerRadiusArcLength,  /* sweepAngle= */
            cornerRadiusArcLength
        )

        // Draw the ending line after the right rounded corner.
        shapePath.lineTo( /* x= */length, 0f)
    }

    companion object {
        private const val ARC_QUARTER = 90
        private const val ARC_HALF = 180
        private const val ANGLE_UP = 270
        private const val ANGLE_LEFT = 180
    }
}

从上面的cutoutcircledgetReatment构造器您可以传递CircleDiameterDP,它是DP值中的圆直径(在上面的示例中,将其设置为70DP,因此parent relativeLayout它的paddingtop等于圆的半径为70/2 = 35dp),并且circleleftrightofferoffsetdp用于绘制DP值中的左/右偏置的圆圈。当然,您可以根据自己的需求进一步修改代码。

结果:

”

将半圆与片段托管重叠,

以使半圆与托管的片段重叠,您必须更改的顺序fragment:nav_host_fragment_activity_main带有relativelayout bottomnavigationViewParentrl在下面的示例中类似:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black">

    <fragment
        android:id="@+id/nav_host_fragment_activity_main"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@id/bottomNavigationViewParentRL"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />

    <RelativeLayout
        android:id="@+id/bottomNavigationViewParentRL"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:paddingTop="35dp"
        android:background="@android:color/transparent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottomNavigationView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:backgroundTint="@color/white"
            app:elevation="2dp"
            app:labelVisibilityMode="labeled"
            app:itemIconSize="25dp"
            app:itemIconTint="@color/item_icon_tint_selector"
            app:itemTextColor="@color/item_text_color_selector"
            app:menu="@menu/bottom_nav_menu" />

    </RelativeLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

在您的每个片段中也给出一个底部的底部边距,具有相同的高度,以便在SEMI的点开始,如下样品中的圆圈:

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent"
    tools:context=".ui.dashboard.DashboardFragment">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_green_dark"
        android:layout_marginBottom="55dp">

        <TextView
            android:id="@+id/text_dashboard"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="25dp"
            android:textAlignment="center"
            android:textColor="@color/black"
            android:text="This is dashboard Fragment"
            android:textSize="20sp"
            android:layout_alignParentBottom="true"/>

    </RelativeLayout>

</RelativeLayout>

结果:

”

cutoutcircledgetReatementreatment

class CutoutCircleEdgeTreatment(res: Resources, circleDiameterDp: Float, circleLeftRightOffsetDp: Float) : EdgeTreatment() {

    private val fabDiameter: Float
    private val offset: Float

    init {
        fabDiameter = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, circleDiameterDp, res.getDisplayMetrics())
        offset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, circleLeftRightOffsetDp, res.getDisplayMetrics())
    }

    override fun getEdgePath(length: Float, center: Float, interpolation: Float, shapePath: ShapePath) {
        if (fabDiameter == 0f) {
            // There is no cutout to draw.
            shapePath.lineTo(length, 0f)
            return
        }
        val fabMargin = 0f
        val cradleDiameter = fabMargin * 2 + fabDiameter
        val cradleRadius = cradleDiameter / 2f
        val roundedCornerRadius = 0f
        val roundedCornerOffset = interpolation * roundedCornerRadius
        val horizontalOffset = 0f
        val middle = center + horizontalOffset

        // The center offset of the cutout tweens between the vertical offset when attached, and the
        // cradleRadius as it becomes detached.
        val cradleVerticalOffset = 0f
        val verticalOffset =
            interpolation * cradleVerticalOffset + (1 - interpolation) * cradleRadius
        val verticalOffsetRatio = verticalOffset / cradleRadius
        if (verticalOffsetRatio >= 1.0f) {
            // Vertical offset is so high that there's no curve to draw in the edge, i.e., the fab is
            // actually above the edge so just draw a straight line.
            shapePath.lineTo(length, 0f)
            return  // Early exit.
        }

        // Calculate the path of the cutout by calculating the location of two adjacent circles. One
        // circle is for the rounded corner. If the rounded corner circle radius is 0 the corner will
        // not be rounded. The other circle is the cutout.

        // Calculate the X distance between the center of the two adjacent circles using pythagorean
        // theorem.
        val fabCornerSize = -1f
        val cornerSize = fabCornerSize * interpolation
        val arcOffset = 0f
        val distanceBetweenCenters = cradleRadius + roundedCornerOffset
        val distanceBetweenCentersSquared = distanceBetweenCenters * distanceBetweenCenters
        val distanceY = verticalOffset + roundedCornerOffset
        val distanceX =
            Math.sqrt((distanceBetweenCentersSquared - distanceY * distanceY).toDouble())
                .toFloat()

        // Calculate the x position of the rounded corner circles.
        val leftRoundedCornerCircleX = middle - distanceX
        val rightRoundedCornerCircleX = middle + distanceX

        // Calculate the arc between the center of the two circles.
        val cornerRadiusArcLength =
            Math.toDegrees(Math.atan((distanceX / distanceY).toDouble())).toFloat()
        val cutoutArcOffset = ARC_QUARTER - cornerRadiusArcLength + arcOffset

        // Draw the cutout circle.
        shapePath.addArc( /* left= */
            middle - (cradleRadius + offset),  /* top= */
            -cradleRadius - verticalOffset,  /* right= */
            middle + (cradleRadius + offset),  /* bottom= */
            (cradleRadius - verticalOffset) * 2,  /* startAngle= */
            ANGLE_LEFT + 20.0f,  /* sweepAngle= */
            ARC_HALF - 40.0f
        )
    }

    companion object {
        private const val ARC_QUARTER = 90
        private const val ARC_HALF = 180
        private const val ANGLE_UP = 270
        private const val ANGLE_LEFT = 180
    }
}

用法:

val materialShapeDrawable = bottomNavigationView.getBackground() as MaterialShapeDrawable
materialShapeDrawable.shapeAppearanceModel = materialShapeDrawable.shapeAppearanceModel
    .toBuilder()
    .setTopEdge(CutoutCircleEdgeTreatment(resources, 70.toFloat(), 20.toFloat()))
    .build()

The BottomNavigationView by default has a background of MaterialShapeDrawable so you can change its shape using the ShapeAppearanceModel by defining a custom TopEdge EdgeTreatment to draw the half-circle above the BottomNavigationView. To be able to draw something above the BottomNavigationView you need to have a parent which has the below attributes:

android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="35dp"

An Xml sample will be like the below:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black">

    <RelativeLayout
        android:id="@+id/bottomNavigationViewParentRL"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:paddingTop="35dp"
        android:background="@android:color/transparent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottomNavigationView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:backgroundTint="@color/white"
            app:elevation="2dp"
            app:labelVisibilityMode="labeled"
            app:itemIconSize="25dp"
            app:itemIconTint="@color/item_icon_tint_selector"
            app:itemTextColor="@color/item_text_color_selector"
            app:menu="@menu/bottom_nav_menu" />

    </RelativeLayout>

    <fragment
        android:id="@+id/nav_host_fragment_activity_main"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@id/bottomNavigationViewParentRL"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />

</androidx.constraintlayout.widget.ConstraintLayout>

Then draw the shape like the below:

val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottomNavigationView)
val materialShapeDrawable = bottomNavigationView.getBackground() as MaterialShapeDrawable
materialShapeDrawable.shapeAppearanceModel = materialShapeDrawable.shapeAppearanceModel
    .toBuilder()
    .setTopEdge(CutoutCircleEdgeTreatment(resources, 70.toFloat(), 10.toFloat()))
    .build()

where CutoutCircleEdgeTreatment is a subclass of EdgeTreatment to draw the half-circle at the top which is similar code like the build-in BottomAppBarTopEdgeTreatment class which draws a semi-circular cutout from the top edge to bottom:

class CutoutCircleEdgeTreatment(res: Resources, circleDiameterDp: Float, circleLeftRightOffsetDp: Float) : EdgeTreatment() {

    private val fabDiameter: Float
    private val offset: Float

    init {
        fabDiameter = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, circleDiameterDp, res.getDisplayMetrics())
        offset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, circleLeftRightOffsetDp, res.getDisplayMetrics())
    }

    override fun getEdgePath(length: Float, center: Float, interpolation: Float, shapePath: ShapePath) {
        if (fabDiameter == 0f) {
            // There is no cutout to draw.
            shapePath.lineTo(length, 0f)
            return
        }
        val fabMargin = 0f
        val cradleDiameter = fabMargin * 2 + fabDiameter
        val cradleRadius = cradleDiameter / 2f
        val roundedCornerRadius = 0f
        val roundedCornerOffset = interpolation * roundedCornerRadius
        val horizontalOffset = 0f
        val middle = center + horizontalOffset

        // The center offset of the cutout tweens between the vertical offset when attached, and the
        // cradleRadius as it becomes detached.
        val cradleVerticalOffset = 0f
        val verticalOffset =
            interpolation * cradleVerticalOffset + (1 - interpolation) * cradleRadius
        val verticalOffsetRatio = verticalOffset / cradleRadius
        if (verticalOffsetRatio >= 1.0f) {
            // Vertical offset is so high that there's no curve to draw in the edge, i.e., the fab is
            // actually above the edge so just draw a straight line.
            shapePath.lineTo(length, 0f)
            return  // Early exit.
        }

        // Calculate the path of the cutout by calculating the location of two adjacent circles. One
        // circle is for the rounded corner. If the rounded corner circle radius is 0 the corner will
        // not be rounded. The other circle is the cutout.

        // Calculate the X distance between the center of the two adjacent circles using pythagorean
        // theorem.
        val fabCornerSize = -1f
        val cornerSize = fabCornerSize * interpolation
        val arcOffset = 0f
        val distanceBetweenCenters = cradleRadius + roundedCornerOffset
        val distanceBetweenCentersSquared = distanceBetweenCenters * distanceBetweenCenters
        val distanceY = verticalOffset + roundedCornerOffset
        val distanceX =
            Math.sqrt((distanceBetweenCentersSquared - distanceY * distanceY).toDouble())
                .toFloat()

        // Calculate the x position of the rounded corner circles.
        val leftRoundedCornerCircleX = middle - distanceX
        val rightRoundedCornerCircleX = middle + distanceX

        // Calculate the arc between the center of the two circles.
        val cornerRadiusArcLength =
            Math.toDegrees(Math.atan((distanceX / distanceY).toDouble())).toFloat()
        val cutoutArcOffset = ARC_QUARTER - cornerRadiusArcLength + arcOffset

        // Draw the starting line up to the left rounded corner.
        shapePath.lineTo( /* x= */leftRoundedCornerCircleX, 0f)

        // Draw the arc for the left rounded corner circle. The bounding box is the area around the
        // circle's center which is at `(leftRoundedCornerCircleX, roundedCornerOffset)`.
        shapePath.addArc( /* left= */
            leftRoundedCornerCircleX - roundedCornerOffset, 0f,  /* right= */
            leftRoundedCornerCircleX + roundedCornerOffset,  /* bottom= */
            roundedCornerOffset * 2,  /* startAngle= */
            ANGLE_UP.toFloat(),  /* sweepAngle= */
            cornerRadiusArcLength
        )

        // Draw the cutout circle.
        shapePath.addArc( /* left= */
            middle - (cradleRadius + offset),  /* top= */
            -cradleRadius - verticalOffset,  /* right= */
            middle + (cradleRadius + offset),  /* bottom= */
            cradleRadius - verticalOffset,  /* startAngle= */
            ANGLE_LEFT - cutoutArcOffset,  /* sweepAngle= */
            cutoutArcOffset * 2 + ARC_HALF
        )

        // Draw an arc for the right rounded corner circle. The bounding box is the area around the
        // circle's center which is at `(rightRoundedCornerCircleX, roundedCornerOffset)`.
        shapePath.addArc( /* left= */
            rightRoundedCornerCircleX - roundedCornerOffset, 0f,  /* right= */
            rightRoundedCornerCircleX + roundedCornerOffset,  /* bottom= */
            roundedCornerOffset * 2,  /* startAngle= */
            ANGLE_UP - cornerRadiusArcLength,  /* sweepAngle= */
            cornerRadiusArcLength
        )

        // Draw the ending line after the right rounded corner.
        shapePath.lineTo( /* x= */length, 0f)
    }

    companion object {
        private const val ARC_QUARTER = 90
        private const val ARC_HALF = 180
        private const val ANGLE_UP = 270
        private const val ANGLE_LEFT = 180
    }
}

From the above CutoutCircleEdgeTreatment constructor you can pass the circleDiameterDp which is the circle diameter in dp value (in the above example is set to 70dp so the parent RelativeLayout it should have paddingTop equal to the radius of the Circle which is 70/2 = 35dp) and the circleLeftRightOffsetDp is used to draw the circle with a left/right offset in dp value. Of Course you can modify further the code based on your needs.

Result:

navigation_bar

To overlap the semi circle with the fragment hosted

To make the semi circle overlap with the fragment hosted you have to change the order of fragment:nav_host_fragment_activity_main with the RelativeLayout bottomNavigationViewParentRL like in the below sample:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black">

    <fragment
        android:id="@+id/nav_host_fragment_activity_main"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@id/bottomNavigationViewParentRL"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />

    <RelativeLayout
        android:id="@+id/bottomNavigationViewParentRL"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:paddingTop="35dp"
        android:background="@android:color/transparent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottomNavigationView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:backgroundTint="@color/white"
            app:elevation="2dp"
            app:labelVisibilityMode="labeled"
            app:itemIconSize="25dp"
            app:itemIconTint="@color/item_icon_tint_selector"
            app:itemTextColor="@color/item_text_color_selector"
            app:menu="@menu/bottom_nav_menu" />

    </RelativeLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

And also give in each of your fragments some bottom margin with the same height of the navigation bar to start at the point of semi circle like in the below sample:

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent"
    tools:context=".ui.dashboard.DashboardFragment">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_green_dark"
        android:layout_marginBottom="55dp">

        <TextView
            android:id="@+id/text_dashboard"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="25dp"
            android:textAlignment="center"
            android:textColor="@color/black"
            android:text="This is dashboard Fragment"
            android:textSize="20sp"
            android:layout_alignParentBottom="true"/>

    </RelativeLayout>

</RelativeLayout>

Result:

overlap_navigation_bar

Another variation of CutoutCircleEdgeTreatment

class CutoutCircleEdgeTreatment(res: Resources, circleDiameterDp: Float, circleLeftRightOffsetDp: Float) : EdgeTreatment() {

    private val fabDiameter: Float
    private val offset: Float

    init {
        fabDiameter = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, circleDiameterDp, res.getDisplayMetrics())
        offset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, circleLeftRightOffsetDp, res.getDisplayMetrics())
    }

    override fun getEdgePath(length: Float, center: Float, interpolation: Float, shapePath: ShapePath) {
        if (fabDiameter == 0f) {
            // There is no cutout to draw.
            shapePath.lineTo(length, 0f)
            return
        }
        val fabMargin = 0f
        val cradleDiameter = fabMargin * 2 + fabDiameter
        val cradleRadius = cradleDiameter / 2f
        val roundedCornerRadius = 0f
        val roundedCornerOffset = interpolation * roundedCornerRadius
        val horizontalOffset = 0f
        val middle = center + horizontalOffset

        // The center offset of the cutout tweens between the vertical offset when attached, and the
        // cradleRadius as it becomes detached.
        val cradleVerticalOffset = 0f
        val verticalOffset =
            interpolation * cradleVerticalOffset + (1 - interpolation) * cradleRadius
        val verticalOffsetRatio = verticalOffset / cradleRadius
        if (verticalOffsetRatio >= 1.0f) {
            // Vertical offset is so high that there's no curve to draw in the edge, i.e., the fab is
            // actually above the edge so just draw a straight line.
            shapePath.lineTo(length, 0f)
            return  // Early exit.
        }

        // Calculate the path of the cutout by calculating the location of two adjacent circles. One
        // circle is for the rounded corner. If the rounded corner circle radius is 0 the corner will
        // not be rounded. The other circle is the cutout.

        // Calculate the X distance between the center of the two adjacent circles using pythagorean
        // theorem.
        val fabCornerSize = -1f
        val cornerSize = fabCornerSize * interpolation
        val arcOffset = 0f
        val distanceBetweenCenters = cradleRadius + roundedCornerOffset
        val distanceBetweenCentersSquared = distanceBetweenCenters * distanceBetweenCenters
        val distanceY = verticalOffset + roundedCornerOffset
        val distanceX =
            Math.sqrt((distanceBetweenCentersSquared - distanceY * distanceY).toDouble())
                .toFloat()

        // Calculate the x position of the rounded corner circles.
        val leftRoundedCornerCircleX = middle - distanceX
        val rightRoundedCornerCircleX = middle + distanceX

        // Calculate the arc between the center of the two circles.
        val cornerRadiusArcLength =
            Math.toDegrees(Math.atan((distanceX / distanceY).toDouble())).toFloat()
        val cutoutArcOffset = ARC_QUARTER - cornerRadiusArcLength + arcOffset

        // Draw the cutout circle.
        shapePath.addArc( /* left= */
            middle - (cradleRadius + offset),  /* top= */
            -cradleRadius - verticalOffset,  /* right= */
            middle + (cradleRadius + offset),  /* bottom= */
            (cradleRadius - verticalOffset) * 2,  /* startAngle= */
            ANGLE_LEFT + 20.0f,  /* sweepAngle= */
            ARC_HALF - 40.0f
        )
    }

    companion object {
        private const val ARC_QUARTER = 90
        private const val ARC_HALF = 180
        private const val ANGLE_UP = 270
        private const val ANGLE_LEFT = 180
    }
}

Usage:

val materialShapeDrawable = bottomNavigationView.getBackground() as MaterialShapeDrawable
materialShapeDrawable.shapeAppearanceModel = materialShapeDrawable.shapeAppearanceModel
    .toBuilder()
    .setTopEdge(CutoutCircleEdgeTreatment(resources, 70.toFloat(), 20.toFloat()))
    .build()

Result:

navigation_bar_change_v2

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文