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)}"//4.带参数lamda表达式
    android:onClick="@{(view)->presenter.onClick(view, item)}"//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阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容