• 手把手教你搭建android模块化项目框架(十二)——实现自定义view的一些小技巧~


    原来今天才是周六~那就今天水

    自定义view怎么实现,我今天不想多说,毕竟也不是给新人看的。

    那么今天直接讲一些实现自定义view的小技巧吧。

    本期举例的自定义view只是抛砖引玉,随手写的没有经过测试,如果想使用一定要三思而后行~

    1.利用databinding或者viewbinding,告别如下代码~

    animView = findViewById(R.id.anim_view)
    iconView = findViewById(R.id.iv_tab)
    textView = findViewById(R.id.tv_tab)
    badgeView = findViewById(R.id.iv_badge)
    
    • 1
    • 2
    • 3
    • 4

    那么我们直接看优化后的代码~

    private val mBinding by lazy {
        ViewMainBottomLayoutBinding.inflate(
            LayoutInflater.from(context),
            this,
            true
        )
    }
    
    mBinding.tvTab.xxxxxxxx
    //直接使用,节省时间。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 让你的自定义view支持style,方便使用

    首先看我们的自定义属性

    <declare-styleable name="BottomNavigationView">
        <attr name="iconWidth" format="dimension" />
        <attr name="iconHeight" format="dimension" />
        <attr name="textSize" format="dimension" />
        <attr name="textColor" format="color" />
    declare-styleable>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后定义默认style

    <style name="BottomNavigationViewStyle">
        "iconWidth">30dp
        "iconHeight">30dp
        "textColor">@color/color_bottom_nav_view_text_default
        "textSize">12sp
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在创建view的构造函数中填入默认style,然后其他与正常写自定义view就一样啦~

    constructor(context: Context, attrs: AttributeSet?) : super(
        context,
        attrs,
        R.style.BottomNavigationViewStyle
    ) {
        setupAttr(attrs)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果我们想动态加入主题呢?可以在自定义view中添加setTheme方法,然后取值方式如下,可能还有其他取值方式~不过懒得找了。

    fun setTheme(themeId: Int) {
        val mTheme = context.resources.newTheme()
        mTheme.applyStyle(themeId, true)
        mTheme.obtainStyledAttributes(
            intArrayOf(
                R.attr.iconWidth,
                R.attr.iconHeight,
                R.attr.textColor,
                R.attr.textSize
            )
        ).run {
            iconWidth =
                this.getDimensionPixelSize(this.getIndex(0), iconWidth)
            iconHeight =
                this.getDimensionPixelSize(this.getIndex(1), iconHeight)
            textColor =
                this.getColorStateList(this.getIndex(2)) ?: textColor
            textSize = this.getDimension(this.getIndex(3), textSize)
            recycle()
        }
        setup()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    如此,我们便可以直接配置style给自定义view啦~由于本demo使用的是组合view,所以我们可以在父view中接受自定义参数例如:

    <declare-styleable name="BottomNavigationGroup">
        <attr name="navBottomViewStyle" format="reference" />
    declare-styleable>
    
    • 1
    • 2
    • 3

    然后获取:

    context.obtainStyledAttributes(attrs, R.styleable.BottomNavigationGroup).run {
        navViewThemeId =
            getResourceId(R.styleable.BottomNavigationGroup_navBottomViewStyle, navViewThemeId)
        recycle()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    之后在Build子view时,将themeId传入即可~

    当然,写法有很多,本篇仅仅是抛砖引玉而已。

    1. dsl构建view参数

    先看效果~ 可以是这样的

    mBinding.homeTab.setup {
        options(
            bottomNavOption {
                id { R.id.home }
                tabText { "home" }
                iconRes { R.drawable.ic_main_nav_home }
            },
            bottomNavOption {
                id { R.id.topic }
                tabText { "topic" }
                iconRes { R.drawable.ic_main_nav_home }
            },
            bottomNavOption {
                id { R.id.find }
                tabText { "find" }
                iconRes { R.drawable.ic_main_nav_home }
            },
            bottomNavOption {
                id { R.id.me }
                tabText { "me" }
                iconRes { R.drawable.ic_main_nav_home }
            }
        )
        listener {
            object : BottomNavigationGroup.OnCheckedChangeListener {
                override fun onCheckedChanged(group: BottomNavigationGroup?, checkedId: Int) {
    
                }
    
            }
        }
        defaultChecked {
            R.id.home
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    也可以是这样的~

    mBinding.homeTab.setup {
        options(
            bottomNavOption {
                id { R.id.home }
                tabText { "home" }
                iconRes { R.drawable.ic_main_nav_home }
            })
        options(bottomNavOption {
            id { R.id.topic }
            tabText { "topic" }
            iconRes { R.drawable.ic_main_nav_home }
        })
        options(
            bottomNavOption {
                id { R.id.find }
                tabText { "find" }
                iconRes { R.drawable.ic_main_nav_home }
            })
        bottomNavOption {
            id { R.id.me }
            tabText { "me" }
            iconRes { R.drawable.ic_main_nav_home }
        }
        )
        listener {
            object : BottomNavigationGroup.OnCheckedChangeListener {
                override fun onCheckedChanged(group: BottomNavigationGroup?, checkedId: Int) {
    
                }
    
            }
        }
        defaultChecked {
            R.id.home
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    当然,写法有很多,本文最终提交的是第一种的写法~

    这个dsl看起来复杂,其实很简单,例如option构建时我们多写一些方法~

    class Option {
        @IdRes
        var id: Int = -1
            private set
        var tabText: String = ""
    
        @DrawableRes
        var iconRes: Int = 0
            private set
    
        var textColor: ColorStateList? = null
            private set
    
        var iconW: Int = 0
            private set
    
        var iconH: Int = 0
            private set
    
        var textSize: Float = 0f
            private set
    
        fun id(init: () -> Int) {
            id = init()
        }
    
        fun tabText(init: () -> String) {
            tabText = init()
        }
    
        fun iconRes(init: () -> Int) {
            iconRes = init()
        }
    
        fun textColor(init: () -> Int) {
            textColor = ResourceUtil.getColorStateList(resId = init())
        }
    
        fun iconW(init: () -> Int) {
            iconW = init()
        }
    
        fun iconH(init: () -> Int) {
            iconH = init()
        }
    
        fun textSize(init: () -> Float) {
            textSize = init()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    这样就可以使用高阶函数进行构建了,配合kotlin的lambda特性即可达到效果~

    当然,为了看起来更舒适,也少不了我们的扩展函数啦~

    fun bottomNavOption(init: BottomNavigationView.Option.() -> Unit): BottomNavigationView.Option {
        val option = BottomNavigationView.Option()
        option.init()
        return option
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    至此,我们便完成了一个优雅的自定义view

    完整代码

  • 相关阅读:
    使用python setup.py报错:Upload failed (403) / Upload failed (400)
    【疑难杂症】恢复挂载的nacos持久卷
    openh264解码数据流向分析
    计算机网络八股总结
    华为OD机考算法题:机器人活动区域
    倒计时 2 天!聚焦 Arm 性能提升,助力龙蜥生态落地应用
    HOOPS/MVO技术概述
    RPA流程调试:准确定位错误原因及位置
    微信小程序图片展示淡入淡出纯WXSS实现,无需使用消耗性能的动画引擎
    【算法合集】学习算法第五天(递归/回溯篇)
  • 原文地址:https://blog.csdn.net/flowerdanceX/article/details/132643439