閱讀這篇筆記你需要了解安卓的數據綁定框架databinding
首先貼上校長看到的感覺寫得最好的兩篇 介紹databinding的文章:
1. CornorLin:Android Data Binding 系列(一) -- 詳細介紹與使用
2. QQ音樂技術團隊:Android DataBinding 數據綁定
不管作為一名安卓還是android程序猿,總是少不了一直沒完沒了的重復制造adapter,viewholder,就像
等等,啊喂,這根本是同一個類呀!哦不好意思,實在太像了,搞錯了,真實的情況是這樣的:
這簡直可以做一個找茬游戲了,整天在這弄重復的代碼,不禁要想,除了那些骯臟(滑稽)的大洋我們整天這樣圖的是什么。作為一名有追求的立志成為一名架構師,從來懶得多寫代碼的程序猿這樣的情況怎么能忍!
于是不禁讓人想我們寫這些代碼屎味的什么?
Adapter與ViewHolder的作用:
-
ViewHolder
是用來通過findviewById來存放item對應的layout里邊的View控件的, -
Adapter
中onCreateViewHolder(ViewGroup parent, int viewType)
方法負責將獲取將ViewHolder取出;void onBindViewHolder(BindingHolder holder, int position)
負責將實體類的內容一條一條的通過set方法顯示到對應的界面上。
再看看databinding的作用:
- 通過DatabindingUtils的
public static <T extends ViewDataBinding> T inflate(LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent, boolean attachToParent)
方法獲取一個ViewDataBinding
,包含了Layout中所有的控件; -
boolean setVariable(int variableId, Object value)
負責將數據與界面綁定自動完成類似textview.setText(item.text)
這樣的工作,
是不是感覺職能高度重合呢,而且databinding好像用起來更省力,那是不是可以利用ViewDataBinding
替代ViewHoldr里邊的沒完沒了的findViewById呢,能不能用一個boolean setVariable(int variableId, Object value)
來替代onBindViewHolder(BindingHolder holder, int position)
中沒完沒了的set房呢。答案是可以的
現(xiàn)在假定你已經閱讀過那兩篇文章,對于databinding有了一定的理解。先上demo的代碼地址
先看一下效果圖:
可以看出來在demo中有三種item,按照以前的慣例。我們需要三個Adapter,三個ViewHolder,三個實體Bean,三個layout文件。但是呢,讓我們看一下demode代碼結構
三個實體bean,三個layout,但是只有一個Adapter,里邊有一個ViewHolder。但是實現(xiàn)了三種item的效果好神奇吧,并且即使我想再加一種item,只需要添加一個實體Bean,再加一個layout文件就好了不用去寫什么ViewHolder跟Adapter了,哈哈神奇吧。
首先看看我們是怎么用的吧:
List<BindingAdapterItem> items = new ArrayList<>();
items.add(new TextBean("哈哈哈哈"));
items.add(new ImageBean());
items.add(new Image2Bean());
items.add(new Image2Bean());
items.add(new TextBean("我又來啦"));
items.add(new Image2Bean());
items.add(new ImageBean());
items.add(new TextBean("我還來"));
items.add(new TextBean("就是不讓你看美女"));
items.add(new Image2Bean());
items.add(new ImageBean());
items.add(new TextBean("哈哈你當不住我看見啦"));
BindingAdapter adapter = new BindingAdapter();
adapter.setItems(items);
//這也是一個坑,經常忘了加LayoutManger導致東西Item無法顯示,RecyclerView把測量,布局的工作甩給了LayoutManager
LinearLayoutManager manager = new LinearLayoutManager(getApplicationContext());
binding.rv.setLayoutManager(manager);
binding.rv.setAdapter(adapter);
adapter.notifyDataSetChanged();
就像平常使用RecyclerView一樣,用一個List包裝要顯示的數據,其中TextBean,ImageBean,Image2Bean是對應三種不同布局的實體類。那么這些實體類里邊一定要有一些信息能夠讓BindingAdapter識別他們的布局信息,最簡單的方法就是在這些實體重直接返回布局文件,把他們返回布局的共同方法命名為int getViewType()
并創(chuàng)造一個新的iterface來封裝這個方法:
public interface BindingAdapterItem {
int getViewType();
}
以后每一個Item只需要實現(xiàn)這個接口中的int getViewType()方法就能告訴Adapter自己的布局了。
例如TextItem的實現(xiàn)為:
public class TextBean extends BaseObservable implements BindingAdapterItem {
@Override
public int getViewType() {
return R.layout.adapter_text;
}
public TextBean(String text) {
this.text = text;
}
private String text;
@Bindable
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
notifyPropertyChanged(BR.text);
}
}
繼承BaseObservalable是為了將數據與界面綁定,詳情請閱讀開頭的兩篇文章。
再看一下TextItem的layout的內容:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="item"
type="com.example.m.bean.TextBean"/>
</data>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{item.text}"
android:gravity="center"
android:textSize="25sp"
/>
</layout>
剛剛講過ViewDataBing
通過
boolean setVariable(int variableId, Object value)
方法來將數據綁定到界面上,其中int variableId
指的是變量在BR類中的ID,
<data>
<variable
name="item"
type="com.example.m.bean.TextBean"/>
</data>
中的name,而Object value
對應其中的type,在
android:text="@{item.text}"
中將TextItem
中的text
屬性綁定到對對應的控件上.
<data>
<variable
name="item"
type="com.example.m.bean.TextBean"/>
</data>
好的下面去往通用的BindingAdapter去看看
public class BindingAdapter extends RecyclerView.Adapter<BindingAdapter.BindingHolder> {
public List<BindingAdapterItem> getItems() {
return items;
}
public void setItems(List<BindingAdapterItem> items) {
this.items = items;
}
List<BindingAdapterItem> items = new ArrayList<>();
/**
* @return 返回的是adapter的view
*/
@Override
public BindingHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), viewType, parent, false);
return new BindingHolder(binding);
}
/*
* 數據綁定
* */
@Override
public void onBindViewHolder(BindingHolder holder, int position) {
holder.bindData(items.get(position));
}
@Override
public int getItemCount() {
return items.size();
}
@Override
public int getItemViewType(int position) {
return items.get(position).getViewType();
}
static class BindingHolder extends RecyclerView.ViewHolder {
ViewDataBinding binding;
/**
* @param binding 可以看作是這個hodler代表的布局的馬甲,getRoot()方法會返回整個holder的最頂層的view
* */
public BindingHolder(ViewDataBinding binding) {
//
super(binding.getRoot());
this.binding = binding;
}
public void bindData(BindingAdapterItem item) {
binding.setVariable(BR.item,item);
}
}
}
這個類的基本要求是:
- 能夠根據傳進來的對定的item判斷對應的布局,
- 能夠自動的把傳進來的數據顯示到對應的布局上;
adpter獲取正確的布局很簡單,只需要重寫int getItemViewType(int position)方法,在里邊直接返回item里邊的layout就行了:
@Override
public int getItemViewType(int position) {
return items.get(position).getViewType();
}
現(xiàn)在先用ViewDataBinding來取代View,標準的VIewholder應該是通過layout的rootView來構造,我們可以通過ViewDataBinding.getRoot()來返回這個rootview
ViewDataBinding binding;
/**
* @param binding 可以看作是這個hodler代表的布局的馬甲,getRoot()方法會返回整個holder的最頂層的view
* */
public BindingHolder(ViewDataBinding binding) {
//
super(binding.getRoot());
this.binding = binding;
}
綁定數據的時候只需要將實例化后的實體類對象傳入ViewDataBinding的對應的virable中就好了:
public void bindData(BindingAdapterItem item) {
//
binding.setVariable(BR.item,item);
}
因為這里的int variableId
是固定的BR.item
所以每一個layout中variable的name
屬性必須為item!
在Adapter的onCreateViewHolder(ViewGroup parent, int viewType)
中改用獲取對應的ViewDataBing來初始化ViewHodler:
@Override
public BindingHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), viewType, parent, false);
return new BindingHolder(binding);
}
然后在onBindViewHolder(BindingHolder holder, int position)
中調用就好了:
@Override
public void onBindViewHolder(BindingHolder holder, int position) {
holder.bindData(items.get(position));
}