Android 三种状态的Checkbox

CheckBox原本只有两种状态,选中和不选中,使用系统默认的state_checked就可以满足。但是多了一种状态,就得自定义state了

1. 在attrs.xml文件中添加state
<declare-styleable name="GpState">
    <attr name="gp_state_middle" format="boolean" />
</declare-styleable>
2. 在selector文件中使用state
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
    <item app:gp_state_middle="true" android:drawable="@drawable/middle"/>
    <item android:state_checked="true" android:drawable="@drawable/checked"/>
    <item android:drawable="@drawable/unchecked"/>
</selector>

注意这里需要添加命名空间xmlns:app="http://schemas.android.com/apk/res-auto"

如果需要在代码中获取drawable,比如使用iconfont,就得在代码中设置StateListDrawable

val stateListDrawable = StateListDrawable()
stateListDrawable.addState(states, middleDrawable)
stateListDrawable.addState(intArrayOf(android.R.attr.state_checked), checkDrawable)
stateListDrawable.addState(intArrayOf(), unCheckDrawable)
stateListDrawable.setBounds(0, 0, stateListDrawable.minimumWidth, stateListDrawable.minimumHeight)
setCompoundDrawables(stateListDrawable, null, null, null)
3.在代码中将自定义state合并到系统中,重写onCreateDrawableState()
override fun onCreateDrawableState(extraSpace: Int): IntArray {
    val drawableState = super.onCreateDrawableState(extraSpace + 1)
    if (getState() == null) {  //null表示中间状态
        View.mergeDrawableStates(drawableState, states)
    }
    return drawableState
}
4.当Checkbox状态改变时,需要刷新状态,调用refreshDrawableState()
5.重新梳理一遍调用流程

来自https://www.jianshu.com/p/b676be395df1
1. 自定义的setType方法实现了类似setEnabled方法的功能。当调动改方法时,通知自定义view更新drawable状态,即调用refreshDrawableState方法。
2. refreshDrawableState方法内部调用drawableStateChanged方法。
3. drawableStateChanged方法通过getDrawableState方法获取最新的drawable状态值。
4. getDrawableState方法调用自定义类内的onCreateDrawableState方法来实现drawable状态的变更(通过mergeDrawableStates方法)。
5. 通过新的drawable状态来更新view的背景。

6.完整代码
class ThreeStateCheckbox : AppCompatCheckBox {
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    private val states = intArrayOf(R.attr.gp_state_middle)
    private var isMiddle: Boolean = false
    private var mBroadcasting: Boolean = false
    private var onStateChangeListener: OnStateChangeListener? = null

    private val checkDrawable = ...
    private val unCheckDrawable = ...
    private val middleDrawable = ...

    init {
        val stateListDrawable = StateListDrawable()
        stateListDrawable.addState(states, middleDrawable)
        stateListDrawable.addState(intArrayOf(android.R.attr.state_checked), checkDrawable)
        stateListDrawable.addState(intArrayOf(), unCheckDrawable)
        stateListDrawable.setBounds(0, 0, stateListDrawable.minimumWidth, stateListDrawable.minimumHeight)
        setCompoundDrawables(stateListDrawable, null, null, null)
    }

    override fun onFinishInflate() {
        super.onFinishInflate()
//        buttonDrawable = null
        buttonDrawable = StateListDrawable()   //有些低版本手机还是会显示默认的框,用这个方式去掉
    }

    override fun toggle() {
        if (isMiddle) {
            isChecked = true
        } else {
            super.toggle()
        }
    }

    override fun setChecked(checked: Boolean) {
        val checkedChanged = isChecked != checked
        super.setChecked(checked)
        val wasMiddle = isMiddle
        setMiddleState(false, false)
        if (wasMiddle || checkedChanged) {
            notifyStateListener()
        }
    }

    /**
     * 设置中间状态
     */
    fun setMiddleState(indeterminate: Boolean) {
        setMiddleState(indeterminate, true)
    }

    private fun getState(): Boolean? {
        return if (isMiddle) null else isChecked
    }

    /**
     * 设置状态,null表示中间状态
     */
    fun setState(state: Boolean?) {
        if (state != null) {
            isChecked = state
        } else {
            setMiddleState(true)
        }
    }

    private fun setMiddleState(isMiddle: Boolean, notify: Boolean) {
        if (this.isMiddle != isMiddle) {
            this.isMiddle = isMiddle
            refreshDrawableState()
            if (notify) {
                notifyStateListener()
            }
        }
    }

    private fun notifyStateListener() {
        if (mBroadcasting) {
            return
        }

        mBroadcasting = true
        if (onStateChangeListener != null) {
            onStateChangeListener!!.onStateChanged(this, getState())
        }
        mBroadcasting = false
    }

    override fun onCreateDrawableState(extraSpace: Int): IntArray {
        val drawableState = super.onCreateDrawableState(extraSpace + 1)
        if (getState() == null) {
            View.mergeDrawableStates(drawableState, states)
        }
        return drawableState
    }

    fun setOnStateChangeListener(listener: OnStateChangeListener) {
        this.onStateChangeListener = listener
    }

    interface OnStateChangeListener {
        fun onStateChanged(checkbox: GpThreeStateCheckbox, newState: Boolean?)
    }
}
7.遇到的问题

在自定义state时,如果项目里是多个module,定义state的attrs.xml文件和使用state的selector文件不在同一个module,运行./gradlew build命名会报错:
java.util.concurrent.ExecutionException:com.android.builder.internal.aapt.v2.Aapt2Exception:Android resource linking failed
这是因为上面导入命名空间,默认是当前包名,而attrs.xml和selector文件不在同一个module中,包名不同,会导致文件找不到问题。解决办法就是将这两个文件放到一起,或者命名空间强制设置包名,但是这好像已经不推荐了

参考:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值