DataBinding實用指南

本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家發布

寫在前面

對于android開發者而言,寫冗余重復的代碼一直是一件吃力不討好的事情,而數據綁定技術能夠減少大量重復的代碼,可以說是android開發者的福音。它學習起來十分簡單(相信了解過的應該都這么覺得),但使用起來卻不那么盡如人意(對不起,binding文件未找到)。

從16年11月到現在,經過這么長時間的實踐,除了前4個月在踩坑之外,到現在都沒再遇到DataBinding相關的錯誤,趁年前有些時間,因此總結了一下實際項目中使用DataBinding的一些經驗。

本文重點不在于講解DataBinding語法,這樣的文章夠多了。

如果你對DataBinding稍有興趣,可以看看我以前的文章告別findView和ButterKnife
如果你想學習DataBinding語法,推薦看看泡網的DataBinding專題或者慕課網的視頻

如果你正在使用DataBinding并苦惱于不能稱心如意的使用它,那么看本文是一個不錯的選擇。

相關代碼:

完整示例:https://github.com/ditclear/PaoNet

DataBinding-AspectJ:https://github.com/ditclear/DataBinding-AspectJ

閱讀下文請具備DataBinding相關的基礎知識。

正文

  • 首先推薦一款AS插件DataBinding Support

    可以簡化DataBinding的轉換操作并支持和ViewModel和與之關聯的layout文件的跳轉,可以提升開發時的效率,節省時間

    貼兩張圖看看:

convert
link

