如何在 BottomNavigationView 中设置指标? [复制]

Posted

技术标签:

【中文标题】如何在 BottomNavigationView 中设置指标? [复制]【英文标题】:How to set indicator in BottomNavigationView? [duplicate] 【发布时间】:2019-07-11 12:12:14 【问题描述】:

我想在底部导航视图上设置指示器。您可以在底部看到橙色线 如何设置指标?

我没有找到任何指标示例。

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

【问题讨论】:

@NileshRathod 他/她想要指示器(底部为 TabLayout),而不是徽章。 我没有问过徽章。我问了指标。 @PratikButani 对不起我的错 @Kvoid 我认为这是不好的做法,因为您会找到一些库或 Google 推荐的代码,它会让您更改图标颜色或文本颜色以识别当前选定的项目。即使您想做同样的事情,您也可以为此创建自定义视图。谢谢。 我不明白为什么这个问题已经结束,它指向了一个不相关的问题。有足够声誉的人必须重新提出这个问题。 【参考方案1】:

我之前的答案提供了很大的灵活性,并且在所选状态之间很好地动画,但涉及的代码量相对较大。没有动画的更简单的解决方案是使用BottomNavigationViewitemBackground 属性:

<?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_
    android:layout_
    android:orientation="vertical">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_
        android:layout_
        android:layout_weight="1" />

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

</LinearLayout>

bottom_nav_tab_background

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true">
        <layer-list>
            <item android:gravity="bottom">
                <shape android:shape="rectangle">
                    <size android: />
                    <solid android:color="?attr/colorPrimary" />
                </shape>
            </item>
        </layer-list>
    </item>
</selector>

【讨论】:

我的代码使用 app:itemIconTint="@drawable/bottom_nav_tab_background" 而不是 app:itemBackground="@drawable/bottom_nav_tab_background" 我不知道为什么【参考方案2】:

这是我为添加指示器和移动它而设计的。

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)
        
    

【讨论】:

这太酷了,但是第一次没有显示选中的项目指示符 也不适用于背压【参考方案3】:

要使其正常工作,您需要扩展 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:
    android:
    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_
    android:layout_>

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewpager"
        android:layout_
        android:layout_
        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_
        android:layout_
        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_
        android:layout_
        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 articleGitHub project

【讨论】:

你真的不需要多个,不过......你可以将监听器包装在另一个监听器中并调用它。【参考方案4】:

这是一种不好的做法,但如果您希望这样做,您可以采用 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_
    android:layout_>

    <android.support.design.widget.AppBarLayout
        android:layout_
        android:layout_
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

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

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



        <RelativeLayout
            android:layout_
            android:layout_>

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


            <android.support.design.widget.TabLayout
                android:id="@+id/tabs"
                android:layout_
                android:layout_
                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>

【讨论】:

究竟为什么它是“不好的做法”? 推荐不使用底部Tab布局的人数我们可以使用bottomNavigationView

以上是关于如何在 BottomNavigationView 中设置指标? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何在 BottomNavigationView 中动态隐藏菜单项?

如何禁用 BottomNavigationView 移位模式?

如何在 BottomNavigationView 中设置指标? [复制]

如何在没有 ActionBar 的 AndroidX 中设置 BottomNavigationView

如何通过 Xamarin MvvmCross 中的 BottomNavigationView 在视图模型之间导航

BottomNavigationView - 如何获取选定的菜单项?