View 自定义 - 组合已有控件

一、概念

二、步骤

2.1 布局文件

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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    
    <TextView
        android:id="@+id/tvMinus"
        android:layout_width="80dp"
        android:layout_height="match_parent"
        android:textSize="18sp"
        android:gravity="center"
        android:text="-"
        />

    <EditText
        android:id="@+id/etValue"
        android:layout_width="80dp"
        android:layout_height="match_parent"
        android:focusable="false"
        android:gravity="center"
        android:text="0"
        android:textSize="14sp"
        />

    <TextView
        android:id="@+id/tvPlus"
        android:layout_width="80dp"
        android:layout_height="match_parent"
        android:textSize="18sp"
        android:gravity="center"
        android:text="+"
        />

</LinearLayout>

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 使用自定义控件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <com.jomurphys.demo.view.Counter
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        />

</LinearLayout>

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。

<resources>
    <declare-styleable name="Counter">
        <attr name="max" format="integer" />
        <attr name="min" format="integer" />
        <attr name="step" format="integer" />
        <attr name="disable" format="boolean" />
        <attr name="btnColor" format="color|reference" />
        <attr name="valueSize" format="dimension" />
    </declare-styleable>
</resources>

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会自动补全)。

<LinearLayout 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"
    >

    <com.jomurphys.demo.view.Counter
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        app:min="-10"
        app:max="10"
        app:enable="true"
        />

</LinearLayout>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值