DataBinding實(shí)用指南

本篇文章已授權(quán)微信公眾號(hào) guolin_blog (郭霖)獨(dú)家發(fā)布

寫(xiě)在前面

對(duì)于android開(kāi)發(fā)者而言,寫(xiě)冗余重復(fù)的代碼一直是一件吃力不討好的事情,而數(shù)據(jù)綁定技術(shù)能夠減少大量重復(fù)的代碼,可以說(shuō)是android開(kāi)發(fā)者的福音。它學(xué)習(xí)起來(lái)十分簡(jiǎn)單(相信了解過(guò)的應(yīng)該都這么覺(jué)得),但使用起來(lái)卻不那么盡如人意(對(duì)不起,binding文件未找到)。

從16年11月到現(xiàn)在,經(jīng)過(guò)這么長(zhǎng)時(shí)間的實(shí)踐,除了前4個(gè)月在踩坑之外,到現(xiàn)在都沒(méi)再遇到DataBinding相關(guān)的錯(cuò)誤,趁年前有些時(shí)間,因此總結(jié)了一下實(shí)際項(xiàng)目中使用DataBinding的一些經(jīng)驗(yàn)。

本文重點(diǎn)不在于講解DataBinding語(yǔ)法,這樣的文章夠多了。

如果你對(duì)DataBinding稍有興趣,可以看看我以前的文章告別findView和ButterKnife
如果你想學(xué)習(xí)DataBinding語(yǔ)法,推薦看看泡網(wǎng)的DataBinding專(zhuān)題或者慕課網(wǎng)的視頻

如果你正在使用DataBinding并苦惱于不能稱(chēng)心如意的使用它,那么看本文是一個(gè)不錯(cuò)的選擇。

相關(guān)代碼:

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

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

閱讀下文請(qǐng)具備DataBinding相關(guān)的基礎(chǔ)知識(shí)。

正文

  • 首先推薦一款A(yù)S插件DataBinding Support

    可以簡(jiǎn)化DataBinding的轉(zhuǎn)換操作并支持和ViewModel和與之關(guān)聯(lián)的layout文件的跳轉(zhuǎn),可以提升開(kāi)發(fā)時(shí)的效率,節(jié)省時(shí)間

    貼兩張圖看看:

