自定义View之tab导航栏

293 阅读4分钟
最终效果图:

image-20211221175733639.png

要求实现的功能
  • 1、可自定义返回图标
  • 2、可自定义返回文本,文本大小以及文本颜色
  • 3、可自定义tab的文本内容、颜色、选中后的背景
实现思路
  • 采用组合ViewGroup 的方式,分别放置AppCompatImageView、AppCompatTextView、和LinearLayoutCompat,其中LinearLayoutCompat用于添加tab标签。
  • 定义三个属性类,维护图标、文本以及tab的属性样式。
  • 定义接口,回调图标以及tab的点击事件
源码分析:
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.appcompat.widget.AppCompatImageView
import androidx.appcompat.widget.AppCompatTextView
import androidx.appcompat.widget.LinearLayoutCompat
import androidx.constraintlayout.widget.ConstraintLayout
import com.iflytek.lib_view.R
import java.util.ArrayList

/**
 * Described by lxj on 2021/12/21 11:01
 * Description : 带tab的toolbar
 * 1、自定义属性控制显示样式以及显示的内容
 * 2、关于tab的显示,采用动态添加的方式
 **/
class ToolBarWithTabView : ConstraintLayout {
    val TAG = "ToolBarWithTab"
    /*********************** view 定义 ************************/
    private lateinit var iconView: AppCompatImageView  // 最左侧图标  2021/12/21
    private lateinit var titleView: AppCompatTextView // 左侧title  2021/12/21
    private lateinit var contentView: LinearLayoutCompat
    /*********************** 属性定义 -- 整体 ************************/
    private var bg = 0 // 背景  2021/12/21
    /*********************** 属性定义 -- 图标 ************************/
    private lateinit var icon:Icon
    /*********************** 属性定义 -- title ************************/
    private lateinit var title:Title
    /***********************  属性定义 -- tab内容  ************************/
    private lateinit var tabText:TabText
    // tab文本对象  2021/12/21
    val listOfTextView:MutableList<TextView>? = ArrayList<TextView>()
    // 点击事件处理  2021/12/21
    var viewClick: ViewClick? = null

    constructor(context: Context) : super(context) {
        init(null)
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init(attrs)
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        init(attrs)
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
        init(attrs)
    }

    // 初始化操作  2021/12/21
    private fun init(attrs: AttributeSet?) {
        LayoutInflater.from(context).inflate(R.layout.view_toolbarwithtab, this, true)
        initTypeArr(attrs)
        initView()
        layout()
        initEvents()
    }

    // 初始化属性  2021/12/21
    private fun initTypeArr(attrs: AttributeSet?) {
        val mtypeArray = context.obtainStyledAttributes(attrs, R.styleable.style_view_toolbarwithtab)
        // 整体背景  2021/12/22
        bg = mtypeArray.getResourceId(R.styleable.style_view_toolbarwithtab_bg, R.color.colorPrimary)
        // 左侧图标相关属性  2021/12/22
        icon = Icon()
        icon.iconWidth = mtypeArray.getDimensionPixelSize(R.styleable.style_view_toolbarwithtab_icon_width,icon.iconWidth)
        icon.iconMarginLeft = mtypeArray.getDimensionPixelSize(R.styleable.style_view_toolbarwithtab_icon_margin_left,icon.iconMarginLeft)
        icon.iconSrc = mtypeArray.getDrawable(R.styleable.style_view_toolbarwithtab_icon_src)

        // 左侧文本相关属性  2021/12/22
        title = Title()
        title.titleContent = mtypeArray.getString(R.styleable.style_view_toolbarwithtab_title_content).toString()
        title.titleMarginLeft = mtypeArray.getDimensionPixelSize(R.styleable.style_view_toolbarwithtab_title_margin_left,title.titleMarginLeft)
        title.titleColor = mtypeArray.getColor(R.styleable.style_view_toolbarwithtab_title_color,title.titleColor)
        title.titleSize = mtypeArray.getDimensionPixelSize(R.styleable.style_view_toolbarwithtab_title_size, title.titleSize.toInt()).toFloat()

        // 中间tab相关属性  2021/12/22
        tabText = TabText()
        tabText.tabTitle = mtypeArray.getString(R.styleable.style_view_toolbarwithtab_tab_title).toString()
        tabText.tabTextColor = mtypeArray.getColor(R.styleable.style_view_toolbarwithtab_tab_text_color,tabText.tabTextColor)
        tabText.tabMarignLeft = mtypeArray.getDimensionPixelSize(R.styleable.style_view_toolbarwithtab_tab_margin_left,tabText.tabMarignLeft)
        tabText.tabBgSeclected = mtypeArray.getResourceId(R.styleable.style_view_toolbarwithtab_tab_bg_selected,tabText.tabBgSeclected)
        tabText.tabBgNormal = mtypeArray.getResourceId(R.styleable.style_view_toolbarwithtab_tab_bg_normal,R.drawable.tab_normal_bg)
        tabText.tabTextSize = mtypeArray.getDimensionPixelSize(R.styleable.style_view_toolbarwithtab_tab_textsize,R.drawable.tab_normal_bg)
        tabText.tabTextHorizontalPadding = mtypeArray.getDimensionPixelSize(R.styleable.style_view_toolbarwithtab_tab_text_horizontal_padding,tabText.tabTextHorizontalPadding)
        tabText.tabTextVerticalPadding = mtypeArray.getDimensionPixelSize(R.styleable.style_view_toolbarwithtab_tab_text_vertical_padding,tabText.tabTextVerticalPadding)
        // 释放  2021/12/21
        mtypeArray.recycle()
    }

