Android DataBinding框架的使用以及簡單分析

轉載請標明出處http://www.itdecent.cn/p/fb8d33168f57

前言:畢業(yè)快3年了,出來工作之后一直是傾向于 Android相關平臺逆向以及廣告SDK的開發(fā)和學習,一直想補一下Android開發(fā)相關的知識點,國慶前群里老哥們聊起了MVVM和DataBinding,我心里終究是按耐不住好奇心了,于是乎也打開了瀏覽器,學了起來,其實本來一開始是想學MVVM先,畢竟databinding也是配合MVVM的一套工具,一種實現(xiàn)。但是機緣巧合先學起了這個,后面再補一篇MVVM的學習。

1.DataBinding有什么用,能解決什么問題

DataBinding是一個Google官方發(fā)布的框架,是為了應對日常開發(fā)中數(shù)據(jù)層和UI層之間的傳遞的。我們日常開發(fā)中需要自己手動去寫findViewById,setOnClickListener這類厚重的代碼。這個框架就可以簡便的實現(xiàn)數(shù)據(jù)和UI的傳遞,UI對數(shù)據(jù)的監(jiān)聽,UI的刷新。

2.DataBinding的使用

DataBinding作為官方直推,更是不用添加依賴使用,而是gradle直接支持了,我們只需要在項目app下build.gradle之中的android下添加下面代碼就可以使用,如果添加了之后無法使用,可以嘗試升級AS版本。

android {
    dataBinding {
        enabled = true;
    }
}

3.DataBinding的代碼實現(xiàn)

3.1.視圖與數(shù)據(jù)變量的綁定
<layout xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
        <variable
            name="text"
            type="com.llyyyw.databinding.TextBean" />
        <variable
            name="activity"
            type="com.llyyyw.databinding.MainActivity" />
    </data>
<!--往下開始常規(guī)的UI布局-->
</layout>

DataBinding對XML布局格式有一定的要求,需要在xml最外層使用layout包裹,之后里面分別寫入data以及正常UI布局。其中data標簽中聲明你變量名以及變量類,我這里就是聲明了自己寫的TextBean類,命名為text。供下面使用。

3.2.視圖數(shù)據(jù)的使用以及控件事件監(jiān)聽

DataBinding會根據(jù)activity和xml自動生成一個ViewDataBinding類,這個類負責了view的管理和邏輯,事件監(jiān)聽以及變量的管理。我這個類是放在build文件夾下,這個看看它是如何實現(xiàn)的。


ViewDataBinding.jpg

之后在想要操作view的類中進行viewDataBinding的實例化,設置數(shù)據(jù)。之后XML可以通過@{}的格式配合變量名獲取,比方說@{text.title}。

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        textBean = new TextBean();
        textBean.setTitle("dataBinding你好");
        textBean.setText("dataBinding你好");
        viewDataBinding.setActivity(this);
        viewDataBinding.setText(textBean);
    }
 <TextView
            android:id="@+id/tv_title2"
            android:layout_marginTop="20dp"
            android:layout_marginBottom="20dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{text.title}" />

這個text就是傳遞過去的“dataBinding你好”。
除了數(shù)據(jù)的傳遞,還有對于view事件的監(jiān)聽,比如點擊事件。

<TextView
            android:id="@+id/tv_title2"
            android:layout_marginTop="20dp"
            android:layout_marginBottom="20dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{activity::updateTitle}"
            android:text="@{text.title}" />

這里就是調用了傳遞進去的activity中的updateTitle方法,這里activity::updateTitle這種格式的調用,規(guī)定這個方法必須要是一個有View參數(shù)的方法。

public void updateTitle(View view) {
        textBean.setTitle("dataBinding你好" + i);
        i++;
    }

當然這個activity完全可以是你想要調用的任意類,updateTitle也可以是類里的任意方法。只要你在data里綁定了數(shù)據(jù)。
除此之外,還有其他比如@{() -> activity.updateTitle(obj)},這種寫法無論帶不帶參數(shù),方法參數(shù)對應上即可。所以其實也可以實現(xiàn)在xml中傳遞參數(shù)到方法里。

3.3.BindingAdapter,視圖控件對于數(shù)據(jù)變化監(jiān)聽。

BindingAdapter是DataBinding提供的一個用于控件對于變量發(fā)生變化的監(jiān)聽的。

@BindingAdapter("android:text_title_check")
    public static void checkTitle(TextView view, CharSequence text) {
        CharSequence oldText = view.getText();
        if (!haveContentsChanged(text, oldText)) {
            return; // 數(shù)據(jù)沒有變化不進行刷新視圖
        }
        view.setText(text);
    }

這里@BindingAdapter()中可以根據(jù)你自己的需要進行命名,方法名也可以隨意,但是要注意參數(shù)以及XML中調用時的對應。

<TextView
            android:id="@+id/tv_title2"
            android:layout_marginTop="20dp"
            android:layout_marginBottom="20dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text_title_check="@{text.title}"/>

