Data Binding

Posted by アライさん on 2019年11月12日

data binding xml中无须做null判断,自带

module的build.gradle

1
2
3
4
5
android {
dataBinding {
enabled true
}
}

xml

自动创建:在xml文件的根节点点击,就会出现灯泡,点击灯泡可选“Convert to data binding layout”
手动创建:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewmodel"
type="io.github.araisann.navigationtest.WordViewModel" />
</data>
<!--界面布局部分-->
</layout>

xml中调用:

1
2
android:text="@{@string/fragment0 + viewmodel.allWords.size()}"
android:onClick="@{(v) -> viewmodel.jumpFragment2(v)}"

Fragment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Fragment0 : Fragment() {
lateinit var wordViewModel: WordViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
wordViewModel = ViewModelProviders.of(activity!!).get(WordViewModel::class.java)
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val fragmentTodayBinding = DataBindingUtil.inflate(
inflater,
R.layout.fragment0,
container,
false
) as Fragment0Binding

val view = fragmentTodayBinding.root
fragmentTodayBinding.viewmodel = wordViewModel
return view
}

}

ViewModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class WordViewModel : ViewModel() {
val allWords: MutableLiveData<List<Word>>

init {
val list = ArrayList<Word>()
list.add(Word("DEFAULT"))
list.add(Word("DEFAULT"))
list.add(Word("DEFAULT"))
list.add(Word("DEFAULT"))
allWords = MutableLiveData()
allWords.value = list
}

fun modify(position: Int, wordStr: String) {
if (position > allWords.value?.size ?: 0 || position < 0 ){
return
}
allWords.value?.let {
it[position].word = wordStr
}
}

fun jumpFragment2(v:View){
//用Navigation的方式跳转到新Fragment
val action = Fragment0Directions.actionFragment0ToFragment1("time", 1)
Navigation.findNavController(v).navigate(action)
}
}

Adapter使用Data Binding

点击事件依旧通过手动设置
item:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="word"
type="io.github.araisann.navigationtest.Word" />
</data>
<!--界面布局-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{word.word}" />
</LinearLayout>
</layout>

adapter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class MyRecyclerAdapter(private val allWords: List<Word>?) :
RecyclerView.Adapter<MyRecyclerAdapter.MyViewHolder>() {
var listener: OnItemClickListener? = null

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val itemBinding = ItemWordBinding.inflate(layoutInflater, parent, false)
return MyViewHolder(itemBinding)
}

override fun getItemCount(): Int {
return allWords?.size ?: 0
}

override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
allWords?.let {
holder.bind(it[position])
}
holder.binding.itemRoot.setOnClickListener { view ->
//点击事件
allWords?.let {
listener?.onClick(view, allWords[position])
}
}
}

fun setOnItemClickListener(listener: OnItemClickListener) {
this.listener = listener
}

interface OnItemClickListener {
fun onClick(view: View, data: Word)
}

//ViewHolder
class MyViewHolder(val binding: ItemWordBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(word: Word) {
binding.word = word
binding.executePendingBindings()
}
}
}

Fragment:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Fragment0 : Fragment() {
private lateinit var wordViewModel: WordViewModel
private lateinit var fragment0Binding: Fragment0Binding
private lateinit var adapter: MyRecyclerAdapter

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
wordViewModel = ViewModelProviders.of(activity!!).get(WordViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragment0Binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment0,
container,
false
) as Fragment0Binding
fragment0Binding.viewmodel = wordViewModel
setupRecyclerView()
return fragment0Binding.root
}
private fun setupRecyclerView(){
val recyclerView = fragment0Binding.recyclerView
recyclerView.layoutManager = LinearLayoutManager(context)
adapter = MyRecyclerAdapter(wordViewModel.allWords.value)
adapter.setOnItemClickListener(object : MyRecyclerAdapter.OnItemClickListener{
override fun onClick(view: View, data: Word) {
}
})
recyclerView.adapter = adapter
}
}

相关注解

@BindAdapter(‘name’):自定义方法

给View增加一个自定义的属性,并绑定。
不推荐覆盖系统自带的属性。
可以参考ImageViewBindingAdapter等自带实现,里面的”android:src”等自带属性也是通过@BindingAdapter实现的。

1
2
3
4
5
6
7
8
<data>
<variable
name="myValue"
type="xx.xx.xx.A"/>
</data>
<MyView
app:myAttribute="@{myValue}"
/>
1
2
3
4
5
//Adapter中
@BindingAdapter("myAttribute")
@JvmStatic
fun setMyAttribute(view: MyView, value: xx.xx.xx.A) {
}

@BindingMethod、 @BindingMethods

可以参考ImageViewBindingAdapter等自带实现。
类似简单版@BindAdapter。通过给类增加注释,实现xml属性和方法的bind。直接set。

1
2
3
4
@BindingMethods({
BindingMethod(type = MyView::class, attribute = "myAttribute", method = "setMyAttribute"),
BindingMethod(type = MyView::class, attribute = "myAttribute2", method = "setMyAttribute2")
})

@Bindable

绑定,实现通知UI更新。
不过有LiveData了,似乎不太需要这个了。
DataBinding调用setLifecycleOwner,xml即可随着数据改变而改变。