Android:DataBinding的一二事

ju829758 8年前
   <h2>Android:DataBinding的一二事</h2>    <h2>写在前面</h2>    <p>最近冷静了一个星期,学了大名鼎鼎的DataBinding。导师说这跟H5一样,是未来Android发展的趋势。看了一下,确实是Google官方跟着MVVM一起推出来的,所以就学了相关的知识,个别也运用到项目中。</p>    <p>不过讲心里话,不知道是不是平时的findViewById用习惯了,还是ButterKnife太好用了,一直觉得用这样的方式去绑定数据有点浑身不得劲,先学着吧。</p>    <p>所以今天来分享一下学习DataBinding过程。不过不同的是,在这里我不介绍它的使用规范或者方法,因为目前网上关于DataBinding的优秀文章很多很多,文末也会有相关链接。</p>    <p>只不过由于大部分文章都是针对Gradle1.5版本去写的,有些方法和注意事项已经不适用了,而且文章几乎只分开介绍了基本使用,并没有介绍开发中的一些实际问题,我在学的过程中也踩了不少坑,所以本篇文章主要先介绍一下新版本的不同(坑),然后用一个小的实战项目来具体展示如何使用DataBinding。</p>    <h2>站在前人的肩膀上,帮后人踩坑</h2>    <h3>初始配置</h3>    <p>第一次使用的时候需要注意,Gradle版本需要大于1.5。如果大于1.5, 只需要在当前 Module 下的 build.gradle 文件中添加如下代码即可 :</p>    <pre>  <code class="language-java">android {      ...      dataBinding {          enabled true      }      ...  }</code></pre>    <p>注意,只需要在 <strong>android{ }</strong> 里面加省略号之间的三行就行了,其他啥都不用配置。在这里,我项目用的版本是 <strong>Gradle 2.1.3</strong> 。</p>    <h3>使用注意事项</h3>    <p>1、 android:text 中只能写” @{…}”的形式</p>    <p>在其他优秀DataBinding使用教程,我们学会了像下面这样的使用规范:</p>    <pre>  <code class="language-java"><TextView      android:layout_width="wrap_content"      android:layout_height="wrap_content"      android:text="@{user.age}" /></code></pre>    <p>但是千万不能这样写:</p>    <pre>  <code class="language-java"><TextView      ...      android:text="年龄@{user.age}" />    <TextView      ...      android:text="年龄+@{user.age}" /></code></pre>    <p>对比一下吧,没有对比就没有伤害:</p>    <p><img src="https://simg.open-open.com/show/a8c3aaf423a35d54d54a26fc7d04b22f.jpg"></p>    <p>当然如果你这样写的话:</p>    <pre>  <code class="language-java"><TextView      ...      android:text="@{年龄user.age}" />    <TextView      ...      android:text="@{年龄+user.age}" /></code></pre>    <p>压根编译都过不去,请珍惜生命,善待自己。</p>    <p>2、注意 android:text 中是 String 类型</p>    <p>假如上面的 <strong>user.age</strong> 是 <strong>int</strong> 类型的话,一定要记得转成 <strong>String</strong> 类型,怎么转?当然是 <strong>String.valueOf()</strong> 了:</p>    <pre>  <code class="language-java"><TextView      ...      android:text="@{String.valueOf(user.age)}" /></code></pre>    <p>3、引用资源Bug已经被解决</p>    <p>在之前所有的文章中,都有提到 <a href="/misc/goto?guid=4959713590796310274" rel="nofollow,noindex">官方有个bug</a> ,说在引用资源时必须加上 <strong>int</strong> 才能通过编译。</p>    <p>但是经过我的测试,不加也是可以编译且正常运行的,可能这个bug已经被解决掉了:</p>    <pre>  <code class="language-java"><!--不同间距-->  <TextView      ...      android:text="@{person.name}"      android:paddingLeft="@{person.isTrue ? @dimen/largeDP : @dimen/smallDP}"/>    <!--不同字号-->  <TextView      ...      android:text="@{person.phone}"      android:textSize="@{large ? @dimen/largeSP : @dimen/smallSP}" /></code></pre>    <p>从下图可以看到,第一个行间距不同,第二行字号不同:</p>    <p><img src="https://simg.open-open.com/show/599bb967fd8ebb6bda50e1ef6d4aa85a.jpg"></p>    <p>4、include不能添加在非根结点的ViewGroup中?</p>    <p>同样的,在之前几乎所有的教程中,说 include 标签不能非根结点的 ViewGroup 中,说程序会Crash掉,但是经过我的测试,程序也能正常编译运行,估计也是一个Bug吧:</p>    <pre>  <code class="language-java"><!--根结点-->  <LinearLayout      ...      android:orientation="vertical">        <include          layout="@layout/layout_text"          bind:user="@{user}" />        <include          layout="@layout/layout_button"          bind:btClick="@{btClick}" />        <!-- 非根节点 ViewGroup 仍然有效-->      <LinearLayout          ...          android:orientation="vertical">            <include              layout="@layout/layout_text"              bind:user="@{user}" />            <include              layout="@layout/layout_button"              bind:btClick="@{btClick}" />        </LinearLayout>  </LinearLayout></code></pre>    <p>如下图,布局是一样的:</p>    <p><img src="https://simg.open-open.com/show/a8c3aaf423a35d54d54a26fc7d04b22f.jpg"></p>    <p>以上两个Bug问题是仅在 <strong>有限测试</strong> 后得出的结论,如有错误请指正,谢谢!</p>    <p>5、使用资源或者属性时,记得引包</p>    <p>什么意思呢,比如下面根据 <strong>large</strong> 的值引用不同资源/属性:</p>    <pre>  <code class="language-java"><!--显示-->  <TextView      ...       android:visibility="@{large ? View.INVISIBLE : View.VISIBLE}"/>    <!--颜色-->  <TextView      ...      android:textColor="@{large ? Color.RED : Color.YELLOW}"/></code></pre>    <p>这个时候,我们用到了 <strong>View</strong> 包和 <strong>Color</strong> 包,所以必须在 <strong>data</strong> 标签中 <strong>import</strong> 这两个包,才能显示正常:</p>    <pre>  <code class="language-java"><import type="android.view.View" />  <import type="android.graphics.Color"/></code></pre>    <p>从下图可以看到第一行没有显示,第二行是红色的:</p>    <p><img src="https://simg.open-open.com/show/8a2fbd8d5b2c5a75f197f67fb75addc4.jpg"></p>    <p>6、自定义绑定类名称注意包名</p>    <p>一般来说,编译器会给我们自动生成一个绑定类,类名与布局文件的名称一致。有些时候,我们需要自己自定义:</p>    <pre>  <code class="language-java"><import class="**.CustomBinding" /></code></pre>    <p>需要注意的是,这里 <strong>CustomBinding</strong> 之前的 * 号部分必须是项目的包名,那包名需要一直写到哪呢?</p>    <p>这个没有具体规定,比如我项目的目录是这样的:</p>    <pre>  <code class="language-java">|--com      |--dataBinding          |--adapter          |--bean          |--pojo          |--ui          |--utils</code></pre>    <p>我可以写 <strong>com.databinding.CustomBinding</strong> 也可以写 <strong>com.databinding.ui.CustomBinding</strong> ,只要在项目包中即可。当然了,不能只写个 <strong>com.CustomBinding</strong> ,这是个细节问题,大家注意一下就好。</p>    <p>7、XML文件中不能包含< >符号</p>    <p>实际上这是个很有趣的问题,开始我还没有发现,先看下面这段代码:</p>    <pre>  <code class="language-java"><!--数据层-->  <data>      <import type="android.databinding.ObservableMap" />      <variable          name="muser"          type="ObservableMap&lt;String, Object>" />  </data></code></pre>    <p>这里的 <strong>variable</strong> 类型为一个 <strong>ObservableMap</strong> ,既然是 Map 当然就有 Key 和 Value ,这里是 String 类型的 Key ,Object 类型的 Value ,但是注意这里并不是你看错了,就是这么写的。因为这里不允许使用 <strong>“< >”</strong> 这样的格式,否则会直接报错。</p>    <p>8、IDE的各种问题</p>    <p>因为DataBinding推出不是很久,用的人不是很多,听说在早前的AS版本中,IDE是没有智能提示的。但是现在的IDE已经对DataBinding有了很好的支持了,但是仍然还是有很多小的问题。</p>    <p>比如在XML文件的UI层的根结点中,一些常用的属性没法提示,连 width 和 height 有时候都打不出来,但是如果真的手打出来的话,仍然是有效的,估计是IDE的问题。</p>    <p>同样的,有时候在Activity中想要得到绑定类的时候,总是提示没有这个类,但是绑定命名却跟XML文件命名一致的,这个时候我们只要把XML文件名称随便重命名一下就行了,估计也是IDE没有反应过来。</p>    <h3>常见问题</h3>    <p>1、xx missing it</p>    <pre>  <code class="language-java">Error:Execution failed for task ':DataBindingDemo:compileDebugJavaWithJavac'.  > java.lang.RuntimeException: Found data binding errors.  ****/ data binding error ****msg:Identifiers must have user defined types from the XML file. Color is missing it  file:D:\Android\AndroidProject\Android5.0Demo\DataBindingDemo\src\main\res\layout\activity_resource.xml  loc:120:45 - 120:49  ****\ data binding error ****</code></pre>    <p>这个问题很常见,不过跟我上面说的第五点是一致的,大部分都是因为没有导包的原因,所以找不到这个 Color 。</p>    <p>解决办法就是一句话,什么Missing导什么。</p>    <p>2、Could not find method XX</p>    <p>这也是个细节性问题了,比如下面这段代码(有省略):</p>    <pre>  <code class="language-java"><variable      name="btclick"      type="android.view.View.OnClickListener" />        ...    <!--点击事件-->      <Button          ...          android:onClick="btclick" /></code></pre>    <p>这里就是给按钮定义一个监听,一般我们写 <strong>android:onClick</strong> 的时候直接写方法名就可以了,但是这里如果我们直接写的话,能编译通过,但是一点击就会Crash,错误如下:</p>    <p>java.lang.IllegalStateException: Could not find method btclick(View) in a parent or ancestor Context for android:onClick attribute defined on view class android.support.v7.widget.AppCompatButton</p>    <p>提示说没有找到方法,解决办法就是 <strong>android:onClick=”@{btclick}”</strong> ,没什么好解释的,细心细心。</p>    <p>3、NullPoint怎么办</p>    <p>这个问题也很有趣,刚接触DataBinding的时候,我也有相同的疑问,但是实际上,DataBinding不会出现NollPoint,至少在已经绑定的对象上不会出现。</p>    <p>为什么呢?下面这是 <a href="/misc/goto?guid=4959635414380572581" rel="nofollow,noindex">Data Binding(数据绑定)用户指南</a> 给出的解释:</p>    <p>“Data Binding代码生成时自动检查是否为nulls来避免出现null pointer exceptions错误。例如,在表达式@{user.name}中,如果user是null,user.name会赋予它的默认值(null)。如果你引用user.age,age是int类型,那么它的默认值是0。”</p>    <p>可以看到,DataBinding已经给我们处理好了,所以不用担心这个问题。</p>    <p>4、编译出错,控制台一大串错误信息怎么办</p>    <p>在编译过程中,如果有错,控制台会打印一大串问题,这个时候只需要看错误信息的最后一条就行了。</p>    <p>而且一般来说,关键性错误信息都是下面这样的格式:</p>    <pre>  <code class="language-java">****/ data binding error ****  msg:...  file:...  ****\ data binding error ****</code></pre>    <h2>举个栗子</h2>    <p>好了,大概了解了DataBinding的使用方式,现在来用一个小小的例子来说具体如何在项目中使用它。</p>    <p>关于下面的例子很简单,但是有几点说明:</p>    <ul>     <li>只说DataBinding相关部分;</li>     <li>特地选用了比较复杂的RecyclerView;</li>     <li>特地在item中添加图片和文字,图片来源为Url;</li>     <li>重点在于RecyclerView的动态绑定。</li>    </ul>    <p>先来看一下item中的布局吧,也是精简了一部分:</p>    <pre>  <code class="language-java"><layout       xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:app="http://schemas.android.com/apk/res-auto">        <data>          <variable              name="news"              type="com.databinding.bean.NewsBean" />      </data>        <android.support.v7.widget.CardView          ...>            <ImageView              .../>            <TextView              ...              android:text="@{news.title}"/>            <TextView              ...              android:text="@{news.desc}"/>            <TextView              ...              android:text="@{news.time}"/>        </android.support.v7.widget.CardView>  </layout></code></pre>    <p>可以看到,这里只有TextView使用了DataBinding,图片并没有设置,让我们来看看在复杂的布局中如何使用动态绑定。</p>    <p>因为是一个列表数据,item是复用的,所以没办法直接绑定,那么就给ViewHolder绑定。</p>    <p>直接上源码吧,难度不大:</p>    <pre>  <code class="language-java">public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.NewsViewHolder> {        private List<NewsBean> list;      private Context context;        public NewsAdapter(Context context, List<NewsBean> list) {          this.list = list;          this.context = context;      }        @Override      public NewsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {          View itemView = LayoutInflater.from(parent.getContext())                  .inflate(R.layout.item_news, parent, false);          return new NewsViewHolder(itemView);      }        @Override      public void onBindViewHolder(NewsViewHolder holder, int position) {          //给ViewHolder绑定          holder.bind(list.get(position));          //加载图片          Picasso.with(context).load(list.get(position).getPicUrl()).into(holder.itemPicImage);      }        @Override      public int getItemCount() {          return list.size();      }        public class NewsViewHolder extends RecyclerView.ViewHolder {            //绑定类          private ItemNewsBinding inBinding;          private ImageView itemPicImage;            public NewsViewHolder(View itemView) {              super(itemView);              //为每一个item设置              inBinding = DataBindingUtil.bind(itemView);              itemPicImage = (ImageView) itemView.findViewById(R.id.iv_item_pic);          }            /**           * 绑定方法           *           * @param news Bean对象           */          public void bind(@NonNull NewsBean news) {              inBinding.setNews(news);          }      }</code></pre>    <p>如果你仔细看了上面的代码,你会发现其实只是多了三件事情:</p>    <ul>     <li>ViewHolder中创建绑定类;</li>     <li>利用绑定类设置相关内容;</li>     <li>绑定ViewHolder的时候绑定上相关内容。</li>    </ul>    <p>就这么多,来看看效果图吧:</p>    <p><img src="https://simg.open-open.com/show/d023451528f2e06c8db6d7e4890b83e3.png"></p>    <p>其实例子本身很简单,但是想说的不是例子本身。从代码中我们可以发现,图片的加载并没有使用DataBinding,而是用Picasso直接加载。当然也可以使用DataBinding的静态方法去直接展示,但是感觉有点怪怪的。而且如果这个item的布局十分复杂,考虑的情况很多的话,这种情况显然很麻烦很麻烦。何况这里还没有考虑点击事件。</p>    <p>所以总的来说就是,不熟悉就慎用慎用。</p>    <h2>总结</h2>    <p>因为是刚接触,对这种数据绑定的方式各种不适应,而且很多方法可能暂时不知道,就像上面复杂的布局不知道有没有更简单的方法去处理。反正给我的感觉就是有点麻烦,也不准备继续深入学习DataBinding了。</p>    <h3>优点</h3>    <ol>     <li>节省了给View设置Id的工作;</li>     <li>节省了findViewById的工作;</li>     <li>大部分情况都能很快捷的处理;</li>     <li>没有空指针的问题;</li>     <li>还有很多优点待发掘。</li>    </ol>    <h3>缺点</h3>    <ol>     <li>ButterKnife好像更方便;</li>     <li>复杂的布局处理起来很困难;</li>     <li>IDE不够完善。</li>     <li>还有很多缺点待发现。</li>    </ol>    <h3> </h3>    <p>来自:http://www.iamxiarui.com/2016/08/28/android:databinding的一二事/</p>    <p> </p>