更多功能可查看此鏈接:https://plugins.jetbrains.com/plugin/9271-databinding-support

  • 統一命名的variable

    俗話說無法不成章,對于一個團隊而言,不管是大還是小,都需要一套合理的統一的命名規范,既方便相互之間的協作,也減輕了CodeReview時的困難。對實踐了Databinding的團隊格外如此。

    bad:
    <!--user_activity.xml -->
    <variable
                    name="uservm"
                    type="io.ditclear.app.viewmodel.UserViewModel"/>
    <!--student_activity.xml -->
    <variable
                    name="studentvm"
                    type="io.ditclear.app.viewmodel.StudentViewModel"/>
    
    better:
    <!--user_activity.xml -->
    <variable
                    name="vm"
                    type="io.ditclear.app.viewmodel.UserViewModel"/>
    <!--student_activity.xml -->
    <variable
                    name="vm"
                    type="io.ditclear.app.viewmodel.StudentViewModel"/>
    
  • 盡可能少的variable和import

    也許你是剛接觸DataBinding,按照官網的Guide,很可能會定義一些非必需的variable或者import 一些自定義的靜態類來進行字符串處理、View顯示隱藏等等的操作。但是這不是一個好習慣,過多的variable除了會讓你多做幾次無謂的綁定外,數據也將變得難以管理。所以盡可能少的variable和import是一種較好的實踐。

    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="presenter"
                    type="com.ditclear.app.helper.presenter.Presenter"/>
    
            <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中,并且為頁面布局數據提供了唯一的入口,方便查看和管理數據源。

  • 避免使用復雜的表達式

    bad:

    在官方的指南里有這樣的寫法:

    <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"/>
    

    或者這樣的

    android:text="@{String.valueOf(index + 1)}"
    android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
    android:transitionName='@{"image_" + id}'
    android:text="@{@string/nameFormat(firstName, lastName)}"
    android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
    android:text="@{map[`firstName`]}"
    

    需要注意的是這些都不代表著最佳實踐,只是說明有這樣的功能,支持這樣的用法。

    而且它有一個很大的弊端,相信很多剛入門DataBinding的人都遇到過找不到binding文件的錯,然后被勸退,原因無外乎就是表達式寫錯、variable的類路徑不對或者沒import相應的類(比如View)等等,都與復雜的表達式有關系,而DataBinding報錯也不太智能,有時不能準確定位,所以建議不要在xml里進行復雜的數據綁定,這些都盡量放到ViewModel里進行,xml只綁定簡單的基礎數據類型。

    better:
    <data>
        <variable name="vm" type="com.example.UserViewModel"/>
    </data>
    
    <TextView
       android:text="@{vm.capitalizeLastName}"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:hint="@{vm.index}"
       android:visibility="@{vm.showName}"
       android:transitionName='@{vm.transitionName}'/>
    
  • 點擊事件的命名和處理

    先看看DataBinding支持的寫法

    android:onClick="@{presenter.onClick()}" //1.方法引用
    android:onClick="@{()->presenter.onClick()}" //2.lamda表達式
    android:onClick="@{(view)->presenter.onClick(view)}" //3.lamda表達式
    android:onClick="@{()->presenter.onClick(item)}"http://4.帶參數lamda表達式
    android:onClick="@{(view)->presenter.onClick(view, item)}"http://5.帶參數lamda表達式
    

    選擇很多,而且使用方法也是五花八門,有的喜歡直接使用viewmodel里的方法,有的直接將Activity或者fragment作為handler,還有的可能會在activity/fragment里寫一個內部類作為presenter,然后由于方法名也可以自定義,所以很可能出現你叫presenter.save()另一個叫(v)->handler.onSave(v)的情況。

    bad:
    <layout>
    <data>
    
            <variable
                    name="vm"
                    type="io.ditclear.app.viewmodel.AnimalViewModel"/>
            
            <variable
                    name="handler"
                    type="io.ditclear.app.view.AnimalActivity"/>
    
            <variable
                    name="presenter"
                    type="io.ditclear.app.view.AnimalActivity.Presenter"
    
        </data>
    
        <LinearLayout
                tools:context="io.ditclear.app.view.AnimalActivity">
            <Button
                    android:onClick="@{vm.shoutWhat()}"/>
    
            <Button
                    android:onClick="@{(v)->handler.shout(v)}"/>
            <Button
                    android:onClick="@{()->presenter.onShout()}"/>
      </LinearLayout>
      </layout>
    
    better:

    推薦的一種處理方式是使用封裝過的View.OnClickListener來統一處理點擊事件,包裹一層的目的是為了不依賴于具體實現。

    public interface Presenter extends View.OnClickListener{   
        @Override
        void onClick(View v);
    }
    

    xml布局文件

    <layout>
    <data>
    
            <variable
                    name="vm"
                    type="io.ditclear.app.viewmodel.AnimalViewModel"/>
            <variable
                    name="presenter"
                    type="io.ditclear.app.helper.Presenter"
    
        </data>
    
        <LinearLayout
                tools:context="io.ditclear.app.view.AnimalActivity">
            <Button
                    android:id="@+id/save_btn"   
                    android:onClick="@{(v)->presenter.onClick(v)}"/>
    
            <Button
                    android:id="@+id/delete_btn"   
                    android:onClick="@{(v)->presenter.onClick(v)}"/>
            <Button
                    android:id="@+id/submit_btn"   
                    android:onClick="@{(v)->presenter.onClick(v)}"/>
      </LinearLayout>
      </layout>
    

    這里推薦使用(v)->presenter.onClick(v)的寫法,原因之一是比較直觀一點,其二是需要參數view

    接著在activity/fragment中來實現Presenter接口,處理點擊事件

    public class AnimalActivity extends AppCompatActivity implements Presenter {
    
        private AnimalActivityBinding mBinding;
      private AnimalViewModel mViewModel;
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mBinding= DataBindingUtil.setContentView(this, R.layout.animal_activity);
            Animal animal=new Animal("dog",1);
            mViewModel=new AnimalViewModel(animal);
            mBinding.setVm(viewModel);
            mBinding.setPresenter(this);
        }
    
          @SingleClick
        @Override
        public void onClick(View v) {
              //根據id進行區分
            switch (v.getId()){
                case R.id.save_btn:
                    save();
                    break;
                case R.id.delete_btn:
                    delete();
                    break;
                case R.id.submit_btn:
                    submit();
                    break;
            }
        }
      
          private void submit(){
            //調用viewModel的方法
              mViewModel.submit();
        }
    }
    

    @SingleClick是一個注解,作為AspectJ的切面,來防止多次點擊,需要將view作為參數,詳細可參考文章DataBinding結合AspectJ防止多次點擊

    如果你使用RxJava和RxLifeCycle來處理數據和管理生命周期,那么這里的submit()方法將會更加簡單。

    private void submit(){
            //調用viewModel的方法
              mViewModel.submit()
              .compose(bindToLifecycle())
              .subscribe({
                  //success
              },{
                  //error
              })
        }
    

    詳細情況可以看這篇文章:Retrofit及RxJava

  • 處理RecyclerView 列表項的數據及點擊事件

    RecyclerView功能極其強大,能做到的事情很多,網上已經出現很多關于多類型RecyclerView的處理方法,在使用DataBinding這一年多時間里,感受便是使用DataBinding來處理RecyclerView Item再合適不過,充分做到了數據和itemView的完美分離,告別了反復、冗余的自定義Adapter,不需要關心太多無意義的事情。

    詳情請查看github地址(kotlin版本):https://github.com/ditclear/BindingListAdapter

