当前位置: 首页 > news >正文

Android 自定义View之底部导航栏

文章目录

  • Android 自定义View之底部导航栏
    • 概述
    • 代码
      • 定义TabIndex
      • 定义Tab
      • 定义TabView
      • 定义NavigationBar
      • FragmentSwitchHelper管理类
      • 使用
    • 源码下载

Android 自定义View之底部导航栏

概述

封装一个通用的底部导航栏控件。

在这里插入图片描述

代码

定义TabIndex

@Retention(AnnotationRetention.SOURCE)
@IntDef(ONE_INDEX, TWO_INDEX, THREE_INDEX, FOUR_INDEX)
annotation class TabIndex {
    companion object {
        const val ONE_INDEX = 0
        const val TWO_INDEX = 1
        const val THREE_INDEX = 2
        const val FOUR_INDEX = 3
    }
}

定义Tab

data class Tab(
    @TabIndex val index: Int,
    val label: String,
    @DrawableRes val tabIconDefault: Int,
    @DrawableRes val tabIconSelected: Int,
    @ColorRes val tabTextColorDefault: Int = R.color.tab_unselect_color,
    @ColorRes val tabTextColorSelected: Int = R.color.tab_selected_color
)

定义TabView

class TabView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
    private val paddingVertical = context.resources.getDimension(R.dimen.tab_padding_vertical).toInt()
    private val iconSize = context.resources.getDimension(R.dimen.tab_icon_size).toInt()
    private val fontSize = context.resources.getDimension(R.dimen.tab_text_size)

    init {
        layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT).apply {
            weight = 1F
            orientation = VERTICAL
        }
        gravity = Gravity.CENTER
        setPadding(0, paddingVertical, 0, paddingVertical)
    }

    fun setData(tab: Tab) {
        removeAllViews()
        addViews(tab)
    }

    private fun addViews(tab: Tab) {
        addView(createIcon(tab.tabIconDefault, tab.tabIconSelected))
        addView(createText(tab.label, tab.tabTextColorDefault, tab.tabTextColorSelected))
    }

    private fun createIcon(
        @DrawableRes tabIconDefault: Int,
        @DrawableRes tabIconSelected: Int
    ): ImageView {
        val drawable = StateListDrawable().apply {
            addState(
                intArrayOf(android.R.attr.state_selected),
                ContextCompat.getDrawable(context, tabIconSelected)
            )
            addState(StateSet.NOTHING, ContextCompat.getDrawable(context, tabIconDefault))
        }
        return ImageView(context).apply {
            layoutParams = LayoutParams(iconSize, iconSize)
            setImageDrawable(drawable)
            isSelected = false
        }
    }

    private fun createText(
        text: String,
        @ColorRes textColorDefault: Int,
        @ColorRes textColorSelected: Int
    ): TextView {
        val states = arrayOf(
            intArrayOf(android.R.attr.state_selected),
            intArrayOf()
        )
        val colors = intArrayOf(
            ContextCompat.getColor(context, textColorSelected),
            ContextCompat.getColor(context, textColorDefault)
        )
        val colorStateList = ColorStateList(states, colors)
        return TextView(context).apply {
            layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
            setText(text)
            setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize)
            setTextColor(colorStateList)
            isSelected = false
        }
    }

    fun selected(isSelected: Boolean) {
        children.forEach {
            it.isSelected = isSelected
        }
    }
}

定义NavigationBar

class NavigationBar @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr), View.OnClickListener {
    private val lineView by lazy { createLine() }
    private val containerView by lazy { createContainer() }
    private var onItemSelectedListener: ((Int) -> Unit)? = null
    private var onItemReselectListener: ((Int) -> Unit)? = null
    private var currentIndex: Int = ONE_INDEX

    init {
        addView(containerView, 0)
        addView(lineView, 1)
    }

    fun setData(tabs: List<Tab>, defaultIndex: Int = ONE_INDEX) {
        containerView.removeAllViews()
        addViews(tabs)
        currentIndex = defaultIndex
        (containerView.getChildAt(currentIndex) as TabView).selected(true)
    }

    private fun addViews(tabs: List<Tab>) {
        tabs.let {
            for (tab in it) {
                containerView.addView(createTabView(tab))
            }
        }
    }

    private fun createTabView(tab: Tab): TabView {
        return TabView(context).apply {
            tag = tab.index
            setData(tab)
            setOnClickListener(this@NavigationBar)
        }
    }

    private fun createContainer(): LinearLayout {
        return LinearLayout(context).apply {
            layoutParams =
                LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
                    orientation = HORIZONTAL
                }
            gravity = Gravity.CENTER
        }
    }

    private fun createLine(): View {
        return View(context).apply {
            setBackgroundColor(ContextCompat.getColor(context, R.color.tab_line))
            layoutParams = FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT, 1.dp, Gravity.TOP
            )
        }
    }

    override fun onClick(view: View) {
        if (view is TabView) {
            clickTabView(view)
        }
    }

    private var lock = false
    private fun clickTabView(view: View) {
        if (lock) {
            return
        }
        lock = true
        val selectIndex = view.tag as Int
        if (selectIndex == currentIndex) {
            onItemReselectListener?.invoke(selectIndex)
        } else {
            (containerView.getChildAt(currentIndex) as TabView).selected(false)
            (containerView.getChildAt(selectIndex) as TabView).selected(true)
            onItemSelectedListener?.invoke(selectIndex)
            currentIndex = selectIndex
        }
        lock = false
    }

    fun setOnItemSelectedListener(onItemSelectedListener: ((Int) -> Unit)) {
        this.onItemSelectedListener = onItemSelectedListener
    }

    fun setOnItemReselectListener(onItemReselectListener: ((Int) -> Unit)) {
        this.onItemReselectListener = onItemReselectListener
    }

}