這樣子,當傳入BindingAdapter標注的方法的變量發(fā)生變化時,就會調用該方法,該方法可以進行一定的邏輯操作,比如圖片的加載,文本的判定。

3.4.單向和雙向數(shù)據(jù)綁定

前面我們使用了單向數(shù)據(jù)綁定,數(shù)據(jù)變化引起視圖Textview中的更新。
DataBinding提供了BaseObservable、ObservableField、ObservableCollection三種方式實現(xiàn)。

我這里使用BaseObservable為例。

public class TextBean extends BaseObservable implements Serializable {
    private String title;
    private String text;


    @Bindable
    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
        notifyPropertyChanged(BR.title);
    }

    @Bindable
    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
        // 需要手動刷新
        notifyPropertyChanged(BR.text);
    }
}

Bindable注解get方法,notifyPropertyChanged在set方法刷新。也可以使用ObservableField這個類,對BaseObservable綁定和解綁的進一步封裝。
BaseObservable是官方實現(xiàn)的觀察器,該接口自己實現(xiàn)了綁定和解綁,我們只需要專注于數(shù)據(jù)的傳遞更新。
這樣子配合@{}就可以達到數(shù)據(jù)變化,視圖也實時變化。

但是我現(xiàn)在有一個需求,我現(xiàn)在有一個輸入框editText,或者一個多選框,這個框的結果我必須知道并拿來使用,正常的開發(fā)中,我們會通過獲取控件之后getText之類的方法獲取框中的實時內容,很麻煩。但是DataBinding提供了我們雙向綁定。我們只需要通過@={}的寫法,就可以實現(xiàn)
視圖改變==>數(shù)據(jù)實時改變。
數(shù)據(jù)改變==>視圖實時改變。

<EditText
            android:id="@+id/et_title"
            android:layout_marginTop="20dp"
            android:layout_marginBottom="20dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={text.title}" />

效果如下:
demo.gif

像這樣,點擊textview的事件中對數(shù)據(jù)進行修改,由于textview單向綁定,editText雙向綁定,都指向同一個數(shù)據(jù),所以都實時的改變。editText修改的時候也同理。

3.5.DataBinding自提供變量

如果現(xiàn)在我們有一個需求,需要在布局中使用上下文的變量,那我們該怎么做。正常思路就是繼續(xù)使用data,Variable標簽。
其實DataBinding自己也提供了一些Variable的,比方說context。 是可以直接使用的. 代表View的getContext()。

<TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="@{()->activity.packageName(context.applicationContext.packageName)}"
        android:text="@{context.applicationContext.packageName}"/>

我這里就配合上面講的控件事件,將context.applicationContext.packageName作為一個string參數(shù),傳遞給了packageName方法。

 public void packageName(String packageName) {
        Toast.makeText(this, packageName, Toast.LENGTH_SHORT).show();
    }
contextToast.jpg
3.6.DataBinding和include的使用

關于include的使用,其實主要是主activity和包含布局之間要使用 bind來傳遞數(shù)據(jù)。包含布局中也要用data和variable實現(xiàn)數(shù)據(jù)變量和view的綁定。注意bind:xxx和variable name="xxx",這里兩者xxx必須是一樣的才能匹配到。

<include
        layout="@layout/include_main"
        bind:textText="@{text.text}"/>
<layout>
   <data>
        <variable
            name="textText"
            type="String" />
    </data>

    <TextView
        tools:context=".MainActivity"
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{textText}">
    </TextView>
</layout>

4.DataBinding的簡單分析

DataBinding的簡單使用可以說是很簡單,省去了很多代碼,簡化了數(shù)據(jù)和視圖之間的交互。不禁我們就想知道,他其實是怎么實現(xiàn)的,我也很好奇,然后點開了源碼,看的云里霧里,但是大概是,DataBinding對自己定義的結構的xml進行了解析,配合activity創(chuàng)建ViewDataBinding,獲取各種view以及監(jiān)聽數(shù)據(jù)的過程。

ActivityMainBinding對象的生成

由DataBinding對象獲取的方法DataBindingUtil.setContentView最終追溯到這個方法。

public class DataBindingUtil {
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
    }
}

這個方法最終返回的其實就是相對應ActivityMainBindingImpl的對象。
ActivityMainBindingImpl.jpg

ViewDataBinding實例化:

 protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {
        mBindingComponent = bindingComponent;
        mLocalFieldObservers = new WeakListener[localFieldCount];
        this.mRoot = root;
        if (Looper.myLooper() == null) {
            throw new IllegalStateException("DataBinding must be created in view's UI Thread");
        }
        if (USE_CHOREOGRAPHER) {
            mChoreographer = Choreographer.getInstance();
            mFrameCallback = new Choreographer.FrameCallback() {
                @Override
                public void doFrame(long frameTimeNanos) {
                    mRebindRunnable.run();
                }
            };
        } else {
            mFrameCallback = null;
            mUIThreadHandler = new Handler(Looper.myLooper());
        }
    }

