280,android DataBinding的使用和字符串拼接效率

對(duì)Repository的解釋
http://m.itdecent.cn/p/4679c384acae //初探Android中Repository模式

在應(yīng)用需要加載數(shù)據(jù)或者保存數(shù)據(jù)的時(shí)候,建議創(chuàng)建一個(gè)Repository的存儲(chǔ)區(qū)類,里面放置存儲(chǔ)與加載應(yīng)用數(shù)據(jù)的API

注意:

一,anroid 的文件夾一般都是小寫字母開頭(尤其包名一定要小寫字母開頭),類都是大寫字母開頭.
二,用dataBinding可以不用給findById 這要找控件賦值,可以不用設(shè)置Id直接在xml種賦值,這樣就不用設(shè)置了Id了

三,AndroidViewModel

使用ViewModel的時(shí)候,需要注意的是ViewModel不能夠持有View、Lifecycle、Acitivity引用,而且不能夠包含任何包含前面內(nèi)容的類。因?yàn)檫@樣很有可能會(huì)造成內(nèi)存泄漏。

那如果需要使用Context對(duì)象改怎么辦。這時(shí)候我們可以給ViewModel一個(gè)Application。Application是一個(gè)Context,而且一個(gè)應(yīng)用也只會(huì)有Application。

我們自己添加Application?其實(shí)沒必要Google還有一個(gè)AndroidViewModel。這是一個(gè)包含Application的ViewModel。
    dataBinding {
        enabled = true
    }
}

ViewModel類是用來(lái)保存UI數(shù)據(jù)的類,它會(huì)在配置變更(即 Configuration Change,例如手機(jī)屏幕的旋轉(zhuǎn))之后繼續(xù)存在

截屏2020-07-14 下午2.05.51.png
截屏2020-07-14 下午2.07.24.png
截屏2020-07-14 下午2.13.27.png

android 編譯以后會(huì)有dataBinding,dataBinding是出現(xiàn)在這個(gè)目錄下

截屏2020-07-15 上午10.32.48.png

截屏2020-07-14 下午1.47.01.png
截屏2020-07-14 下午1.47.14.png
截屏2020-07-14 下午1.44.53.png
截屏2020-07-14 下午1.45.01.png
截屏2020-07-14 下午1.45.09.png
截屏2020-07-14 下午1.41.58.png

dataBinding:

DataBinding 是谷歌官方發(fā)布的一個(gè)框架,顧名思義即為數(shù)據(jù)綁定,是 MVVM 模式在 Android 上的一種實(shí)現(xiàn),用于降低布局和邏輯的耦合性,使代碼邏輯更加清晰。MVVM 相對(duì)于 MVP,其實(shí)就是將 Presenter 層替換成了 ViewModel 層。DataBinding 能夠省去我們一直以來(lái)的 findViewById() 步驟,大量減少 Activity 內(nèi)的代碼,數(shù)據(jù)能夠單向或雙向綁定到 layout 文件中,有助于防止內(nèi)存泄漏,而且能自動(dòng)進(jìn)行空檢測(cè)以避免空指針異常

啟用 DataBinding 的方法是在對(duì)應(yīng) Model 的 build.gradle 文件里加入以下代碼,同步后就能引入對(duì) DataBinding 的支持

android {
    dataBinding {
        enabled = true
    }
}

一、基礎(chǔ)入門

啟用 DataBinding 后,這里先來(lái)看下如何在布局文件中綁定指定的變量
打開布局文件,選中根布局的 ViewGroup,按住 Alt + 回車鍵,點(diǎn)擊 “Convert to data binding layout”,就可以生成 DataBinding 需要的布局規(guī)則

image.png
<?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"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

    </data>

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    </android.support.constraint.ConstraintLayout>
</layout>

和原始布局的區(qū)別在于多出了一個(gè) layout 標(biāo)簽將原布局包裹了起來(lái),data 標(biāo)簽用于聲明要用到的變量以及變量類型,要實(shí)現(xiàn) MVVM 的 ViewModel 就需要把數(shù)據(jù)(Model)與 UI(View)進(jìn)行綁定,data 標(biāo)簽的作用就像一個(gè)橋梁搭建了 View 和 Model 之間的通道

