如何在BottomNavigationView中设置指示器?

时间:2019-02-18 06:42:11

标签: android bottomnavigationview

我想在bottomNavigationView上设置指示器。 如何设置指示器?

enter image description here

我没有找到指标的任何例子。

 <android.support.design.widget.BottomNavigationView
            android:id="@+id/bottom_navigation_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:background="@drawable/bottom_navigation_bg"
            app:labelVisibilityMode="unlabeled"
            app:menu="@menu/bottom_menu_main"/>

4 个答案:

答案 0 :(得分:3)

我以前的答案提供了很大的灵活性,并且可以在选定状态之间实现良好的动画效果,但是涉及的代码量却相对较大。一个没有动画的简单解决方案是使用itemBackground的{​​{1}}属性:

BottomNavigationView

bottom_nav_tab_background

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_nav"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:itemBackground="@drawable/bottom_nav_tab_background"
        android:layout_gravity="bottom"
        app:menu="@menu/menu_bottom_nav" />

</LinearLayout>

答案 1 :(得分:0)

这是一种不好的做法,但是如果您希望这样做,可以采用Tab布局。

这是代码

<android.support.design.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">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    </android.support.design.widget.AppBarLayout>



        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <android.support.v4.view.ViewPager
                android:id="@+id/viewpager"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_above="@+id/tabs"
                />


            <android.support.design.widget.TabLayout
                android:id="@+id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                app:tabBackground="@color/colorPrimary"
                app:tabIndicatorColor="@android:color/white"
                app:tabMode="fixed"
                app:tabGravity="fill"/>


        </RelativeLayout>


     </android.support.design.widget.CoordinatorLayout>

答案 2 :(得分:0)

要使此工作正常进行,您需要扩展BottomNavigationView才能具有多个侦听器。最初的实现只允许一个,但是您需要另一个才能告诉指示器何时移动。

class CustomBottomNavigationView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : BottomNavigationView(context, attrs, defStyleAttr), OnNavigationItemSelectedListener {

    private val onNavigationItemSelectedListeners =
        mutableListOf<OnNavigationItemSelectedListener>()

    init {
        super.setOnNavigationItemSelectedListener(this)

        itemIconTintList = null
    }

    override fun setOnNavigationItemSelectedListener(listener: OnNavigationItemSelectedListener?) {
        if (listener != null) addOnNavigationItemSelectedListener(listener)
    }

    fun addOnNavigationItemSelectedListener(listener: OnNavigationItemSelectedListener) {
        onNavigationItemSelectedListeners.add(listener)
    }

    fun addOnNavigationItemSelectedListener(listener: (Int) -> Unit) {
        addOnNavigationItemSelectedListener(OnNavigationItemSelectedListener {
            for (i in 0 until menu.size()) if (menu.getItem(i) == it) listener(i)
            false
        })
    }

    override fun onNavigationItemSelected(item: MenuItem) = onNavigationItemSelectedListeners
        .map { it.onNavigationItemSelected(item) }
        .fold(false) { acc, it -> acc || it }
}

然后创建一个指标项:

