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 = 0const val TWO_INDEX = 1const val THREE_INDEX = 2const 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 = 1Forientation = VERTICAL}gravity = Gravity.CENTERsetPadding(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)? = nullprivate var onItemReselectListener: ((Int) -> Unit)? = nullprivate var currentIndex: Int = ONE_INDEXinit {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.indexsetData(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 = falseprivate fun clickTabView(view: View) {if (lock) {return}lock = trueval selectIndex = view.tag as Intif (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? = nullprivate var currentIndex: Int? = nullfun 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 = indexcurrentFragment = fragment}fun getCurrentIndex() = currentIndexfun getCurrentFragment() = currentFragmentobject 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.FragmentContainerViewandroid:id="@+id/fragment_container"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1" /><com.example.widgets.navigationbar.NavigationBarandroid:id="@+id/navigation_bar"android:layout_width="match_parent"android:layout_height="wrap_content" />
</LinearLayout>
class NavigationBarActivity : BaseActivity() {private lateinit var navigationBar: NavigationBarprivate 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)}
}

源码下载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/72222.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

西门子S7-1200 PLC远程调试技术方案(巨控GRM532模块)

三步快速实现远程调试 硬件部署 准备西门子S7-1200 PLC、巨控GRM552YW-C模块及编程电脑。GRM552YW-C通过网口与PLC连接&#xff0c;支持4G/5G/Wi-Fi/有线网络接入&#xff0c;无需复杂布线。 软件配置 安装GVCOM3配置软件&#xff0c;注册模块&#xff08;输入唯一序列号与密…

上下文学习思维链COTPrompt工程

一、上下文学习 上下文学习强调在学习过程中考虑问题所处的上下文环境。 1.1 上下文学习的分类 零样本&#xff08;Zero-Shot&#xff09;上下文学习单样本&#xff08;One-Shot&#xff09;上下文学习少样本&#xff08;Few-Shot&#xff09;上下文学习 1.2 示例选择方法 …

node.js-WebScoket心跳机制(服务器定时发送数据,检测连接状态,重连)

1.WebScoket心跳机制是&#xff1f; 基于上一篇文章&#xff0c;WebScoket在浏览器和服务器间完成一次握手&#xff0c;两者间创建持久性连接&#xff0c;并进行双向数据连接。node.js-node.js作为服务器&#xff0c;前端使用WebSocket&#xff08;单个TCP连接上进行全双工通讯…

若依RuoYi-Cloud-Plus微服务版(完整版)前后端部署

一.目标 在浏览器上成功登录进入 二.源码下载 后端源码&#xff1a;前往Gitee下载页面(https://gitee.com/dromara/RuoYi-Cloud-Plus)下载解压到工作目录。 前端源码&#xff1a; 前往Gitee下载页面(https://gitee.com/JavaLionLi/plus-ui)下载解压到工作目录。 文档地址&a…

Nginx 多协议代理功能(Nginx Multi Protocol Proxy Function)

前言 Nginx 作为高性能的反向代理和负载均衡工具&#xff0c;广泛应用于 HTTP 和 HTTPS 协议的代理。但你知道吗&#xff1f;Nginx 还可以代理其他协议&#xff0c;比如 TCP 和 UDP&#xff01;这些功能让它在多协议支持方面表现出色&#xff0c;可以用于数据库代理、流媒体服…

MistralAI挑战DeepSeek:开源模型能否颠覆行业巨头

在2025年&#xff0c;世界移动通信大会的展台上&#xff0c;MistralAI的创始人ArthurMensch对着镜头&#xff0c;露出了温和的笑容。不过他随后讲出的话&#xff0c;就仿佛一颗重磅炸弹&#xff0c;在AI领域引发了巨大的动荡——他们即将推出的开源模型&#xff0c;据传能够超越…

代码随想录第五十二天| 101.孤岛的总面积 102.沉没孤岛 103.水流问题 104.建造最大岛屿

孤岛的总面积 题目描述 给定一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的矩阵&#xff0c;岛屿指的是由水平或垂直方向上相邻的陆地单元格组成的区域&#xff0c;且完全被水域单元格包围。孤岛是那些位于矩阵内部、所有单元格都不接触边缘的岛…

八叉树地图的原理与实现

八叉树与体素图 八叉树地图 八叉树地图是可变分辨率的三维栅格地图&#xff0c;可以自由调整分辨率&#xff0c;如下所示&#xff1a; 根据点云的数量或密度决定每个叶子方块是否被占据 体素图 体素就是固定分辨率的三维栅格地图&#xff0c;如下所示&#xff1a; 根据点云…

最节省服务器,手搓电子证书查询系统

用户预算150元&#xff0c;想要一个最简单证书查询系统。前台能查询证书、后台管理员能登录能修改密码&#xff0c;证书能够手动输入修改删除、批量导入导出删除数据、查询搜索。能够兼容苹果、安卓、PC三端浏览器&#xff0c;最后帮忙部署到云服务器上。 用户预算不多&#xf…

什么是全栈?

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点下班 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 &#x1f4c3;文章前言 &#x1f537;文章均为学习工…

作物移栽机器人的结构设计的介绍

作物移栽机器人的结构设计是一个复杂的机械与电子结合的系统工程&#xff0c;单纯用代码来实现整个结构设计是不现实的&#xff0c;因为结构设计更多涉及到机械结构、硬件选型等物理层面的内容。不过&#xff0c;我们可以通过代码来模拟作物移栽机器人的部分功能&#xff0c;例…

【文献阅读】SPRec:用自我博弈打破大语言模型推荐的“同质化”困境

&#x1f4dc;研究背景 在如今的信息洪流中&#xff0c;推荐系统已经成为了我们生活中的“贴心小助手”&#xff0c;无论是看电影、听音乐还是购物&#xff0c;推荐系统都在努力为我们提供个性化的内容。但这些看似贴心的推荐背后&#xff0c;其实隐藏着一个严重的问题——同质…

使用1Panel一键搭建WordPress网站的详细教程(全)

嘿&#xff0c;各位想搭建自己网站的朋友们&#xff01;今天我要跟大家分享我用1Panel搭建WordPress网站的全过程。说实话&#xff0c;我之前对服务器运维一窍不通&#xff0c;但通过这次尝试&#xff0c;我发现原来建站可以这么简单&#xff01;下面是我的亲身经历和一些小技巧…

本地fake server,

C# 制作的系统级tcp 重定向&#xff0c;整个系统只要有访问指定url&#xff0c;返回自定义内容到访问端。不局限在浏览器单一方面。 再者请理解这个图的含金量&#xff0c;服务器down机都可以模拟。 用途那就太多了&#xff0c;当然很多用途都不正当。嘿嘿 如果你很想要源代…

设计模式之美

UML建模 统一建模语言&#xff08;UML&#xff09;是用来设计软件的可视化建模语言。它的语言特点是简单 统一 图形化 能表达软件设计中的动态与静态信息。 UML的分类 动态结构图&#xff1a; 类图 对象图 组件图 部署图 动态行为图&#xff1a; 状态图 活动图 时序图 协作…

【openGauss】物理备份恢复

文章目录 1. gs_backup&#xff08;1&#xff09;备份&#xff08;2&#xff09;恢复&#xff08;3&#xff09;手动恢复的办法 2. gs_basebackup&#xff08;1&#xff09;备份&#xff08;2&#xff09;恢复① 伪造数据目录丢失② 恢复 3. gs_probackup&#xff08;1&#xf…

一文了解JVM的垃圾回收

Java堆内存结构 java堆内存是垃圾回收器管理的主要区域&#xff0c;也被称为GC堆。 为了方便垃圾回收&#xff0c;堆内存被分为新生代、老年代和永久代。 新创建的对象的内存会在新生代中分配&#xff0c;达到一定存活时长后会移入老年代&#xff0c;而永久代存储的是类的元数…

SQL子查询与MyBatis映射

文章目录 前言1. 数据库表结构2. MyBatis Mapper XML3. Java 实体类4. 技术点解析5. 执行效果6. 优化建议 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 以下是一个结合 SQL 别名、子查询、MyBatis 字段映射和代码复用的完整案例&#xff0c;以用户管…

基于SpringBoot的“校园周边美食探索及分享平台”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“校园周边美食探索及分享平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 校园周边美食探索及分享平台结构图…

时间复杂度(Time Complexity)

时间复杂度 1. 什么是时间复杂度&#xff1f; 时间复杂度&#xff08;Time Complexity&#xff09;是计算算法执行时间随输入规模&#xff08;n&#xff09;增长的变化趋势。它衡量算法的效率&#xff0c;通常使用大 O 记号&#xff08;Big-O notation&#xff09;表示&#…