DataBinding的學習使用

先看兩個問題:

問題1:android開發中,需要使用到findviewById來進行初始化view,如果頁面,可能會看到幾十行的findviewById方法,而如果需要設置點擊事件,又要幾十行的代碼。

解決:ButterKnife,自動綁定了view,點擊事件等,代替了繁雜的手寫步驟。

問題2:用ButterKnife,就是如果一個頁面view過多的話,也需要一長串的 @bindView 代碼,導致一個頁面輕輕鬆鬆300行+的代碼量,看着也有點不舒服。而這些也都是重複性的工作,那麼有什麼辦法解決嗎?

解決:DataBinding!

 

MVVM

MVVM和MVP的區別,是把presenter層換成了viewmodel層,還有就是view層和viewmodel層是相互綁定的關係,這意味着當你更新viewmodel層的數據的時候,view層會相應的變動ui。視圖和數據的雙向綁定就是 通過Android Data Binding技術。

下面學習開始學習dataBinding。

一、配置databinding

首先在Module app下build.gradle中配置databinding

android {
    dataBinding{
        enabled = true
    }
}

二、使用databinding

1、一個例子--簡單使用

先寫個Model實體類Man:

/**
 * 俠客,ViewModel
 */
public class Man extends BaseObservable{
    private String name;

    private String level;

    public Man(String name, String level) {
        this.name = name;
        this.level = level;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLevel() {
        return level;
    }

    public void setLevel(String level) {
        this.level = level;
    }
}

然後佈局文件:

<?xml version="1.0" encoding="utf-8"?>

<!--根標籤-->
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <!--變量的定義-->
    <data>
        <variable
            name="man"   <!--變量名-->
            type="com.hfy.demo01.module.mvvm.bean.Man" />  <!--變量類型-->
    </data>

   <!--傳統的視圖-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{man.name}" />  <!--變量的引用-->

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{man.level}" />

    </LinearLayout>

</layout>

說明:

a.佈局根標籤爲:<layout>,由以前的<LinearLayout>改爲<layout>,注意 l 小寫。

b.定義變量的標籤:<data>、<variable>,變量的引用:@{man.level}

 

最後在MvvmActivity中對 數據和視圖 進行綁定。(要先Make Project)注意下面代碼的註釋1、2、3。

    public class MvvmActivity extends AppCompatActivity {

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

//        setContentView(R.layout.activity_mvvm);

            //1、這裏使用DataBindingUtil.setContentView()
            //2、ActivityMvvmBinding是在寫好<layout>佈局後,make project,自動生成的Binding輔助類,包含了佈局中所有的綁定關係
            ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);

            //3、給 佈局中定義的變量man 進行賦值。
            Man man = new Man("段譽","愛情高手");
            binding.setMan(man);
        }

運行結果如下圖。可見:我們沒有findViewById、setText,依然可以把數據展示出來。

2、事件處理,兩種方法如下圖

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <!--對象變量的定義-->
        <variable
            name="man"
            type="com.hfy.demo01.module.mvvm.bean.Man" />

        <variable
            name="onClickListener"
            type="android.view.View.OnClickListener" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{man.name}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{man.level}" /> <!--支持表達式-->

        <!--事件處理的兩種方法-->

        <!--方法一、先設置控件id,然後java代碼中使用Binding設置-->
        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="button1" />
        
        <!--方法二、引用變量-->
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{onClickListener}"
            android:text="button2" />

    </LinearLayout>

</layout>
        //事件處理方法一,binding.
        binding.btnTest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                man.setName("段正淳");
            }
        });

        //事件處理方法二:
        binding.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MvvmActivity.this, "button2", Toast.LENGTH_SHORT).show();
            }
        });

 

3、佈局屬性

import用法,

    <data>
        
        <!--導入-->
        <import type="com.hfy.demo01.module.mvvm.bean.Man" />

        <!--對象變量的定義-->
        <variable
            name="man"
            type="Man" />

    </data>

變量定義,

        <!--基本數據類型 的變量, java.lang.*會自動導入-->
        <variable
            name="home"
            type="String" />

        <variable
            name="age"
            type="int" />

        <variable
            name="isMale"
            type="boolean" />

        <!--集合變量的定義( <> 要用轉義符 )-->
        <variable
            name="list"
            type="ArrayList&lt;String&gt;" />

