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.pswp.cn/pingmian/72222.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/72222.shtml
英文地址,請注明出處:http://en.pswp.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;表示&#…