View 自定义 - 组合已有控件

一、概念

二、步骤

2.1 布局文件

2.1.1 创建组合已有控件的布局文件


    
    

    

    

2.1.2 创建自定义类继承已有的容器布局类

class Counter : LinearLayout {
    //通过 this 调用到三参构造中进行统一处理
    constructor(context: Context?) : this(context, null)
    constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        //TODO...
    }
}

2.1.3 加载组合好的布局文件并初始化控件

private lateinit var tvPlus: TextView
private lateinit var tvMinus: TextView
private lateinit var etValue: EditText

//初始化控件
private fun initView(context: Context?) {
    if (context != null) {
        val view = LayoutInflater.from(context).inflate(R.layout.layout_counter, this)
        tvPlus = view.findViewById(R.id.tvPlus)
        tvMinus = view.findViewById(R.id.tvMinus)
        etValue = view.findViewById(R.id.etValue)
    }

    //简写
//        context?.let {
//            val view = LayoutInflater.from(context).inflate(R.layout.layout_counter, this).run {
//                tvPlus = findViewById(R.id.tvPlus)
//                tvMinus = findViewById(R.id.tvMinus)
//                etValue = findViewById(R.id.etValue)
//            }
//        }
}

2.1.4 使用自定义控件



    

2.2 自定义类

2.2.1 处理数据

private var mCounter = 0

//获取计数
fun getCounter(): Int = mCounter

//设置计数
fun setCounter(value: Int) {
    mCounter = value
    updateCounter()
}

//更新计数
private fun updateCounter() {
    etValue.setText(mCounter.toString())
}

2.2.2 处理事件

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

//处理点击事件
private fun handleEvents() {
    tvPlus.setOnClickListener {
        mCounter++
        updateCounter()
    }
    tvMinus.setOnClickListener {
        mCounter--
        updateCounter()
    }
}

2.2.3 定义功能接口将数据暴露给外部使用

private var mOnCounterChangeListener: OnCounterChangeListener? = null

//暴露接口
interface OnCounterChangeListener {
    fun onCounterChange(value: Int)
}

//设置计数变动监听器
fun setOnCounterChangeListener(listener: OnCounterChangeListener) {
    mOnCounterChangeListener = listener
}

private fun updateCounter() {
    //监听器不为null就调用回调方法
    mOnCounterChangeListener?.onCounterChange(mCounter)
}

2.3 自定义属性

2.3.1 定义attr属性资源文件

values文件夹右键→New→Values Resource File→命名attrs。


    
        
        
        
        
        
        
    

2.3.2 引入属性并暴露getter/setter

//属性(保持public因为要暴露getter/setter供UI中代码调用)
var min: Int = 0
var max: Int = 0
var step: Int = 0
var disable: Boolean = false
var defaultValue: Int = 0
    //调用setter的时候要赋值给mCounter并更新
    set(value) {
        field = value
        mCounter = value
        updateCounter()
    }

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

//初始化属性
private fun initAttrs(context: Context?, attrs: AttributeSet?) {
    if (context != null && attrs != null) {
        val attributes = context.obtainStyledAttributes(attrs, R.styleable.Counter)
        min= attributes.getInt(R.styleable.Counter_min, 0)
        max= attributes.getInt(R.styleable.Counter_max, 0)
        step= attributes.getInt(R.styleable.Counter_step, 0)
        disable = attributes.getBoolean(R.styleable.Counter_disable, false)
        defaultValue = attributes.getInt(R.styleable.Counter_disable, 0)
        mCounter = defaultValue    //获取了默认值就设置给mCounter,更新动作放在initView()中
        attributes.recycle()
    }

    //简写
//        context?.obtainStyledAttributes(attrs, R.styleable.Counter)?.run {
//            min= getInt(R.styleable.Counter_min, 0)
//            max= getInt(R.styleable.Counter_max, 0)
//            step = getInt(R.styleable.Counter_step, 0)
//            disable = getBoolean(R.styleable.Counter_disable, false)
//            defaultValue = getInt(R.styleable.Counter_disable, 0)
//            recycle()
//        }
}

2.3.3 运用属性

private fun initView(context: Context?) {
    if (context != null) {
        //...
        //将属性中设置的值更新到控件上
        updateCounter()
        tvPlus.isEnabled = mEnable
        tvMinus.isEnabled = mEnable
    }
}

private fun handleEvents() {
    tvPlus.setOnClickListener {
        //按下后就不是最小值,解除tvMinus按钮禁用
        tvMinus.isEnabled = true
        //步进不为1的时候,要考虑值溢出的情况
        mCounter += mStep
        //如果计数大于等于最大值就设为最大值,并禁用按钮
        if (mCounter >= mMax) {
            mCounter = mMax
            tvPlus.isEnabled = false
        }
        updateCounter()
    }
    tvMinus.setOnClickListener {
        tvPlus.isEnabled = true
        mCounter -= mStep
        if (mCounter <= mMin) {
            mCounter = mMin
            tvMinus.isEnabled = false
        }
        updateCounter()
    }
}

2.3.4 布局文件中使用自定义属性

根布局添加命名空间(只需要输入app,IDE会自动补全)。



    

你可能感兴趣的:(View,android)