convert
link

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

  • 統(tǒng)一命名的variable

    俗話說(shuō)無(wú)法不成章,對(duì)于一個(gè)團(tuán)隊(duì)而言,不管是大還是小,都需要一套合理的統(tǒng)一的命名規(guī)范,既方便相互之間的協(xié)作,也減輕了CodeReview時(shí)的困難。對(duì)實(shí)踐了Databinding的團(tuán)隊(duì)格外如此。

    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,按照官網(wǎng)的Guide,很可能會(huì)定義一些非必需的variable或者import 一些自定義的靜態(tài)類(lèi)來(lái)進(jìn)行字符串處理、View顯示隱藏等等的操作。但是這不是一個(gè)好習(xí)慣,過(guò)多的variable除了會(huì)讓你多做幾次無(wú)謂的綁定外,數(shù)據(jù)也將變得難以管理。所以盡可能少的variable和import是一種較好的實(shí)踐。

    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>
    

    數(shù)據(jù)的處理和view的顯示都用ViewModel來(lái)處理,比如:

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

    這樣的好處是減輕了layout.xml文件的復(fù)雜程度,xml文件只用來(lái)單純的展示數(shù)據(jù), 而復(fù)雜的邏輯判斷都放在了ViewModel中,并且為頁(yè)面布局?jǐn)?shù)據(jù)提供了唯一的入口,方便查看和管理數(shù)據(jù)源。

  • 避免使用復(fù)雜的表達(dá)式

    bad:

    在官方的指南里有這樣的寫(xiě)法:

    <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`]}"
    

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

    而且它有一個(gè)很大的弊端,相信很多剛?cè)腴T(mén)DataBinding的人都遇到過(guò)找不到binding文件的錯(cuò),然后被勸退,原因無(wú)外乎就是表達(dá)式寫(xiě)錯(cuò)、variable的類(lèi)路徑不對(duì)或者沒(méi)import相應(yīng)的類(lèi)(比如View)等等,都與復(fù)雜的表達(dá)式有關(guān)系,而DataBinding報(bào)錯(cuò)也不太智能,有時(shí)不能準(zhǔn)確定位,所以建議不要在xml里進(jìn)行復(fù)雜的數(shù)據(jù)綁定,這些都盡量放到ViewModel里進(jìn)行,xml只綁定簡(jiǎn)單的基礎(chǔ)數(shù)據(jù)類(lèi)型。

    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}'/>
    
  • 點(diǎn)擊事件的命名和處理

    先看看DataBinding支持的寫(xiě)法

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

    選擇很多,而且使用方法也是五花八門(mén),有的喜歡直接使用viewmodel里的方法,有的直接將Activity或者fragment作為handler,還有的可能會(huì)在activity/fragment里寫(xiě)一個(gè)內(nèi)部類(lèi)作為presenter,然后由于方法名也可以自定義,所以很可能出現(xiàn)你叫presenter.save()另一個(gè)叫(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:

    推薦的一種處理方式是使用封裝過(guò)的View.OnClickListener來(lái)統(tǒng)一處理點(diǎn)擊事件,包裹一層的目的是為了不依賴(lài)于具體實(shí)現(xiàn)。

    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)的寫(xiě)法,原因之一是比較直觀一點(diǎn),其二是需要參數(shù)view

    接著在activity/fragment中來(lái)實(shí)現(xiàn)Presenter接口,處理點(diǎn)擊事件

    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) {
              //根據(jù)id進(jìn)行區(qū)分
            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(){
            //調(diào)用viewModel的方法
              mViewModel.submit();
        }
    }
    

    @SingleClick是一個(gè)注解,作為AspectJ的切面,來(lái)防止多次點(diǎn)擊,需要將view作為參數(shù),詳細(xì)可參考文章DataBinding結(jié)合AspectJ防止多次點(diǎn)擊

    如果你使用RxJava和RxLifeCycle來(lái)處理數(shù)據(jù)和管理生命周期,那么這里的submit()方法將會(huì)更加簡(jiǎn)單。

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

    詳細(xì)情況可以看這篇文章:Retrofit及RxJava

  • 處理RecyclerView 列表項(xiàng)的數(shù)據(jù)及點(diǎn)擊事件

    RecyclerView功能極其強(qiáng)大,能做到的事情很多,網(wǎng)上已經(jīng)出現(xiàn)很多關(guān)于多類(lèi)型RecyclerView的處理方法,在使用DataBinding這一年多時(shí)間里,感受便是使用DataBinding來(lái)處理RecyclerView Item再合適不過(guò),充分做到了數(shù)據(jù)和itemView的完美分離,告別了反復(fù)、冗余的自定義Adapter,不需要關(guān)心太多無(wú)意義的事情。

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

一些技巧

  • 使用tools來(lái)進(jìn)行預(yù)覽

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

    <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的所有標(biāo)準(zhǔn)屬性,將android:換成tools:即可。同時(shí)在運(yùn)行的時(shí)候就連tools:本身都是被忽略的,不會(huì)被帶進(jìn)apk中。

  • 補(bǔ)全自定義的屬性

    比如為ImageView,你定義了一個(gè)BindingAdapter

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

    實(shí)際情況中,ImageView并沒(méi)有url這個(gè)屬性, 這時(shí)可以在attrs.xml文件中為ImageView添加這一屬性,rebuild一下項(xiàng)目,以后就能自動(dòng)補(bǔ)全屬性了

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

很多開(kāi)發(fā)者放棄DataBinding原因就在于出錯(cuò)了不容易排查錯(cuò)誤。
只顯示出很多XXBinding未找到。
如果有一定使用經(jīng)驗(yàn)的就知道只看最后一條報(bào)錯(cuò)信息就夠了。
這里介紹一種我經(jīng)常使用來(lái)排查錯(cuò)誤的方式:
在Android Studio 的terminal 里運(yùn)行

./gradlew clean assembleDebug

或者

./gradlew compileDebugJavaWithJavac

因?yàn)镈ataBinding是編譯生成代碼的,很多錯(cuò)誤都是xml中表達(dá)式寫(xiě)的有問(wèn)題導(dǎo)致的,所以運(yùn)行以上命令容易在terminal中打印出具體錯(cuò)誤的信息。這些命令對(duì)于需要編譯生成代碼的框架排查錯(cuò)誤十分有用,比如Dagger2。

./gradlew compileDebugJavaWithJavac

最后

DataBinding使用起來(lái)很簡(jiǎn)單,但是由于它沒(méi)有一個(gè)統(tǒng)一的規(guī)范和寫(xiě)法,需要靠開(kāi)發(fā)者自己去摸索和研究才能熟練運(yùn)用,而這其中又會(huì)出現(xiàn)一些小坑,所以可能導(dǎo)致剛學(xué)習(xí)的人覺(jué)得難以駕馭而被迫放棄。但是它卻是一門(mén)非常實(shí)用的技術(shù),不管你是否使用MVVM架構(gòu),單憑它可以減少很多冗余的代碼和跟RecyclerView的完美契合的優(yōu)點(diǎn)就值得去了解和使用它。

關(guān)于怎么較好的實(shí)踐,總結(jié)一哈:

  • 統(tǒng)一命名規(guī)范
  • xml文件中避免復(fù)雜的表達(dá)式
  • xml只負(fù)責(zé)展示文本數(shù)據(jù),數(shù)據(jù)的處理和view的顯示隱藏交給ViewModel去做
  • 點(diǎn)擊事件封裝一哈,在Activity/Fragment中去處理事件或調(diào)用ViewModel的方法

在經(jīng)過(guò)一年以上實(shí)踐后,總結(jié)出了以上的一些避免踩坑的方式和較好的實(shí)踐方法,希望對(duì)準(zhǔn)備學(xué)習(xí)、正在學(xué)習(xí)或者正在使用的同學(xué)一些幫助。

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

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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