如何编写自定义链接



我想创建自定义链接来防止一些重复。

我有点不清楚如何定义它

我有很多:

someLiveData.observe(this) { objectWithTextAndVisibility->
textView.text = objectWithTextAndVisibility.text
textView.visibility = objectWithTextAndVisibility.visibility
}

我想写一些东西,看起来像下面

someLiveData.observe(this).bind(textView).on(text)

,它会做同样的事情有办法定义这个吗?

如果我正确理解了你的问题,像这样的通用结构可能会达到你想要做的:

infix fun <T> LiveData<T>.observe(owner: LifecycleOwner) = LiveDataHolder(this, owner)
class LiveDataHolder<T>(val liveData: LiveData<T>, val owner: LifecycleOwner)
infix fun <T, S> LiveDataHolder<T>.bind(subject: S) = LiveDataBinder(this, subject)
class LiveDataBinder<T, S>(val liveDataHolder: LiveDataHolder<T>, val subject: S)
infix fun <T : VisibilityCarrier> LiveDataBinder<T, TextView>.on(textSelector: (T) -> String) {
liveDataHolder.liveData.observe(liveDataHolder.owner) {
subject.text = textSelector(it)
subject.visibility = it.visibility
}
}
interface VisibilityCarrier {
val visibility: Int
}

这应该给你很好的灵活性,并防止大量的重复,但代价是增加了一些模糊的代码库,并没有传达它总是正确的。

通过实现新的LiveDataBinder扩展函数,如on函数,甚至可以为不同类型的对象和期望的接口添加额外的和不同的行为(而不仅仅是TextViewVisibilityCarrier的这种组合,即使保持on的名称),等等。

使用风格可以有很大的变化,就像下面的(考虑到Data实现了VisibilityCarrier接口,并提供了textString属性):

liveData.observe(this).bind(textView).on { it.text }
liveData.observe(this) bind textView on { it.text }
liveData.observe(this).bind(textView).on(Data::text)
liveData.observe(this) bind textView on Data::text

这个on实现还允许定义使用哪个属性作为文本和不同的类,如:

anotherLiveData.observe(this).bind(textView).on(AnotherData::someText)
anotherLiveData.observe(this).bind(textView).on(AnotherData::anotherText)

更新:在阅读评论中的解释后,我想我明白了,并相信这应该解决这一点(Data包含textvisibility属性):

infix fun <T : Data, S : View> LiveDataBinder<T, out S>.on(textProperty: KMutableProperty1<S, in String>) {
liveDataHolder.liveData.observe(liveDataHolder.owner) {
textProperty.set(subject, it.text)
subject.visibility = it.visibility
}
}

TextView和其他Java定义类的情况下,编译器会报错合成访问,这个问题可以像下面的代码片段那样解决(Kotlin视图没有这种解决方法应该很好):

var TextView.text_: CharSequence
get() = text
set(value) { text = value }

的用法如下:

liveData.observe(this).bind(textView).on(TextView::text_)
liveData.observe(this).bind(customView).on(CustomView::someText)

更新2:Tenfour04建议的更好的方法(谢谢)。以下操作将避免合成属性访问编译器错误:

infix fun <T : Data, S : View> LiveDataBinder<T, S>.on(textProperty: S.(String) -> Unit) {
liveDataHolder.liveData.observe(liveDataHolder.owner) {
subject.textProperty(it.text)
subject.visibility = it.visibility
}
}

的用法是这样的(不需要text_属性扩展):

liveData.observe(owner).bind(textView).on(TextView::setText)

看了你对另一个答案的评论后,我想我明白你想要做什么了。

如果我是正确的,你想使用构建器模式首先绑定设置的东西(setter),然后指定数据类型的getter/mapper来获得应用于该setter的子数据类型。

你可以设置一些中间类,像这样:

fun <T> LiveData<T>.observe(owner: LifecycleOwner) = BindableObserver<T>().also { observe(owner, it) }
class BindableObserver<D>: Observer<D> {
private var boundSetter: BoundSetter<D, *>? = null
fun <S> bind(setter: (S)->Unit) = BoundSetter<D, S>(setter).also { boundSetter = it }
override fun onChanged(t: D) {
boundSetter?.execute(t)
}
}
class BoundSetter<D, S>(private val setter: (S)->Unit) {
private var dataGetter: ((D)->S)? = null
fun on(getter: (D)->S) {
dataGetter = getter
}
fun execute(newValue: D) {
val subData = dataGetter?.invoke(newValue) ?: return
setter.invoke(subData)
}
}

你不能简单地传递一个TextView来绑定,因为Kotlin不知道要设置TextView的哪个属性,所以你使用属性语法(::)传递属性。不幸的是,TextView有一堆setText()重载,所以你必须指定输入类型以及。

用法语法如下:

someLiveData.observe(this)
.bind<String>(textView::setText)
.on(ObjectWithTextAndVisibility::text)

为了避免指定绑定TextView的哪个函数,您可以添加一个辅助函数:

fun <D> BindableObserver<D>.bind(textView: TextView) = bind<String>(textView::setText)

,然后用法会更接近你建议的:

someLiveData.observe(this)
.bind(textView)
.on(ObjectWithTextAndVisibility::text)

你也可以使用lambda语法:

someLiveData.observe(this)
.bind(textView)
.on { it.text }

相关内容

  • 没有找到相关文章