并且ViewDataBinding會通過view創(chuàng)建對應的WeakListener,弱引用類型接口作為觀察者,利于被回收,同時很好的處理內存泄露的情況。之后則是update時的邏輯。

@Override
    public boolean setVariable(int variableId, @Nullable Object variable)  {
        boolean variableSet = true;
        if (BR.activity == variableId) {
            setActivity((com.llyyyw.databinding.MainActivity) variable);
        }
        else if (BR.text == variableId) {
            setText((com.llyyyw.databinding.TextBean) variable);
        }
        else {
            variableSet = false;
        }
            return variableSet;
    }
    public void setText(@Nullable com.llyyyw.databinding.TextBean Text) {
        updateRegistration(0, Text);
        this.mText = Text;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.text);
        super.requestRebind();
    }
/**
     * @hide
     */
    protected boolean updateRegistration(int localFieldId, Observable observable) {
        return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
    }

    /**
     * @hide
     */
    protected boolean updateRegistration(int localFieldId, ObservableList observable) {
        return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
    }

    /**
     * @hide
     */
    protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
        return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER);
    }
private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
        @Override
        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
            return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
        }
    };

    private static final CreateWeakListener CREATE_LIST_LISTENER = new CreateWeakListener() {
        @Override
        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
            return new WeakListListener(viewDataBinding, localFieldId).getListener();
        }
    };

    private static final CreateWeakListener CREATE_MAP_LISTENER = new CreateWeakListener() {
        @Override
        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
            return new WeakMapListener(viewDataBinding, localFieldId).getListener();
        }
    };

最終流程是:

源代碼全部不好貼出來,口述一下。相對應ActivityMainBindingImpl的實例化,數(shù)據(jù)綁定在視圖UI線程中創(chuàng)建,以及調用父類ViewDataBinding的executePendingBindings直到executeBindings這個抽象方法,指向的是ActivityMainBindingImpl的executeBindings方法,注冊WeakListener監(jiān)聽。
當我們設置數(shù)據(jù),BindingImpl的setVariable調用控件set方法,調用updateRegistration監(jiān)聽。當BaseObservable變化,觸發(fā)notifyPropertyChanged,觸發(fā)回調,也就是對應的WeakListener。就會更新視圖。

這里我們講完了數(shù)據(jù)變化從而view變化的原理,那view變化引起數(shù)據(jù)變化呢。
首先要知道ViewDataBinding是如何具體保存view的。前面說明了ActivityMainBindingImpl才是整個DataBinding的核心實現(xiàn)類,也調用了它的executeBindings方法,這個方法里其實就是對控件的一系列操作,控件的設值,控件的各種監(jiān)聽事件。

        if ((dirtyFlags & 0x15L) != 0) {
            // api target 1
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etTitle, textTitle);
            com.llyyyw.databinding.dbAdapter.TextBindingAdapter.checkTitle(this.tvTitle2, textTitle);
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvTitle2, textTitle);
        }
        if ((dirtyFlags & 0x10L) != 0) {
            // api target 1
            androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.etTitle, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, etTitleandroidTextAttrChanged);
            this.tvTitle.setOnClickListener(mCallback1);
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvTitle, contextApplicationContextPackageName);
        }
        if ((dirtyFlags & 0x19L) != 0) {
            // api target 1
            this.mboundView01.setTextText(textText);
        }
        if ((dirtyFlags & 0x12L) != 0) {
            // api target 1
            this.tvTitle2.setOnClickListener(activityUpdateTitleAndroidViewViewOnClickListener);
        }
private androidx.databinding.InverseBindingListener etTitleandroidTextAttrChanged = new androidx.databinding.InverseBindingListener() {
        @Override
        public void onChange() {
            // Inverse of text.title
            //         is text.setTitle((java.lang.String) callbackArg_0)
            java.lang.String callbackArg_0 = androidx.databinding.adapters.TextViewBindingAdapter.getTextString(etTitle);
            // localize variables for thread safety
            // text.title
            java.lang.String textTitle = null;
            // text
            com.llyyyw.databinding.TextBean text = mText;
            // text != null
            boolean textJavaLangObjectNull = false;



            textJavaLangObjectNull = (text) != (null);
            if (textJavaLangObjectNull) {




                text.setTitle(((java.lang.String) (callbackArg_0)));
            }
        }
    };

只看其中的EditText控件可以發(fā)現(xiàn),DataBinding是這樣通過etTitleandroidTextAttrChanged這個監(jiān)聽器去實時獲取控件的值進而對數(shù)據(jù)進行實時更新的。

5.總結

DataBinding的使用并不難,當然DataBinding還有許多其他的使用。深入去了解DataBinding可以發(fā)現(xiàn)里面的大奧秘,我這里云里霧里也只能理解這么冰山一角。學習使我快樂!

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容