class BottomNavigationViewIndicator @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    val size = 8f
    private val targetId: Int
    private var target: BottomNavigationMenuView? = null

    private var rect = Rect()
    //    val drawPaint = Paint()
    private val backgroundDrawable: Drawable

    private var index = 0
    private var animator: AnimatorSet? = null

    init {
        if (attrs == null) {
            targetId = NO_ID
            backgroundDrawable = ColorDrawable(Color.TRANSPARENT)
        } else {
            with(context.obtainStyledAttributes(attrs, BottomNavigationViewIndicator)) {
                targetId =
                    getResourceId(BottomNavigationViewIndicator_targetBottomNavigation, NO_ID)
                val clippableId =
                    getResourceId(BottomNavigationViewIndicator_clippableBackground, NO_ID)
                backgroundDrawable = if (clippableId != NO_ID) {
                    androidx.appcompat.content.res.AppCompatResources.getDrawable(
                        context,
                        clippableId
                    ) ?: ColorDrawable(Color.TRANSPARENT)
                } else {
                    ColorDrawable(
                        getColor(
                            BottomNavigationViewIndicator_clippableBackground,
                            Color.TRANSPARENT
                        )
                    )
                }
                recycle()
            }
        }
    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        if (targetId == NO_ID) return attachedError("invalid target id $targetId, did you set the app:targetBottomNavigation attribute?")
        val parentView =
            parent as? View ?: return attachedError("Impossible to find the view using $parent")
        val child = parentView.findViewById<View?>(targetId)
        if (child !is CustomBottomNavigationView) return attachedError("Invalid view $child, the app:targetBottomNavigation has to be n ListenableBottomNavigationView")
        for (i in 0 until child.childCount) {
            val subView = child.getChildAt(i)
            if (subView is BottomNavigationMenuView) target = subView
        }
        elevation = child.elevation
        child.addOnNavigationItemSelectedListener { updateRectByIndex(it, true) }
        post { updateRectByIndex(index, false) }
    }

    private fun attachedError(message: String) {
        Log.e("BNVIndicator", message)
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        target = null
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.clipRect(rect)

//        canvas.drawCircle(size,size,size, drawPaint)
        backgroundDrawable.draw(canvas)
    }

    private fun updateRectByIndex(index: Int, animated: Boolean) {
        this.index = index
        target?.apply {
            if (childCount < 1 || index >= childCount) return
            val reference = getChildAt(index)

            val start = reference.left + left
            val end = reference.right + left

            backgroundDrawable.setBounds(left, top, right, bottom)
            val newRect = Rect(start, 0, end, height)
            if (animated) startUpdateRectAnimation(newRect) else updateRect(newRect)
        }
    }

    private fun startUpdateRectAnimation(rect: Rect) {
        animator?.cancel()
        animator = AnimatorSet().also {
            it.playTogether(
                ofInt(this, "rectLeft", this.rect.left, rect.left),
                ofInt(this, "rectRight", this.rect.right, rect.right),
                ofInt(this, "rectTop", this.rect.top, rect.top),
                ofInt(this, "rectBottom", this.rect.bottom, rect.bottom)
            )
            it.interpolator = FastOutSlowInInterpolator()
            it.duration = resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
            it.start()
        }
    }

    private fun updateRect(rect: Rect) {
        this.rect = rect
        postInvalidate()
    }

    @Keep
    fun setRectLeft(left: Int) = updateRect(rect.apply { this.left = left })

    @Keep
    fun setRectRight(right: Int) = updateRect(rect.apply { this.right = right })

    @Keep
    fun setRectTop(top: Int) = updateRect(rect.apply { this.top = top })

    @Keep
    fun setRectBottom(bottom: Int) = updateRect(rect.apply { this.bottom = bottom })

}

将一些属性放入attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="BottomNavigationViewIndicator">
        <attr name="targetBottomNavigation" format="reference"/>
        <attr name="clippableBackground" format="reference|color"/>
    </declare-styleable>
</resources>

创建一个背景可绘制对象,它会随着指示器的移动而被剪切: my_orange_background.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="360"
    android:viewportHeight="4">
    <path
        android:fillType="nonZero"
        android:pathData="M0,0h359.899v3.933H0z">
        <aapt:attr name="android:fillColor">
            <gradient
                android:endX="360"
                android:endY="2"
                android:startX="0"
                android:startY="2"
                android:type="linear">
                <item
                    android:color="@color/orange"
                    android:offset="0" />
                <item
                    android:color="@color/orange"
                    android:offset="0.55" />
            </gradient>
        </aapt:attr>
    </path>
</vector>

创建活动/片段布局:

<?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">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintBottom_toTopOf="@+id/bottom_nav"
        app:layout_constraintTop_toTopOf="parent" />

    <com.example.app.view.CustomBottomNavigationView
        android:id="@+id/bottom_nav"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="?attr/colorSurface"
        app:labelVisibilityMode="unlabeled"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/menu_bottom_nav" />

    <com.example.app.view.BottomNavigationViewIndicator
        android:layout_width="0dp"
        android:layout_height="4dp"
        app:clippableBackground="@drawable/my_orange_background"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@+id/bottom_nav"
        app:targetBottomNavigation="@+id/bottom_nav" />

</androidx.constraintlayout.widget.ConstraintLayout>

来源:Medium article GitHub project

答案 3 :(得分:0)

我在这里鞭打了一个指标,以使其移动。

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

    private val indicator: View = View(context).apply {
        layoutParams = LayoutParams(0, 5)
        setBackgroundColor(ContextCompat.getColor(context, R.color.carecredit_green))
    }

    init {
        addView(indicator)
    }

    var animateIndicator = true

    override fun setOnNavigationItemSelectedListener(listener: OnNavigationItemSelectedListener?) {
        OnNavigationItemSelectedListener { selectedItem ->

            menu
                .children
                .first { item ->
                    item.itemId == selectedItem.itemId
                }
                .itemId
                .let {
                    findViewById<View>(it)
                }
                .let { view ->
                    this.post {
                        indicator.layoutParams = LayoutParams(view.width, 5)

                        if (animateIndicator) {
                            indicator
                                .animate()
                                .x(view.x)
                                .start()
                        } else {
                            indicator.x = view.x
                        }

                        indicator.y = view.y
                    }
                }

            listener?.onNavigationItemSelected(selectedItem) ?: false
        }.let {
            super.setOnNavigationItemSelectedListener(it)
        }
    }
}