    // 初始化view  2021/12/21
    private fun initView() {
        iconView = findViewById(R.id.icon_imageview)
        titleView = findViewById(R.id.title_textview)
        contentView = findViewById(R.id.tabcontent_linearlayut)
    }

    // 修改属性  2021/12/21
    private fun layout(){
        // 大背景  2021/12/21
        setBackgroundResource(bg)
        // 图标部分  2021/12/21
        icon.iconSrc?.let {
            iconView.setImageDrawable(it)
        }
        val iconParams = iconView.layoutParams as LayoutParams
        iconParams.width = icon.iconWidth
        iconParams.height = icon.iconWidth
        iconParams.leftMargin = icon.iconMarginLeft
        iconView.layoutParams = iconParams

        // title部分  2021/12/21
        titleView.setText(title.titleContent)
        titleView.setTextColor(title.titleColor)
        titleView.setTextSize(title.titleSize)
        val titleMagin = titleView.layoutParams as LayoutParams
        titleMagin.leftMargin = title.titleMarginLeft

        // tab部分  2021/12/21
        contentView.removeAllViews()
        val listTab = tabText.tabTitle.split(",")
        listOfTextView?.clear()
        listTab.forEach {
            listOfTextView?.add(addTextView(it))
        }
        // 设置第一个选中  2021/12/21
        listOfTextView?.get(0)?.setBackgroundResource(tabText.tabBgSeclected)
    }

    // 添加顶部tab  2021/12/22
    fun addTextView(title: String):TextView{
        val textView = TextView(context)
        textView.setText(title)
        textView.setTextSize(tabText.tabTextSize.toFloat())
        textView.setTextColor(tabText.tabTextColor)
        textView.setBackgroundResource(tabText.tabBgNormal)
        textView.setPadding(tabText.tabTextHorizontalPadding,tabText.tabTextVerticalPadding,tabText.tabTextHorizontalPadding,tabText.tabTextVerticalPadding)
        textView.gravity = Gravity.CENTER_VERTICAL
        val tabTextViewParams = LinearLayoutCompat.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT)
        tabTextViewParams.leftMargin = tabText.tabMarignLeft.div(2)
        tabTextViewParams.rightMargin = tabText.tabMarignLeft.div(2)
        contentView.addView(textView,tabTextViewParams)
        return textView
    }

    // 事件初始化  2021/12/22
    fun initEvents(){
        listOfTextView?.let {
            it.forEachIndexed { index, textView ->
                textView.setOnClickListener {
                    eachTextView(call = ::resetTextViewBg)
                    it.setBackgroundResource(tabText.tabBgSeclected)
                    viewClick?.tabClicked(index)
                }
            }
        }

        iconView.setOnClickListener {
            viewClick?.back()
        }
    }

    // 遍历tab title辅助方法  2021/12/22
    fun eachTextView(call:(TextView) -> Unit){
        listOfTextView?.let {
            it.forEach { textView ->
                call(textView)
            }
        }
    }

    // 重置tab title背景  2021/12/22
    fun resetTextViewBg(textView: TextView){
        textView.setBackgroundResource(tabText.tabBgNormal)
    }


    // 左侧title和顶部tab点击事件  2021/12/22
    interface ViewClick{
        fun back()
        fun tabClicked(index:Int)
    }
}
属性类
import android.graphics.Color
import android.graphics.drawable.Drawable
import androidx.annotation.ColorInt

