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