這里先來(lái)聲明一個(gè) Modle

package com.leavesc.databinding_demo.model;

/**
 * 作者:葉應(yīng)是葉
 * 時(shí)間:2018/5/16 20:20
 * 描述:https://github.com/leavesC
 */
public class User {

    private String name;

    private String password;
    
    ···
}

data 標(biāo)簽里聲明要使用到的變量名、類的全路徑

    <data>
        <variable
            name="userInfo"
            type="com.leavesc.databinding_demo.model.User" />
    </data>

如果 User 類型要多處用到,也可以直接將之 import 進(jìn)來(lái),這樣就不用每次都指明整個(gè)包名路徑了,而 java.lang.* 包中的類會(huì)被自動(dòng)導(dǎo)入,所以可以直接使用

    <data>
        <import type="com.leavesc.databinding_demo.model.User"/>
        <variable
            name="userInfo"
            type="User"/>
    </data>

如果存在 import 的類名相同的情況,可以使用 alias 指定別名

    <data>
        <import type="com.leavesc.databinding_demo.model.User" />
        <import
            alias="TempUser"
            type="com.leavesc.databinding_demo.model2.User" />
        <variable
            name="userInfo"
            type="User" />
        <variable
            name="tempUserInfo"
            type="TempUser" />
    </data>

這里聲明了一個(gè) User 類型的變量 userInfo,我們要做的就是使這個(gè)變量與兩個(gè) TextView 控件掛鉤,通過(guò)設(shè)置 userInfo 的變量值同時(shí)使 TextView 顯示相應(yīng)的文本
完整的布局代碼如下所示

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

    <data>
        <import type="com.leavesc.databinding_demo.model.User" />
        <variable
            name="userInfo"
            type="User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="20dp"
        android:orientation="vertical"
        tools:context="com.leavesc.databinding_demo.Main2Activity">

        <TextView
            android:id="@+id/tv_userName"
            ···
            android:text="@{userInfo.name}" />

        <TextView
            ···
            android:text="@{userInfo.password}" />

    </LinearLayout>

</layout>

通過(guò) @{userInfo.name} 使 TextView 引用到相關(guān)的變量,DataBinding 會(huì)將之映射到相應(yīng)的 getter 方法

之后可以在 Activity 中通過(guò) DataBindingUtil 設(shè)置布局文件,省略原先 Activity 的 setContentView() 方法,并為變量 userInfo 賦值

    private User user;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMain2Binding activityMain2Binding = DataBindingUtil.setContentView(this, R.layout.activity_main2);
        user = new User("leavesC", "123456");
        activityMain2Binding.setUserInfo(user);
    }
image.png

由于 @{userInfo.name}在布局文件中并沒有明確的值,所以在預(yù)覽視圖中什么都不會(huì)顯示,不便于觀察文本的大小和字體顏色等屬性,此時(shí)可以為之設(shè)定默認(rèn)值(文本內(nèi)容或者是字體大小等屬性都適用),默認(rèn)值將只在預(yù)覽視圖中顯示,且默認(rèn)值不能包含引號(hào)

    android:text="@{userInfo.name,default=defaultValue}"

此外,也可以通過(guò) ActivityMain2Binding 直接獲取到指定 ID 的控件

    activityMain2Binding.tvUserName.setText("leavesC");

每個(gè)數(shù)據(jù)綁定布局文件都會(huì)生成一個(gè)綁定類,ViewDataBinding 的實(shí)例名是根據(jù)布局文件名來(lái)生成,將之改為首字母大寫的駝峰命名法來(lái)命名,并省略布局文件名包含的下劃線??丶墨@取方式類似,但首字母小寫

也可以通過(guò)如下方式自定義 ViewDataBinding 的實(shí)例名

    <data class="CustomBinding">

    </data>

此外,在綁定表達(dá)式中會(huì)根據(jù)需要生成一個(gè)名為context的特殊變量,context的值是根 ViewgetContext()方法返回的Context對(duì)象, context變量會(huì)被具有該名稱的顯式變量聲明所覆蓋