<!-------->

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{home}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{list.get(0)}" />
        binding.setHome("安徽");

        ArrayList<String> strings = new ArrayList<>();
        strings.add("一");
        strings.add("二");
        binding.setList(strings);

靜態方法調用,

        <!--靜態方法調用-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{Utils.getName(man)}" /> <!--要先導入Utils-->
public class Utils {
    public static String getName(Man man) {
        return man.getName();
    }
}
Man man = new Man("段正淳","愛情高高高手");
                binding.setMan(man);

支持表達式,(三目預算、+、!、&&、>= 等等)

        <variable
            name="isMale"
            type="boolean" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text='@{isMale? man.name + ":男的": man.name + ":女的"}' /> <!--支持表達式-->

 

Convert,把數據格式 轉爲需要的格式

        <variable
            name="time"
            type="java.util.Date" />
public class Utils {
    /**
     * convertDate()這個方法在哪個類不重要,重要的是 @BindingConversion
     * @param date
     * @return
     */
    @BindingConversion
    public static String convertDate(Date date) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String format = simpleDateFormat.format(date);
        return format;
    }
}
        ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
        binding.setTime(new Date());

 

三、動態更新 / 雙向綁定

此前例子中,Model實體類變化,UI是不會動態更新的。 接下來實現 動態更新:繼承BaseObservable

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <import type="com.hfy.demo01.module.mvvm.bean.Man" />

        <variable
            name="man"
            type="Man" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{man.name}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{man.level}" />

    </LinearLayout>

</layout>
/**
 * 俠客,  繼承BaseObservable
 */
public class Man extends BaseObservable {
    private String name;

    private String level;

    public Man(String name, String level) {
        this.name = name;
        this.level = level;
    }

    /**
     * @return
     * @Bindable BR中生成一個對應的字段,BR編譯時生成,類似R文件
     */
    @Bindable
    public String getName() {
        return name;
    }

    /**
     * notifyPropertyChanged(BR.name),通知系統BR.name已發送變化,並更新UI
     *
     * @param name
     */
    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }

    @Bindable
    public String getLevel() {
        return level;
    }

    public void setLevel(String level) {
        this.level = level;
        notifyPropertyChanged(BR.level);
    }
    
}
        ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);

        Man man = new Man("段譽","愛情高手");
        binding.setMan(man);

//        //事件處理方法一,binding.
        binding.btnTest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Model實體類發生變化  -- UI會動態變化
                man.setName("段正淳");
            }
        });

接下來就是雙向綁定:就是Model和View 通過VIewModel 進行雙向動態更新。Model實體類變化,VIew會自動更新;View發生變化,Model實體類也能動態改變。

        <!--使用EditText通過 "@={}" 改變man.name的值-->
        <!--UI EditText變化,實體類man也動態變化-->
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={man.name}"/>

 

五、結合RecyclerView

avtivity佈局:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <!--這裏設置了id,就可以通過binding直接找到RecyclerView-->
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </android.support.v7.widget.RecyclerView>

    </LinearLayout>
</layout>

item佈局:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="man"
            type="com.hfy.demo01.module.mvvm.bean.Man" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{man.name}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{man.level}" />

    </LinearLayout>
</layout>

activity:

public class RecyclerActivity extends AppCompatActivity {

    private ActivityRecyclerBinding binding;

    /**
     * launch MvpActivity
     */
    public static void launch(Activity activity) {
        Intent intent = new Intent(activity, RecyclerActivity.class);
        activity.startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

//        setContentView(R.layout.activity_recycler);

        // 1、使用 DataBindingUtil.setContentView() 代替  setContentView(R.layout.activity_recycler);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_recycler);

        //直接通過binding找到了recyclerView
        LinearLayoutManager layout = new LinearLayoutManager(this);
        layout.setOrientation(LinearLayoutManager.VERTICAL);
        binding.recycler.setLayoutManager(layout);
        binding.recycler.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
        ManAdapter manAdapter = new ManAdapter(getList());
        binding.recycler.setAdapter(manAdapter);
    }

