撸一个kotlin DSL UI框架之二

接上之前的demo撸一个kotlin DSL UI框架

基础界面已经有了,数据源变化该如何更新UI呢?既然写的像声明式了,当然是期望数据源变化时,UI自动刷新。

我选择属性代理,下面开撸。

首先需要ObservableProperty实现类,观察数据变更。

typealias OnChange<T> = (T) -> Unit

class State<T>(value: T, private val onChange: OnChange<T>) : ObservableProperty<T>(value) {

    override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) {
        super.afterChange(property, oldValue, newValue)
        onChange.invoke(newValue)
    }
}

构造方法传入初始值,和回调接口。回调接口期望由对应的View初始化,用一个包装类包装一下State和回调接口Onchange

class ObserverField<T>(initValue: T) {
    private var onChange: OnChange<T>? = null

    var value by State(initValue) {
        onChange?.invoke(it)
    }

    fun setOnChangeListener(onChange: OnChange<T>) {
        this.onChange = onChange
    }
}

value是真正的数据源,通过by关键字将属性代理给代理类State,添加setonChangeListener方法,将数据变更回调给View

这里简单说下属性代理,ObservableProperty抽象类实现了ReadWriteProperty接口,重写了getValuesetValue,属性赋值即调用setValue,获取属性即调用getValueObservablePropertysetValue方法中判断属性是否发生变化,发生变化时回调afterChange方法,当然也可以重写beforeChange方法,在属性变更时做拦截处理。

public abstract class ObservableProperty<V>(initialValue: V) : ReadWriteProperty<Any?, V> {
    private var value = initialValue

    protected open fun beforeChange(property: KProperty<*>, oldValue: V, newValue: V): Boolean = true

    protected open fun afterChange(property: KProperty<*>, oldValue: V, newValue: V): Unit {}

    public override fun getValue(thisRef: Any?, property: KProperty<*>): V {
        return value
    }

    public override fun setValue(thisRef: Any?, property: KProperty<*>, value: V) {
        val oldValue = this.value
        if (!beforeChange(property, oldValue, value)) {
            return
        }
        this.value = value
        afterChange(property, oldValue, value)
    }
}

State继承ObservableProperty后自然就有了观察数据变更的能力

接下来改造ViewGroup.text方法,构造函数添加ObserverField参数,并且在TextView初始化时设置回调函数,数据发生变更时调用TextView.setText()方法。

inline fun ViewGroup.text(
    content: ObserverField<String>? = null,
    width: Int = 0,
    height: Int = 0,
    block: TextView.() -> Unit
) {
    TextView(this.context).apply {
        if (width != 0 && height != 0) {
            val params = ViewGroup.MarginLayoutParams(width, height)
            if (width != 0) params.width = width
            if (height != 0) params.height = height
            layoutParams = params
        }
        //tips
        content?.run {
            text = content.value
            setOnChangeListener{
                text = content.value
            }
        }
        block()
        addView(this)
    }
}

Activity中验证一下,初始化ObserverField传入text构造方法,然后在点击时修改ObserverField.value值,TextView文案自动更新。

class MainActivity : AppCompatActivity() {
    //数据源
    private val content = ObserverField("content")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        contentView {
            viewGroup<LinearLayout> {
                orientation = LinearLayout.VERTICAL
                gravity = Gravity.CENTER_HORIZONTAL
                view<TextView>(width = 500, height = 200) {
                    setBackgroundColor(resources.getColor(android.R.color.holo_orange_light))
                    setTextColor(resources.getColor(android.R.color.black))
                    gravity = Gravity.CENTER
                    text = "kotlin DSL"
                    setTopMargin(100)
                    //点击事件
                    onClick {
                        content.setState(content.value + "onClick")
                    }
                }

                text(content = content, width = 700, height = 200) {
                    gravity = Gravity.CENTER
                    setBackgroundColor(resources.getColor(android.R.color.holo_orange_light))
                    setTextColor(resources.getColor(android.R.color.black))
                    setTopMargin(200)
                }
            }
        }
    }
}

fun <T> ObserverField<T>.setState(newValue: T) {
    value = newValue
}

推荐阅读更多精彩内容