Databinding 同樣是支持在 FragmentRecyclerView 中使用 。例如,可以看 Databinding 在 Fragment 中的使用

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        FragmentBlankBinding fragmentBlankBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_blank, container, false);
        fragmentBlankBinding.setHint("Hello");
        return fragmentBlankBinding.getRoot();
    }

**以上實(shí)現(xiàn)數(shù)據(jù)綁定的方式,每當(dāng)綁定的變量發(fā)生變化的時(shí)候,都需要重新向 ViewDataBinding 傳遞新的變量值才能刷新 UI 。接下來(lái)看如何實(shí)現(xiàn)自動(dòng)刷新 UI **

二、單向數(shù)據(jù)綁定

實(shí)現(xiàn)數(shù)據(jù)變化自動(dòng)驅(qū)動(dòng) UI 刷新的方式有三
種:BaseObservable、ObservableField、ObservableCollection

BaseObservable

一個(gè)純凈的 ViewModel 類被更新后,并不會(huì)讓 UI 自動(dòng)更新。而數(shù)據(jù)綁定后,我們自然會(huì)希望數(shù)據(jù)變更后 UI 會(huì)即時(shí)刷新,Observable 就是為此而生的概念

BaseObservable 提供了 notifyChange()notifyPropertyChanged() 兩個(gè)方法,前者會(huì)刷新所有的值域,后者則只更新對(duì)應(yīng) BRflag,該 BR 的生成通過(guò)注釋@Bindable 生成,可以通過(guò) BR notify 特定屬性關(guān)聯(lián)的視圖

public class Goods extends BaseObservable {

    //如果是 public 修飾符,則可以直接在成員變量上方加上 @Bindable 注解
    @Bindable
    public String name;

    //如果是 private 修飾符,則在成員變量的 get 方法上添加 @Bindable 注解
    private String details;

    private float price;

    public Goods(String name, String details, float price) {
        this.name = name;
        this.details = details;
        this.price = price;
    }

    public void setName(String name) {
        this.name = name;
        //只更新本字段
        notifyPropertyChanged(com.leavesc.databinding_demo.BR.name);
    }

    @Bindable
    public String getDetails() {
        return details;
    }

    public void setDetails(String details) {
        this.details = details;
        //更新所有字段
        notifyChange();
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }

}

在** setName()** 方法中更新的只是本字段,而 setDetails() 方法中更新的是所有字段

添加兩個(gè)按鈕用于改變 goods 變量的三個(gè)屬性值,由此可以看出兩個(gè) notify 方法的區(qū)別。當(dāng)中涉及的按鈕點(diǎn)擊事件綁定,在下面也會(huì)講到

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

    <data>
        <import type="com.leavesc.databinding_demo.model.Goods" />
        <import type="com.leavesc.databinding_demo.Main3Activity.GoodsHandler" />
        <variable
            name="goods"
            type="Goods" />
        <variable
            name="goodsHandler"
            type="GoodsHandler" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="20dp"
        tools:context=".Main3Activity">

        <TextView
            ···
            android:text="@{goods.name}" />

        <TextView
            ···
            android:text="@{goods.details}" />

        <TextView
            ···
            android:text="@{String.valueOf(goods.price)}" />

        <Button
            ···
            android:onClick="@{()->goodsHandler.changeGoodsName()}"
            android:text="改變屬性 name 和 price"
            android:textAllCaps="false" />

        <Button
            ···
            android:onClick="@{()->goodsHandler.changeGoodsDetails()}"
            android:text="改變屬性 details 和 price"
            android:textAllCaps="false" />

    </LinearLayout>
</layout>
public class Main3Activity extends AppCompatActivity {

    private Goods goods;

    private ActivityMain3Binding activityMain3Binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);
        activityMain3Binding = DataBindingUtil.setContentView(this, R.layout.activity_main3);
        goods = new Goods("code", "hi", 24);
        activityMain3Binding.setGoods(goods);
        activityMain3Binding.setGoodsHandler(new GoodsHandler());
    }

    public class GoodsHandler {

        public void changeGoodsName() {
            goods.setName("code" + new Random().nextInt(100));
            goods.setPrice(new Random().nextInt(100));
        }

        public void changeGoodsDetails() {
            goods.setDetails("hi" + new Random().nextInt(100));
            goods.setPrice(new Random().nextInt(100));
        }

    }

}
image.png
可以看到,name 視圖的刷新沒有同時(shí)刷新 price 視圖,而 details 視圖刷新的同時(shí)也刷新了 price 視圖

