搞了搞DataBinding瘸洛,并在項目新模塊中進行了使用,感覺還是不錯的只损,這里記錄下使用過程及基本原理,方便以后查閱七咧。
干啥的啊
DataBinding是啥跃惫?
一個Google官方的數(shù)據(jù)綁定框架-Data Binding Library,是對MVVM在Android上的一種實現(xiàn)艾栋,可以直接綁定數(shù)據(jù)到xml中爆存,并實現(xiàn)自動刷新。
MVVM 又是啥蝗砾?
MVVM是 Model-View-ViewModel的縮寫先较。三部分分別是:Model – 代表你的基本業(yè)務(wù)邏輯;View – 顯示內(nèi)容悼粮;ViewModel – 將前面兩者聯(lián)系在一起的對象闲勺。MVVM 在使用當(dāng)中,通常還會利用雙向綁定技術(shù)即: Model 變化時扣猫,ViewModel 會自動更新菜循,而 ViewModel 變化時,View 也會自動變化苞笨。
1债朵、準備工作
在Module的build.gradle android模塊中添加
android {
…
dataBinding {
enabled = true
}
}
在布局Layout外面添加子眶,<layout></layout>標簽,示例如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<RelativeLayout
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tvHelloWorld"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</RelativeLayout>
</layout>
好序芦,準備到這里呢臭杰,我們就可以在Activity或者Fragment中使用DataBinding了。
2谚中、在Activity渴杆、Fragment中如何初始化Binding類
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
FragmentBinding inflate = DataBindingUtil.inflate(inflater, R.layout.fragment, container, false);
return inflate.getRoot();
}
分別在Activity和Fragment中分別初始化了Binding類,可以看到宪塔,我們Binding類的生成是有規(guī)則的:activity_main-->ActivityMainBinding 磁奖、fragment-->FragmentBinding(不要忘記分別給這兩個布局加<layout>標簽),即:第一個單詞首字母大寫某筐,第二個單詞首字母大寫...最后都會拼上Binding就是生成的Binding類比搭。
如何自定義生成的Binding類名呢?如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class="CustomBindingName">
</data>
...
</layout>
然后我們在生成Binding類的時候就可以:
CustomBindingName inflate = DataBindingUtil.inflate(inflater, R.layout.fragment, container, false);
初始化完Binding類之后南誊,我們就可以直接使用該layout中定義了id的View身诺,如:
viewDataBinding.tvHelloWolrd.setText("厲害了");
為什么可以直接拿來操作呢,因為DataBinding已經(jīng)幫我們初始化好了(具體怎么初始化的下一篇再說抄囚,這篇就講基本使用啦)霉赡,這個已經(jīng)初始化好的View的命名和Binding類的生成命名規(guī)則相同,只是后面沒有加Binding了幔托。
3穴亏、數(shù)據(jù)綁定
3.1、基本數(shù)據(jù)綁定
修改activity_main.xml文件如下(注意:這里不用判斷bean重挑!=null嗓化,因為DataBinding會自動幫助我們進行空指針的避免,比如@{bean.name}谬哀,如果bean是null的話蟆湖,bean.name則會被賦默認值(null)。age的話玻粪,則是0):
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="bean"
type="com.thc.bindingdemo.BindingBean" />
</data>
<LinearLayout
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tvHelloWolrd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<TextView
android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.name}" />
<TextView
android:id="@+id/tvAge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(bean.age)}" />
</LinearLayout>
</layout>
在java代碼中:
BindingBean bindingBean = new BindingBean("thc",18);
//viewDataBinding.setVariable(com.thc.bindingdemo.BR.bean,demoBean);
viewDataBinding.setBean(demoBean);
(以上兩種方式都可以)這樣通過生成的Binding類給xml文件setBean就實現(xiàn)了View和Data的綁定。這樣只能夠?qū)崿F(xiàn)給xml進行設(shè)置數(shù)據(jù)诬垂,但是View如何實現(xiàn)根據(jù)Data的變化實時更新呢劲室?
就是讓我們的Model extends BaseObservable,如果想更新單個屬性就在該屬性set方法中使用
//更新單個屬性
notifyPropertyChanged(com.thc.bindingdemo.BR.school);
//更新所有的屬性
notifyChange();
//定義的Bean如下:
public class BindingDemoBean extends BaseObservable {
private String name;
private String school;
private String className;
public BindingDemoBean(String name, String school,String className) {
this.name = name;
this.school = school;
this.className = className;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
//更新所有的屬性
notifyChange();
}
@Bindable
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
//更新單個屬性
notifyPropertyChanged(com.thc.bindingdemo.BR.school);
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
}
3.2數(shù)據(jù)綁定特殊的幾個地方
1)集合(注意:需要判斷一下集合的長度结窘,否則有可能出現(xiàn)數(shù)組越界的錯誤)
<variable
name="beans"
type="java.util.ArrayList<com.thc.bindingdemo.BindingDemoBean>" />
<variable
name="strings"
type="java.util.ArrayList<String>" />
<variable
name="map"
type="java.util.HashMap<String,String>" />
<variable
name="str"
type="String" />
<variable
name="num"
type="int" />
<!--需要注意的是很洋,給ArrayList<...> 的<>進行了轉(zhuǎn)義 < > -->
<variable
name="beans"
type="java.util.ArrayList<com.thc.bindingdemo.BindingDemoBean>" />
<!--取集合中的數(shù)據(jù),設(shè)置姓名-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{beans.size>0?beans.get(0).name:bean.name}" />
2)給TextView setText的時候既有動態(tài)又有寫死的隧枫。注意:方式2使用單引號&&字符個數(shù)>2喉磁,如果只有一個比如"寫"的話會報錯(待搞)谓苟。
<!--寫死的字符+動態(tài)字符 方式1-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{beans.get(0).name + @string/app_name}" />
<!--寫死的字符+動態(tài)字符 方式2-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{"寫死的"+beans.get(0).name}' />
4、事件綁定
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{presenter.listenBind}"
android:text="單獨更新" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{()->presenter.lambda(bean)}"
android:text="全部更新" />
//事件綁定
public class Presenter{
/**
* 實現(xiàn)單獨更新某個屬性
* 方法綁定:這種方式要求协怒,自定義的方法要和 public void onClick(View v) {} 一樣涝焙,方法名可以不同,但是參數(shù)一定要有
*/
public void listenBind(View view){
demoBean.setSchool("北大");
demoBean.setClassName("二班");
}
/**
* 更新所有的屬性
* lambda表達式綁定:這種就可以任意定義了孕暇,這里是把綁定到xml的bean仑撞,傳回來并show出來
* 切記:如果使用lambda表達式綁定事件,在xml中調(diào)用它的方法的時候要加()妖滔,擦隧哮,吃了這里的虧
*/
public void lambda(BindingDemoBean bean){
demoBean.setName("很強勢");
demoBean.setClassName("三班");
Toast.makeText(MainActivity.this,bean.toString(),Toast.LENGTH_SHORT).show();
}
public void lambda1(){
Log.e("result","切記lambda表達式調(diào)用的時候要加()");
}
}
viewDataBinding.setPresenter(new Presenter());//這一步要記得哦
5、使用include及給include中的控件設(shè)置數(shù)據(jù)座舍、綁定變量
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="includeBean"
type="com.thc.bindingdemo.BindingDemoBean" />
<variable
name="presenter"
type="com.thc.bindingdemo.MainActivity.Presenter" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical"
android:padding="10dp">
<TextView
android:id="@+id/tvBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{presenter.finish}"
android:text="返回" />
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="標題" />
<TextView
android:id="@+id/tvOperate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="分享" />
</RelativeLayout>
</layout>
viewDataBinding.includeBar.setPresenter(new Presenter());
viewDataBinding.includeBar.setIncludeBean(demoBean);
使用Include還可以直接從外層布局傳值到Include布局中去沮翔,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.thc.myzhihu.bindingdemo.BindingIncludeActivity">
<!--將傳入外層布局的值,直接傳入Include的布局中曲秉,-->
<include
layout="@layout/include_binding_test1"
bind:includeBean="@{outterLayoutBean}"
bind:presenter="@{outterPresenter}" />
</LinearLayout>
6采蚀、表達式 & 表達式鏈
6.1、表達式
這里只是說明下經(jīng)常用的運算符:
注意:第一個三元運算符使用的時候要做如下操作
<data>
...
<import type="android.view.View"/>
</data>
<!--三元運算符-->
<ImageView
android:visibility="@{bean.age>10?View.VISIBLE:View.GONE}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"/>
<!--空合并運算符 取兩個中不為null的數(shù)據(jù)-->
<TextView
android:text="@{beans.get(0).name??bean.name}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
6.2岸浑、表達式鏈
<!--表達式鏈搏存,就比如iv1、iv2的顯示隱藏都和 bean的age大小有關(guān)矢洲,那么可以簡化為如下方式-->
<ImageView
android:id="@+id/iv1"
android:visibility="@{bean.age>10?View.VISIBLE:View.GONE}"
android:src="@mipmap/ic_launcher"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/iv2"
android:visibility="@{iv1.visibility}"
android:src="@mipmap/img2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
7璧眠、自定義BindAdapter
public class BindAdapter {
/**
* 加載網(wǎng)絡(luò)圖片
*/
@BindingAdapter({"app:imageUrl","app:placeholderDraw"})
public static void setNetImg(ImageView ivNet, String imgUrl, Drawable placeHodler){
Glide.with(ivNet.getContext()).load(imgUrl).placeholder(placeHodler).into(ivNet);
}
/**
* 給將第一個字符變成紅色
*/
@BindingAdapter("app:text")
public static void setSpannelText(TextView textView,String text){
SpannableString spannableString = new SpannableString(text);
ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.RED);
spannableString.setSpan(colorSpan,0,1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spannableString);
}
}
在xml中的應(yīng)用:
<!--自定義BindAdapter placeholderDraw和imageUrl必須和你的定義的靜態(tài)方法中的參數(shù)一樣的-->
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
app:imageUrl="@{str}"
app:placeholderDraw="@{@drawable/img2}" />
<!--自定義BindAdapter text必須和你的定義的靜態(tài)方法中的參數(shù)一樣的-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:text='@{map.get("key")}' />
注意:在使用app:placeholderDraw="@{@drawable/img2}" 的時候,不能使用@mipmap/哦
8读虏、自定Setter
針對自定義View
public class MyImageView extends ImageView {
private String imgUrl;
public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl;
Glide.with(getContext()).load(imgUrl).into(this);
}
}
<com.thc.bindingdemo.MyImageView
app:imgUrl="@{myImageUrl}"
android:layout_width="100dp"
android:layout_height="100dp"/>
9责静、BindingConversion
用于時間轉(zhuǎn)化,即我們傳一個Date格式的數(shù)據(jù)給TextView盖桥,通過BindingConversion轉(zhuǎn)化為你想要的時間格式灾螃,并設(shè)置給TextView,如下:
@BindingConversion
public static String convertTime(Date date) {
SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd");
return time.format(date);
}
<!--BindingConversion-->
<TextView
android:text="@{time}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
//設(shè)置數(shù)據(jù)
viewDataBinding.setTime(new Date());
10揩徊、雙向綁定
10.1 直接根據(jù)輸入內(nèi)容修改bean對象
如下:根據(jù)用戶輸入的學(xué)校腰鬼,動態(tài)改變bean的學(xué)校值,同時上面tvSchool的顯示值也變化了塑荒,挺好熄赡。
<!--雙向綁定,動態(tài)修改學(xué)校 這里注意 @={bean.school} @后面有個=號哦 -->
<EditText
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="@={bean.school}"/>
10.2 監(jiān)聽輸入內(nèi)容(即addTextChanged效果)
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="inputText"
type="String" />
<variable
name="presenter"
type="com.thc.dialogfragmentdemo.MainActivity.MainPresenter" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.thc.dialogfragmentdemo.MainActivity">
<EditText
android:id="@+id/edtTest"
android:layout_width="match_parent"
android:layout_height="50dp"
android:afterTextChanged="@{()->presenter.afterTextChanged1(inputText)}"
android:beforeTextChanged="@{()->presenter.beforeTextChanged(inputText)}"
android:onTextChanged="@{()->presenter.onTextChanged(inputText)}"
android:text="@={inputText}" />
<!--同樣-->
</LinearLayout>
</layout>
Presenter代碼如下:
public class MainPresenter {
public void onTextChanged(String s) {
LogUtil.loge("MainPresenter+onTextChanged:" + s);
}
public void afterTextChanged1(String s) {
LogUtil.loge("MainPresenter+afterTextChanged:" + s);
}
public void beforeTextChanged(String s) {
LogUtil.loge("MainPresenter+beforeTextChanged:" + s);
}
}
11齿税、RecyclerView中應(yīng)用
示例如下:
customBindingName.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
customBindingName.recyclerView.setAdapter(new MyAdapter(getActivity(),initDatas()));
//適配器代碼如下
class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyHolder>{
Context mContext;
List<BindingDemoBean> mDatas;
public MyAdapter(Context conetxt,List<BindingDemoBean> list){
this.mContext = conetxt;
this.mDatas = list;
}
@Override
public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflate = LayoutInflater.from(mContext);
ViewDataBinding binding = DataBindingUtil.inflate(inflate, R.layout.item_recyclerview, parent, false);
return new MyHolder(binding);
}
@Override
public void onBindViewHolder(MyHolder holder, int position) {
ViewDataBinding binding = holder.getBinding();
BindingDemoBean bindingDemoBean = mDatas.get(position);
//執(zhí)行一下executePendingBindings,及時刷新
binding.executePendingBindings();
binding.setVariable(com.thc.bindingdemo.BR.item,bindingDemoBean);
}
@Override
public int getItemCount() {
return mDatas.size();
}
class MyHolder extends RecyclerView.ViewHolder{
private ViewDataBinding mBinding;
public MyHolder(ViewDataBinding mBinding) {
super(mBinding.getRoot());
this.mBinding = mBinding;
}
public ViewDataBinding getBinding(){
return mBinding;
}
}
}
ViewHolder抽取出來:
public class BindingViewHolder<T extends ViewDataBinding> extends RecyclerView.ViewHolder {
protected final T mBinding;
public BindingViewHolder(T binding) {
super(binding.getRoot());
mBinding = binding;
}
public T getBinding() {
return mBinding;
}
}
13彼硫、自定義Component
問:我們都知道可以通過自定義BindingAdapter 提供View沒有的setter方法或者,在執(zhí)行View自身的setter之前進行一些操作。但是拧篮,系統(tǒng)是如何找到我們自定義的BindingAdapter并調(diào)用它內(nèi)部的static靜態(tài)方法的呢词渤?
答:在build/intermediates/classes下面,可以找到DataBindingComponent類串绩,包名為android.databinding缺虐,全局只會有一個該類——此接口在編譯時生成,包含了所有用到的實例BindingAdapters的getter方法赏参。
當(dāng)一個BindingAdapter是一個實例方法(instance method)志笼,一個實現(xiàn)該方法的類的實例必須被實例化。這個生成的接口會包含每個聲明BindingAdapter的類/接口的get方法把篓。命名沖突會簡單地加一個數(shù)字前綴到get方法前來解決纫溃。
使用步驟如下:
(1)比如我們這里需要實現(xiàn)一件換膚操作需要定義兩個組件:DayComponent 和 NightComponent ,在自定義組件內(nèi)部進行初始化對應(yīng)的BindingAdapter韧掩,如下:
/**
* 白天組件
*/
public class DayComponent implements android.databinding.DataBindingComponent {
public MyBindingAdapter myBindingAdapter = new DayBindingAdapter();
@Override
public MyBindingAdapter getMyBindingAdapter() {
return myBindingAdapter;
}
}
(2)MyBindingAdapter是DayBindingAdapter紊浩,NightBindingAdapter的父類,來實現(xiàn)具體的換膚操作疗锐,DayBindingAdapter如下:
/**
* 日間Adapter
*/
public class DayBindingAdapter extends MyBindingAdapter{
@Override
public void setBgColor(LinearLayout layout, int llBgColor) {
layout.setBackgroundColor(layout.getResources().getColor(R.color.pop_bgcolor));
}
@Override
public void setTvColor(TextView tv, int tvColor) {
tv.setTextColor(tv.getResources().getColor(R.color.white));
}
}
(3)首先在Application中設(shè)置默認的Component坊谁,setComponent(DayComponent),點擊換膚的時候進行Day和Night的切換
Application中:
DataBindingUtil.setDefaultComponent(new DayComponent());
public void btn5(View v){
if(MyApplication.isDay){
DataBindingUtil.setDefaultComponent(new DayComponent());
}else{
DataBindingUtil.setDefaultComponent(new NightComponent());
}
MyApplication.isDay = !MyApplication.isDay;
recreate();
}
Demo地址:https://github.com/Number-1024/DataBindingDemo
接下來總結(jié)一下滑臊,DataBinding是如何初始化View口芍,及與數(shù)據(jù)綁定實時刷新,及BindingAdapter原理等雇卷。
嗯鬓椭,可以的!