一些技巧

  • 使用tools來進行預覽

    tools可以告訴Android Studio,哪些屬性在運行的時候是被忽略的,只在設計布局的時候有效。比如我們要讓android:text屬性只在布局預覽中有效可以這樣

    <TextView
     android:id="@+id/text_main"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:textAppearance="@style/TextAppearance.Title"
     android:layout_margin="@dimen/main_margin"
     android:text="@{vm.title}"
     tools:text="I am a title" />
    

    tools可以覆蓋android的所有標準屬性,將android:換成tools:即可。同時在運行的時候就連tools:本身都是被忽略的,不會被帶進apk中。

  • 補全自定義的屬性

    比如為ImageView,你定義了一個BindingAdapter

      @BindingAdapter("url")
        public static void bindImgUrl(ImageView imageView,String url){
            Glide.with(imageView.getContext()).load(url).into(imageView);
        }
    

    實際情況中,ImageView并沒有url這個屬性, 這時可以在attrs.xml文件中為ImageView添加這一屬性,rebuild一下項目,以后就能自動補全屬性了

    <declare-styleable name="ImageView">
            <attr name="url" format="string"/>
        </declare-styleable>
    
    attr
  • 錯誤排查

很多開發者放棄DataBinding原因就在于出錯了不容易排查錯誤。
只顯示出很多XXBinding未找到。
如果有一定使用經驗的就知道只看最后一條報錯信息就夠了。
這里介紹一種我經常使用來排查錯誤的方式:
在Android Studio 的terminal 里運行

./gradlew clean assembleDebug

或者

./gradlew compileDebugJavaWithJavac

因為DataBinding是編譯生成代碼的,很多錯誤都是xml中表達式寫的有問題導致的,所以運行以上命令容易在terminal中打印出具體錯誤的信息。這些命令對于需要編譯生成代碼的框架排查錯誤十分有用,比如Dagger2。

./gradlew compileDebugJavaWithJavac

最后

DataBinding使用起來很簡單,但是由于它沒有一個統一的規范和寫法,需要靠開發者自己去摸索和研究才能熟練運用,而這其中又會出現一些小坑,所以可能導致剛學習的人覺得難以駕馭而被迫放棄。但是它卻是一門非常實用的技術,不管你是否使用MVVM架構,單憑它可以減少很多冗余的代碼和跟RecyclerView的完美契合的優點就值得去了解和使用它。

關于怎么較好的實踐,總結一哈:

  • 統一命名規范
  • xml文件中避免復雜的表達式
  • xml只負責展示文本數據,數據的處理和view的顯示隱藏交給ViewModel去做
  • 點擊事件封裝一哈,在Activity/Fragment中去處理事件或調用ViewModel的方法

在經過一年以上實踐后,總結出了以上的一些避免踩坑的方式和較好的實踐方法,希望對準備學習、正在學習或者正在使用的同學一些幫助。

畢竟對于DataBinding :使用得當,那它就是神兵利器,使用不當,那么便傷人(Code)傷己。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容