實(shí)現(xiàn)了Observable 接口的類允許注冊(cè)一個(gè)監(jiān)聽器,當(dāng)可觀察對(duì)象的屬性更改時(shí)就會(huì)通知這個(gè)監(jiān)聽器,此時(shí)就需要用到 OnPropertyChangedCallback

當(dāng)中 propertyId 就用于標(biāo)識(shí)特定的字段

        goods.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(Observable sender, int propertyId) {
                if (propertyId == com.leavesc.databinding_demo.BR.name) {
                    Log.e(TAG, "BR.name");
                } else if (propertyId == com.leavesc.databinding_demo.BR.details) {
                    Log.e(TAG, "BR.details");
                } else if (propertyId == com.leavesc.databinding_demo.BR._all) {
                    Log.e(TAG, "BR._all");
                } else {
                    Log.e(TAG, "未知");
                }
            }
        });

ObservableField

繼承于 Observable 類相對(duì)來(lái)說(shuō)限制有點(diǎn)高,且也需要進(jìn)行 notify 操作,因此為了簡(jiǎn)單起見可以選擇使用 ObservableFieldObservableField 可以理解為官方對(duì) BaseObservable 中字段的注解和刷新等操作的封裝,官方原生提供了對(duì)基本數(shù)據(jù)類型的封裝,例如 ObservableBooleanObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLongObservableFloat、ObservableDouble 以及 ObservableParcelable ,也可通過(guò) ObservableField 泛型來(lái)申明其他類型

public class ObservableGoods {

    private ObservableField<String> name;

    private ObservableFloat price;

    private ObservableField<String> details;

    public ObservableGoods(String name, float price, String details) {
        this.name = new ObservableField<>(name);
        this.price = new ObservableFloat(price);
        this.details = new ObservableField<>(details);
    }