    private List<Man> getList() {
        List<Man> list = new ArrayList<>();
        list.add(new Man("喬峯", "幫主"));
        list.add(new Man("虛竹", "小和尚"));
        return list;
    }

    private class ManAdapter extends RecyclerView.Adapter<ManViewHolder> {

        private final List<Man> mList;

        public ManAdapter(List<Man> list) {
            mList = list;
        }

        @NonNull
        @Override
        public ManViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {

            //2、這裏是把item_layout對應的Binding 注入iewHolder(以前是View)
            ItemLayoutRecyclerBinding manViewBinding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.item_layout_recycler, viewGroup, false);
            return new ManViewHolder(manViewBinding);
        }

        @Override
        public void onBindViewHolder(@NonNull ManViewHolder manViewHolder, int i) {

            //3、綁定只用使用Binding,很簡潔。
            manViewHolder.manViewBinding.setMan(mList.get(i));
        }

        @Override
        public int getItemCount() {
            return mList.size();
        }
    }

    private class ManViewHolder extends RecyclerView.ViewHolder {

        private final ItemLayoutRecyclerBinding manViewBinding;

        //4、這裏沒有findViewById()去找各個view。就holder了Binding。
        public ManViewHolder(ItemLayoutRecyclerBinding manViewBinding) {
            super(manViewBinding.getRoot());
            this.manViewBinding = manViewBinding;
        }
    }
}

結果:

 

RecyclerView功能強大,使用DataBinding來處理RecyclerView Item 再合適不過,做到了數據和itemView的完美分離,告別了反覆、冗餘的自定義Adapter,不需要關心太多無意義的事。

 

六、使用注意點

1、儘可能少的variable和import。  過多的variable除了會讓你多做幾次無謂的綁定外,數據也將變得難以管理。

bad

<data>

        <import type="com.ditclear.app.util.DateUtil"/>

        <import type="android.view.View"/>

        <import type="com.ditclear.app.util.StringUtil"/>

        <import type="com.ditclear.app.network.model.StudentState"/>

        <import type="java.math.BigDecimal"/>

        <import type="com.ditclear.app.presentation.student.StudentActivity"/>

        <variable
            name="isShow"
            type="Boolean"/>

        <variable
            name="time"
            type="java.util.Date"/>

        <variable
            name="date"
            type="java.util.Date"/>

        <variable
            name="signTime"
            type="String"/>

        <variable
            name="item"
            type="com.ditclear.app.network.model.student.StudentItem"/>

        <variable
            name="presenter"
            type="com.ditclear.app.presentation.student.StudentActivity.Presenter"/>
    </data>

better

<data>
        <variable
                name="vm"
                type="com.ditclear.app.view.student.viewmodel.StudentViewModel"/>
</data>

數據的處理和view的顯示都用ViewModel來處理,比如:

android:text="@{vm.signTime}"
android:visibility="@{vm.isShowVisbility}"

好處是減輕了layout.xml文件的複雜程度,xml文件只用來單純的展示數據, 而複雜的邏輯判斷都放在了ViewModel中,並且爲頁面佈局數據提供了唯一的入口,方便查看和管理數據源。

2、避免使用複雜的表達式,下面這種的邏輯判斷最好放在ViewModel中,由ViewModel提供對應的變量。

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text='@{isMale? man.name + ":男的": man.name + ":女的"}' /> <!--支持表達式-->

而且,剛入門DataBinding的人都遇到過找不到binding文件的錯,可能是表達式寫錯、variable的類路徑不對或者沒import相應的類(比如View)等等,都與複雜的表達式有關係,而DataBinding報錯也不太智能,有時不能準確定位,所以建議不要在xml裏進行復雜的數據綁定,這些都儘量放到ViewModel裏進行,xml只綁定簡單的基礎數據類型。

 

七、Android Studio插件 Databinding Support

https://plugins.jetbrains.com/plugin/9271-databinding-support

 

參考:

《Android進階之光》

DataBinding實用指南

推薦閱讀  詳細系列文章:

DataBinding使用指南(一):佈局和binding表達式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章