// 最左侧图标属性  2021/12/22
data class Icon(
    var iconWidth:Int = 40, // 图标大小  2021/12/21
    var iconMarginLeft:Int = 20, // 图标左侧的margin  2021/12/21
    var iconSrc: Drawable? = null // 图标资源  2021/12/21
)

// 最左侧文本辅助属性  2021/12/22
data class Title(
    var titleContent:String = "", // title内容  2021/12/21
    var titleMarginLeft:Int = 0, // title左边距 2021/12/21
    @ColorInt var titleColor:Int = Color.WHITE, // title颜色  2021/12/21
    var titleSize:Float = 20F // title大小  2021/12/21
)

// 顶部tab文本属性  2021/12/22
data class TabText(
    var tabTitle: String = "",  // tab 内容  2021/12/21
    var tabMarignLeft:Int = 0, // 左侧的margin距离  2021/12/21
    @ColorInt var tabTextColor:Int = 0, // 选中后文本背景的颜色  2021/12/21
    var tabBgSeclected:Int = 0, // 选中后文本背景的颜色  2021/12/21
    var tabBgNormal:Int = 0, // 文本常态背景颜色  2021/12/21
    var tabTextSize:Int = 0, // tab文字大小  2021/12/21
    var tabTextHorizontalPadding:Int = 20, // tab文本横向padding  2021/12/21
    var tabTextVerticalPadding:Int = 10 // tab文本纵向padding  2021/12/21
)

布局文件 view_toolbarwithtab.xml

<?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:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 最左侧icon Described by lxj on 2021/12/21 16:03 -->
    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/icon_imageview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

    <!-- 最左侧文本 Described by lxj on 2021/12/21 16:04 -->
    <androidx.appcompat.widget.AppCompatTextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:id="@+id/title_textview"
        app:layout_constraintLeft_toRightOf="@+id/icon_imageview"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:gravity="center"
        android:text="this is title"
        android:layout_marginLeft="20dp"/>

    <!-- tab文本 Described by lxj on 2021/12/21 16:04 -->
    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:id="@+id/tabcontent_linearlayut"
        android:orientation="horizontal"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:gravity="center">
    </androidx.appcompat.widget.LinearLayoutCompat>

</androidx.constraintlayout.widget.ConstraintLayout>
中间用到的tab背景 tab_normal_bg.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#00FFFFFF" />  <!-- #FF6200EE Described by lxj on 2021/7/15 19:52 -->
    <corners android:radius="10dp" />
</shape>
使用:
<com.iflytek.lib_view.view.ToolBarWithTab
    android:layout_marginTop="30dp"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:bg="@color/teal_700"
    app:icon_width="15dp"
    app:icon_margin_left="10dp"
    app:icon_src="@drawable/back_btn"
    app:title_content="返回"
    app:title_margin_left="20dp"
    app:title_color="@color/white"
    app:title_size="9sp"
    app:tab_title="Java,Kotlin"
    app:tab_margin_left="4dp"
    app:tab_text_color="@color/colorAccent"
    app:tab_bg_selected="@drawable/tab_selected_bg"
    app:tab_text_horizontal_padding="22dp"
    app:tab_text_vertical_padding="6dp"
    app:tab_textsize="9sp" />