FragmentSwitchHelper管理类

class FragmentSwitchHelper(
    private val fragmentManager: FragmentManager,
    @IdRes private val containerId: Int
) {

    private var currentFragment: Fragment? = null
    private var currentIndex: Int? = null

    fun switchTo(@TabIndex index: Int) {
        if (currentIndex == index) {
            return
        }

        val transaction = fragmentManager.beginTransaction()
        currentFragment?.let {
            transaction.hide(it)
        }

        val fragment = Factory.getOrCreateFragment(index, fragmentManager)
        if (fragment.isAdded) {
            transaction.show(fragment)
        } else {
            transaction.add(
                containerId,
                fragment,
                index.toString()
            )
        }
        transaction.commit()
        currentIndex = index
        currentFragment = fragment
    }

    fun getCurrentIndex() = currentIndex

    fun getCurrentFragment() = currentFragment

    object Factory {
        fun getOrCreateFragment(@TabIndex index: Int, fragmentManager: FragmentManager) =
            when (index) {
                ONE_INDEX -> fragmentManager.findFragmentByTag(index.toString())
                    ?: SimpleFragment.newInstance("ONE")
                TWO_INDEX -> fragmentManager.findFragmentByTag(index.toString())
                    ?: SimpleFragment.newInstance("TWO")
                THREE_INDEX -> fragmentManager.findFragmentByTag(index.toString())
                    ?: SimpleFragment.newInstance("THREE")
                FOUR_INDEX -> fragmentManager.findFragmentByTag(index.toString())
                    ?: SimpleFragment.newInstance("FOUR")
                else -> throw IllegalStateException("非法参数")
            }
    }
}

使用

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".navigationbar.NavigationBarActivity">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <com.example.widgets.navigationbar.NavigationBar
        android:id="@+id/navigation_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>
class NavigationBarActivity : BaseActivity() {
    private lateinit var navigationBar: NavigationBar

    private val fragmentHelper by lazy {
        FragmentSwitchHelper(
            supportFragmentManager,
            R.id.fragment_container
        )
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_navigation_bar)
        navigationBar = findViewById(R.id.navigation_bar)

        val tabs = listOf<Tab>(
            Tab(
                ONE_INDEX,
                "ONE",
                R.drawable.tab_home_unselect,
                R.drawable.tab_home_selected
            ),
            Tab(
                TWO_INDEX,
                "TWO",
                R.drawable.tab_friends_unselect,
                R.drawable.tab_friends_selected
            ),
            Tab(
                THREE_INDEX,
                "THREE",
                R.drawable.tab_find_unselect,
                R.drawable.tab_find_selected
            ),
            Tab(
                FOUR_INDEX,
                "FOUR",
                R.drawable.tab_setting_unselect,
                R.drawable.tab_setting_selected
            )
        )
        navigationBar.setData(tabs)
        navigationBar.setOnItemSelectedListener {
            Log.e("TAG", "点击:$it")
            fragmentHelper.switchTo(it)
        }
        navigationBar.setOnItemReselectListener {
            Log.e("TAG", "重复点击:$it")
        }
        fragmentHelper.switchTo(ONE_INDEX)
    }
}

源码下载

相关文章:

  • LanceDB快速入门之基本操作与API一览
  • 基于VMware的Ubuntu22.04系统安装和配置以及解决Ubuntu共享文件夹无法实现的问题
  • 优化程序执行时间的核心方法与实战策略
  • 机器学习(吴恩达)
  • MinIO的预签名直传机制
  • UI自动化:seldom框架和Selenium
  • MySQL 优化方案
  • [笔记.AI]KAG(知识增强生成 Knowledge Augmented Generation)
  • 解析Doris编译脚本generated-source.sh的逻辑
  • python-53-分别使用flask和streamlit进行向量存储和检索的服务开发实战
  • 算法分享———进制转换通用算法
  • centos8.0系统部署zabbix6.0监控
  • 说说人工智能
  • FPGA 32 ,以太网TCP/IP四层模型:从MII到RGMII的深度解析( TCP/IP传输控制协议 )
  • postgresql链接详解
  • idea超级AI插件,让 AI 为 Java 工程师
  • 使用Nodejs基于DeepSeek加chromadb实现RAG检索增强生成 本地知识库
  • 【医院成本核算专题】8.大数据与医院成本核算的关联点:开启医疗成本管理新时代
  • 网编高级 day01
  • 第二章身份——一切从信念开始
  • 招商蛇口:今年前4个月销售额约498.34亿元
  • 看展览|2025影像上海艺博会:市场与当代媒介中的摄影
  • 经济日报整版聚焦“妈妈岗”:就业路越走越宽,有温度重实效
  • 习近平抵达莫斯科伏努科沃专机机场发表书面讲话(全文)
  • 重磅金融政策密集发布!一文梳理这场国新办发布会
  • 体坛联播|国米淘汰巴萨晋级欧冠决赛,申花击败梅州避免连败