    ```
}

對(duì) ObservableGoods 屬性值的改變都會(huì)立即觸發(fā) UI 刷新,概念上與 Observable 區(qū)別不大,具體效果可看下面提供的源代碼,這里不再贅述

ObservableCollection

dataBinding 也提供了包裝類用于替代原生的 ListMap,分別是 ObservableListObservableMap,當(dāng)其包含的數(shù)據(jù)發(fā)生變化時(shí),綁定的視圖也會(huì)隨之進(jìn)行刷新

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

    <data>
        <import type="android.databinding.ObservableList"/>
        <import type="android.databinding.ObservableMap"/>
        <variable
            name="list"
            type="ObservableList&lt;String&gt;"/>
        <variable
            name="map"
            type="ObservableMap&lt;String,String&gt;"/>
        <variable
            name="index"
            type="int"/>
        <variable
            name="key"
            type="String"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="com.leavesc.databinding_demo.Main12Activity">

        <TextView
            ···
            android:padding="20dp"
            android:text="@{list[index],default=xx}"/>

        <TextView
            ···
            android:layout_marginTop="20dp"
            android:padding="20dp"
            android:text="@{map[key],default=yy}"/>

        <Button
            ···
            android:onClick="onClick"
            android:text="改變數(shù)據(jù)"/>

    </LinearLayout>
</layout>
    private ObservableMap<String, String> map;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMain12Binding activityMain12Binding = DataBindingUtil.setContentView(this, R.layout.activity_main12);
        map = new ObservableArrayMap<>();
        map.put("name", "leavesC");
        map.put("age", "24");
        activityMain12Binding.setMap(map);
        ObservableList<String> list = new ObservableArrayList<>();
        list.add("Ye");
        list.add("leavesC");
        activityMain12Binding.setList(list);
        activityMain12Binding.setIndex(0);
        activityMain12Binding.setKey("name");
    }

    public void onClick(View view) {
        map.put("name", "leavesC,hi" + new Random().nextInt(100));
    }

DataBindingUtil

獲取dataBinding的方法

 val binding: ItemRdGoodItemBinding =
                    DataBindingUtil.inflate(layoutInflater, R.layout.item_ed_good_item, parent, false)

var binding:ItemProductBinding
        init {
            binding = DataBindingUtil.bind(view)!!

DataBindingUtil.setContentView(this,R.layout.activity_main);

BindingAdapter的使用

我們之前用的都是Android自帶的監(jiān)聽或是屬性,比如text、onClick,但是如果項(xiàng)目中需要?jiǎng)討B(tài)改變ImageView的內(nèi)容,那我們應(yīng)該怎么辦呢?dataBinding給我們提供了BindingAdapter這個(gè)注解,方便我們定義自定義的屬性。
假如我們有個(gè)需求,點(diǎn)擊按鈕更換圖片,這個(gè)時(shí)候我們需要定義靜態(tài)的方法:

@BindingAdapter({"url", "name"})
public static void loadImageView(ImageView view, String url, String name) {
    Log.i("xwz--->", url + "\t" + name);
    Glide.with(view.getContext())
         .load(url)
         .into(view);
}

XML中使用

<ImageView
    android:layout_width="160dp"
    android:layout_height="160dp"
    bind:name="@{student.name}"
    bind:url="@{student.imgUrl}"/>

這里有必要解釋一下,靜態(tài)方法loadImageView里第一個(gè)參數(shù)為作用的View,這里是ImageView;后面的參數(shù)即分別對(duì)應(yīng)于@BindingAdapter里面的參數(shù)。那這里是怎么跟View聯(lián)系在一塊呢?我們發(fā)現(xiàn)XML中有這樣一行代碼bind:name="@{student.name}這里的name對(duì)應(yīng)的的@BindingAdapter注解里的參數(shù)name,并映射于ViewModel中的student.name。當(dāng)student.name值改變,就會(huì)觸發(fā)loadImageView方法,從而執(zhí)行里面的方法。
bind名稱是任意的定義的,不過(guò)要定義對(duì)應(yīng)的命名空間xmlns:bind="http://schemas.android.com/apk/res-auto"。

實(shí)現(xiàn)的效果就很簡(jiǎn)單了:

image.png

更強(qiáng)大的在于可以覆蓋Android原生的元素設(shè)置屬性,比如android:text最常見不過(guò)了

@BindingAdapter ("android:text")
public static void setText(TextView view, String text) {
    view.setText(text + "xiaweizi");
    Log.i("xwz--->", text);
}

XML:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text='@{"測(cè)試"}'/>

這個(gè)時(shí)候所有設(shè)置text的地方后綴全部加上了xiaweizi.

在java中用static的方法
在kotlin中用文件方法

一. @BindingAdapter 介紹

我們?cè)趚ml中會(huì)給view添加各種屬性,比如 textSize = xxx。databinding庫(kù)為我們提供了一種方法,就是擴(kuò)展view控件的xml屬性,而且使用起來(lái)非常簡(jiǎn)單,就是在你的項(xiàng)目里寫一個(gè)類,然后根據(jù)每一個(gè)屬性寫一個(gè)方法,使用@BindingAdapter告訴databinding框架這個(gè)方法就是用來(lái)自定義屬性的。

1。簡(jiǎn)單應(yīng)用,view動(dòng)態(tài)控制:

image.png

如上圖,我們寫了一個(gè)方法,public static void imageSrc(ImageView view, int resId) 用這個(gè)方法來(lái)給ImageView控件添加一個(gè)xml屬性:imageSrc(通過(guò)注解@BindingAdapter來(lái)指定這個(gè)屬性在xml里應(yīng)該叫什么)。 這樣在xml里我們就可以利用這個(gè)屬性來(lái)給imageView來(lái)添加圖片。

xml中使用方式如下:

image.png

我們來(lái)看imageView的最后一行, android:imageSrc="@{menuBean.src()}" 就是我們上面自定義的方法。這個(gè)menuBean 是我們?cè)诋?dāng)前xml頁(yè)面綁定的一個(gè)數(shù)據(jù)對(duì)象。這樣一來(lái),我們就可以調(diào)用這個(gè)對(duì)象的一個(gè)方法:src() 來(lái)給iageView指定圖片。

如果你要問(wèn),這樣做的意義何在?imageView自己就有設(shè)置圖片的方法啊。

我們考慮如下情況,一個(gè)imageView,圖片不是固定的,比如根據(jù)用戶級(jí)別來(lái)顯示不同圖片。這樣一來(lái),我們自定義這個(gè)屬性就派上用場(chǎng)了,從服務(wù)端獲取到用戶數(shù)據(jù)后,我們?cè)趍enuBean內(nèi)部就可以計(jì)算判斷出當(dāng)前應(yīng)該顯示的圖片是什么,并且賦值給這個(gè)成員變量。這樣進(jìn)入頁(yè)面的時(shí)imageview就會(huì)顯示我們想要的圖片。

而我們不在需要代碼中寫mageView的resource控制,而通常要控制一個(gè)view,我們都需要在activity都代碼中來(lái)寫,這就意味這這段代碼既要引用到view,又要引用到數(shù)據(jù)data對(duì)象,不符合代碼低耦合的觀點(diǎn)。而用這種方式以后,data對(duì)象里不需要知道view的存在,activity里也不需要去代碼讓這2者關(guān)聯(lián),而是成功的轉(zhuǎn)到了xml中的一條屬性。

image.png

三、雙向數(shù)據(jù)綁定

雙向綁定的意思即為當(dāng)數(shù)據(jù)改變時(shí)同時(shí)使視圖刷新,而視圖改變時(shí)也可以同時(shí)改變數(shù)據(jù)

看以下例子,當(dāng) EditText 的輸入內(nèi)容改變時(shí),會(huì)同時(shí)同步到變量 goods,綁定變量的方式比單向綁定多了一個(gè)等號(hào):android:text="@={goods.name}"

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

    <data>
        <import type="com.leavesc.databinding_demo.model.ObservableGoods"/>
        <variable
            name="goods"
            type="ObservableGoods" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".Main10Activity">

        <TextView
            ···
            android:text="@{goods.name}" />

        <EditText
            ···
            android:text="@={goods.name}" />

    </LinearLayout>
</layout>
public class Main10Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMain10Binding activityMain10Binding = DataBindingUtil.setContentView(this, R.layout.activity_main10);
        ObservableGoods goods = new ObservableGoods("code", "hi", 23);
        activityMain10Binding.setGoods(goods);
    }

}
image.png

四、事件綁定

3.1.4 導(dǎo)入的類后,就可以在表達(dá)式中使用類的靜態(tài)屬性/方法:

<data>
    <import type="com.example.MyStringUtils"/>
    <variable name="user" type="com.example.User"/>
</data>
…
<TextView
   android:text="@{MyStringUtils.capitalize(user.lastName)}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

嚴(yán)格意義上來(lái)說(shuō),事件綁定也是一種變量綁定,只不過(guò)設(shè)置的變量是回調(diào)接口而已
事件綁定可用于以下多種回調(diào)事件

  • android:onClick
  • android:onLongClick
  • android:afterTextChanged
  • android:onTextChanged

在 Activity 內(nèi)部新建一個(gè) UserPresenter 類來(lái)聲明 onClick()afterTextChanged() 事件相應(yīng)的回調(diào)方法

public class UserPresenter {

        public void onUserNameClick(User user) {
            Toast.makeText(Main5Activity.this, "用戶名:" + user.getName(), Toast.LENGTH_SHORT).show();
        }

        public void afterTextChanged(Editable s) {
            user.setName(s.toString());
            activityMain5Binding.setUserInfo(user);
        }

        public void afterUserPasswordChanged(Editable s) {
            user.setPassword(s.toString());
            activityMain5Binding.setUserInfo(user);
        }

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

    <data>
        <import type="com.leavesc.databinding_demo.model.User" />
        <import type="com.leavesc.databinding_demo.MainActivity.UserPresenter" />
        <variable
            name="userInfo"
            type="User" />
        <variable
            name="userPresenter"
            type="UserPresenter" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="20dp"
        android:orientation="vertical"
        tools:context="com.leavesc.databinding_demo.MainActivity">

        <TextView
            ···
            android:onClick="@{()->userPresenter.onUserNameClick(userInfo)}"
            android:text="@{userInfo.name}" />

        <TextView
            ···
            android:text="@{userInfo.password}" />

        <EditText
            ···
            android:afterTextChanged="@{userPresenter.afterTextChanged}"
            android:hint="用戶名" />

        <EditText
            ···
            android:afterTextChanged="@{userPresenter.afterUserPasswordChanged}"
            android:hint="密碼" />

    </LinearLayout>

</layout>

方法引用的方式與調(diào)用函數(shù)的方式類似,既可以選擇保持事件回調(diào)方法的簽名一致:@{userPresenter.afterTextChanged},此時(shí)方法名可以不一樣,但方法參數(shù)和返回值必須和原始的回調(diào)函數(shù)保持一致。也可以引用不遵循默認(rèn)簽名的函數(shù):@{()->userPresenter.onUserNameClick(userInfo)},這里用到了 Lambda 表達(dá)式,這樣就可以不遵循默認(rèn)的方法簽名,將userInfo對(duì)象直接傳回點(diǎn)擊方法中。此外,也可以使用方法引用 :: 的形式來(lái)進(jìn)行事件綁定

image.png

高級(jí)綁定

截屏2020-12-29 下午5.43.17.png
RecyclerView使用databinding出現(xiàn)數(shù)據(jù)閃爍問(wèn)題
@Override
        public void onBindViewHolder(TestDBViewHolder holder, int position) {
            holder.binding.setData(list.get(position));
            holder.binding.executePendingBindings();//加一行,問(wèn)題解決
        }    

在cycleView中添加dataBinding的問(wèn)題

http://m.itdecent.cn/p/62525ff0caac(這種是用LayoutInflater.from( parent.getContext())來(lái)生成HolderView)
http://m.itdecent.cn/p/4d30efa6b500 (這種是用DataBindingUtil.inflate(inflater, R.layout.item_fruit來(lái)生成HolderView)

兩種方式在cycleView種使用dataBinding

拼接字符串的效率問(wèn)題

JDK1.8

代碼示例

    public void test() {
        String a = "a" + "b";

        // 單行+
        String c = "c" + a + a;

        // 單行append
        StringBuilder builder = new StringBuilder("c");
        builder.append(a).append(a);

        // 多行+
        c += a;
        c += a;

        // for循環(huán)append
        for (int i = 1; i <= 100000; i++) {
            builder.append(a);
        }

        // for循環(huán)+
        for (int i = 1; i <= 100000; i++) {
            String x = c + a;
        }
        for (int i = 1; i <= 100000; i++) {
            c += a;
        }
    }

字節(jié)碼

// class version 52.0 (52)
// access flags 0x21
public class com/zhangyue/momr/api/service/Test {

  // compiled from: Test.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 7 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/zhangyue/momr/api/service/Test; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public test()V
   L0
    LINENUMBER 9 L0
    LDC "ab"
    ASTORE 1
   L1
    LINENUMBER 12 L1
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "c"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 2
   L2
    LINENUMBER 15 L2
    NEW java/lang/StringBuilder
    DUP
    LDC "c"
    INVOKESPECIAL java/lang/StringBuilder.<init> (Ljava/lang/String;)V
    ASTORE 3
   L3
    LINENUMBER 16 L3
    ALOAD 3
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    POP
   L4
    LINENUMBER 19 L4
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ALOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 2
   L5
    LINENUMBER 20 L5
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ALOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 2
   L6
    LINENUMBER 23 L6
    ICONST_1
    ISTORE 4
   L7
   FRAME FULL [com/zhangyue/momr/api/service/Test java/lang/String java/lang/String java/lang/StringBuilder I] []
    ILOAD 4
    LDC 100000
    IF_ICMPGT L8
   L9
    LINENUMBER 24 L9
    ALOAD 3
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    POP
   L10
    LINENUMBER 23 L10
    IINC 4 1
    GOTO L7
   L8
    LINENUMBER 28 L8
   FRAME CHOP 1
    ICONST_1
    ISTORE 4
   L11
   FRAME APPEND [I]
    ILOAD 4
    LDC 100000
    IF_ICMPGT L12
   L13
    LINENUMBER 29 L13
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ALOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 5
   L14
    LINENUMBER 28 L14
    IINC 4 1
    GOTO L11
   L12
    LINENUMBER 31 L12
   FRAME CHOP 1
    ICONST_1
    ISTORE 4
   L15
   FRAME APPEND [I]
    ILOAD 4
    LDC 100000
    IF_ICMPGT L16
   L17
    LINENUMBER 32 L17
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ALOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 2
   L18
    LINENUMBER 31 L18
    IINC 4 1
    GOTO L15
   L16
    LINENUMBER 34 L16
   FRAME CHOP 1
    RETURN
   L19
    LOCALVARIABLE i I L7 L8 4
    LOCALVARIABLE i I L11 L12 4
    LOCALVARIABLE i I L15 L16 4
    LOCALVARIABLE this Lcom/zhangyue/momr/api/service/Test; L0 L19 0
    LOCALVARIABLE a Ljava/lang/String; L1 L19 1
    LOCALVARIABLE c Ljava/lang/String; L2 L19 2
    LOCALVARIABLE builder Ljava/lang/StringBuilder; L3 L19 3
    MAXSTACK = 3
    MAXLOCALS = 6

  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 37 L0
    NEW com/zhangyue/momr/api/service/Test
    DUP
    INVOKESPECIAL com/zhangyue/momr/api/service/Test.<init> ()V
    INVOKEVIRTUAL com/zhangyue/momr/api/service/Test.test ()V
   L1
    LINENUMBER 38 L1
    RETURN
   L2
    LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1
}
  1. 首先String a = "a" + "b";可以看到LDC "ab",證明編譯器在編譯階段就直接做了優(yōu)化

  2. String c = "c" + a + a;可以看到優(yōu)化成了一個(gè)StringBuilder,然后做了3次append

  3. 當(dāng)你把多個(gè)+寫在同一行拼接多次時(shí),是一個(gè)StringBuilder做多次append(加號(hào)的數(shù)量+1);當(dāng)寫成多行的時(shí)候,會(huì)new多個(gè)StringBuilder

  4. 在for循環(huán)中使用+時(shí),每次循環(huán)都會(huì)new一個(gè)StringBuilder

     for (int i = 1; i <= 100000; i++) {
         builder.append(a);
     }
     for (int i = 1; i <= 100000; i++) {
         String x = c + a;
     }
     for (int i = 1; i <= 100000; i++) {
         c += a;
     }
    

第三個(gè)for循環(huán)跟前兩個(gè)相比性能差距巨大,但是看字節(jié)碼長(zhǎng)得都差不多,這是為什么呢?
第二個(gè)和第三個(gè)for循環(huán)里的代碼都等價(jià)于new StringBuilder().append(c).append(a);但是最后一個(gè)for循環(huán)的append(c)里的c是在不斷變大的,append底層調(diào)用的是System.arraycopy,在每次循環(huán)都append一個(gè)比較大的字符串,性能是很差的

測(cè)試單行StringBuilder和+拼接的運(yùn)行時(shí)間

    public void test() {
        String a = "a" + "b";
        String c = "c" + a;

        long t3 = System.currentTimeMillis();
        for (int i = 1; i <= 100000; i++) {
            StringBuilder builder = new StringBuilder(c);
            builder.append(a).append(a);
        }
        long t4 = System.currentTimeMillis();
        System.out.println(t4 - t3);

        long t1 = System.currentTimeMillis();
        for (int i = 1; i <= 100000; i++) {
            String x = c + a + a;
        }
        long t2 = System.currentTimeMillis();
        System.out.println(t2 - t1);
    }

結(jié)果可以看到基本是一樣的

24
19

測(cè)試for循環(huán)中append和+的運(yùn)行時(shí)間

    public void test() {
        String a = "a" + "b";
        String c = "c" + a;

        StringBuilder builder = new StringBuilder(c);
        long t3 = System.currentTimeMillis();
        for (int i = 1; i <= 100000; i++) {
            builder.append(a);
        }
        long t4 = System.currentTimeMillis();
        System.out.println(t4 - t3);

        long t1 = System.currentTimeMillis();
        for (int i = 1; i <= 100000; i++) {
            c += a;
        }
        long t2 = System.currentTimeMillis();
        System.out.println(t2 - t1);
    }

結(jié)果可以看到+的運(yùn)行時(shí)間特別的長(zhǎng)

9
7722
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容