| 注册
请输入搜索内容

热门搜索

Java Linux MySQL PHP JavaScript Hibernate jQuery Nginx
hkxf9949
8年前发布

MVVM之DataBinding学习笔记

   <h2>前言</h2>    <p>...想了半分钟,好像并没有啥需要特别声明的,能翻到这篇文章的人,相信也早已了解了DataBinding是个什么东西,所以还是简单粗暴点儿,就当给自己留个笔记...开撸吧!</p>    <h2>配置</h2>    <ul>     <li>确保sdk的support包更新到了最新版</li>     <li>在对应module的build.gradle文件中进行如下配置(需AS版本1.5以上) <pre>  <code class="language-java">android {    ...    dataBinding {        enabled = true    }  }</code></pre> <h2>基本使用</h2> <h2>数据绑定</h2> <h3>Activity</h3> <strong>先上layout代码</strong></li>    </ul>    <pre>  <code class="language-java"><?xml version="1.0" encoding="utf-8"?>  <layout xmlns:android="http://schemas.android.com/apk/res/android">        <!--如果本页不需要绑定数据,data标签可以省略-->      <data class="DataBingMain">          <import type="lxf.widget.util.AppUtils"/>          <variable              name="user"              type="lxf.androiddemos.model.UserEntity"/>      </data>        <LinearLayout          xmlns:tools="http://schemas.android.com/tools"          tools:context="lxf.androiddemos.ui.DatabindingActivity"          android:layout_width="match_parent"          android:layout_height="match_parent"          android:padding="16dp"          android:background="@mipmap/bg_robot"          android:orientation="vertical"          >          <TextView              style="@style/text_base"              android:text="@{`name:`+user.name}"/>           <TextView              style="@style/text_base"              android:text="@{user.sex}"/>          <TextView              style="@style/text_base"              android:text="@{String.valueOf(user.age)}"/>          <TextView              style="@style/text_base"              android:text="@{user.initType(user.type)}"/>      </LinearLayout>  </layout></code></pre>    <ol>     <li>layout的编写方式改变:新增layout和data标签,layout为根布局,包含data和ui布局两部分。data即要绑定的数据model,ui布局即我们以前写法中的根布局。</li>     <li>data标签:      <ul>       <li>使用DataBinding编写布局,系统会自动生成一个继承ViewDataBinding类,而class属性可以指定这个类的名字,如果不指定,则会根据xml的名字自动生成。</li>       <li>variable可以设置多个。</li>       <li>通俗的讲,name即变量名,可以在下面直接引用,同时会在自动生成的ViewDataBinding类中自动生成setXXX和getXXX方法,用来绑定数据。</li>       <li>type即我们的数据model。</li>       <li>支持import,导入后可以在@{}中直接使用,方式同java。</li>      </ul> </li>     <li>@{}语法中支持大部分的java操作,当然最好不要写太复杂的语句,如果有这个需求,可以在java类中写一个方法,然后在此调用:      <ul>       <li>运算符: + - / * % () && || & | ^ >> >>> << == > < >= <=</li>       <li>字符串拼接 + (注意字符串要用``括起来,esc下面那个键)</li>       <li>instanceof</li>       <li>方法调用</li>       <li>res资源访问</li>       <li>数组访问 []</li>       <li>三目运算 表达式1 ? 表达式2 : 表达式3</li>       <li>聚合判断 表达式1 ?? 表达式2 (表达式1为null,则返回表达式2)</li>       <li>等等...</li>      </ul> </li>    </ol>    <p>activity代码</p>    <p>通过DataBindingUtil.setContentView方法代替原来的setContentView。</p>    <pre>  <code class="language-java">@Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);            //1.获取ViewDataBinding对象          DataBingMain dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_databinding);            //2.获取数据          UserEntity user = new UserEntity();          user.setName("lxf");          user.setSex("man");          user.setAge(25);          user.setType(1);            //3.绑定数据          dataBinding.setUser(user);          //dataBinding.setVariable(BR.user,user);      }</code></pre>    <p>model代码</p>    <pre>  <code class="language-java">package lxf.androiddemos.model;    public class UserEntity {      private String name;      private String sex;      private int age;      private int type;        public String initType(int type){          String result;          switch (type){              case 1:                  result = "程序猿";                  break;              case 2:                  result = "程序猿的天敌";                  break;              default:                  result = "无业游民";                  break;          }          return result;      }        //setter   getter方法略  }</code></pre>    <p>activity和model代码很简单,就不需要解释了。</p>    <h3>Fragment</h3>    <p>看到这里应该有个疑问:fragment中没有setContentView方法,该怎么办?</p>    <p>所幸DataBinding库还提供了另外一个初始化布局的方法:DataBindingUtil.inflate()。</p>    <pre>  <code class="language-java">@Override      public View onCreateView(LayoutInflater inflater, ViewGroup container,                               Bundle savedInstanceState) {          ViewDataBinding binding = DataBindingUtil.inflate(inflater,R.layout.fragment_blank,container,false);          return binding.getRoot();      }</code></pre>    <p>xml布局的写法同activity。</p>    <h2>列表绑定</h2>    <p>在此已RecyclerView为例。</p>    <h3>单布局</h3>    <p>先看item布局:</p>    <pre>  <code class="language-java"><?xml version="1.0" encoding="utf-8"?>  <layout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:app="http://schemas.android.com/apk/res-auto">        <data>          <variable              name="item"              type="lxf.androiddemos.model.MainRecyclerItem"/>      </data>        <android.support.v7.widget.CardView          android:layout_width="match_parent"          android:layout_height="60dp"          android:layout_marginLeft="16dp"          android:layout_marginRight="16dp"          android:layout_marginTop="16dp"          app:cardElevation="5dp"          android:onClick="@{item.onItemClick}"          >            <TextView              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:layout_gravity="center"              android:textSize="18sp"              android:textColor="@color/text_green_bg"              android:text="@{item.content}"/>      </android.support.v7.widget.CardView>  </layout></code></pre>    <p>这里值得我们关注的有两个地方,一个就是TextView上的数据绑定,一个是父布局上的onClick属性,可以通过这种方式来设置item点击事件,说白了其实就是调用MainRecyclerItem中的一个方法,我们可以通过getter方法很方便的知道当前item的具体数据,具体实现请往下看。</p>    <p>model实体类</p>    <pre>  <code class="language-java">public class MainRecyclerItem {      public static final String[] items = new String[]{"ViewDragHelper", "自定义Behavior", "二维码", "DataBinding"};        private String content;        public void onItemClick(View view) {          Intent intent = null;          switch (getContent()) {              case "ViewDragHelper":                  intent = new Intent(view.getContext(), ViewDragHelperActivity.class);                  break;              case "自定义Behavior":                  intent = new Intent(view.getContext(), BehaviorActivity.class);                  break;              case "二维码":                  intent = new Intent(view.getContext(), ZxingActivity.class);                  break;              case "DataBinding":                  intent = new Intent(view.getContext(), DatabindingActivity.class);                  break;          }          if (intent != null)              view.getContext().startActivity(intent);      }        public String getContent() {          return content;      }        public void setContent(String content) {          this.content = content;      }  }</code></pre>    <p>adapter</p>    <pre>  <code class="language-java">class RecyclerBindingViewHolder extends RecyclerView.ViewHolder {      ViewDataBinding binding;        private RecyclerBindingViewHolder(ViewDataBinding binding) {          super(binding.getRoot());          this.binding = binding;      }        static RecyclerBindingViewHolder createViewHolder(ViewGroup parent, int layoutId) {          ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),layoutId,parent,false);          return new RecyclerBindingViewHolder(binding);      }      public abstract class BaseRecyclerBindingAdapter extends RecyclerView.Adapter<RecyclerBindingViewHolder> implements ChangeDataLinstener{      protected List<Object> mData;        public BaseRecyclerBindingAdapter(List<Object> list) {          mData = (list != null) ? list : new ArrayList<>();      }        @Override      public RecyclerBindingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {          return RecyclerBindingViewHolder.createViewHolder(parent,getItemLayoutId(viewType));      }        @Override      public void onBindViewHolder(RecyclerBindingViewHolder holder, int position) {          //绑定数据          holder.binding.setVariable(getItemVariableId(),mData.get(position));          holder.binding.executePendingBindings();      }        @Override      public int getItemCount() {          return mData.size();      }        public void setmData(List<Object> mData) {          this.mData = mData;          notifyDataSetChanged();      }        //item布局id      public abstract int getItemLayoutId(int viewType);        //对应item布局里面data标签中的name,会自动生成一个BR.xxx属性,类似于R文件      public abstract int getItemVariableId();    }</code></pre>    <p>我们这里把adapter写成了一个抽象类,如果没有什么很奇葩的要求,可以算一个通用adapter了,可以看到它没有任何的findviewbyid和set数据,一切都在布局中封装好了,实现非常的简洁。</p>    <h3>多布局</h3>    <p>如果我们想加一个头部文件,可以这样:</p>    <pre>  <code class="language-java"><?xml version="1.0" encoding="utf-8"?>  <layout xmlns:android="http://schemas.android.com/apk/res/android">        <data>          <variable              name="item"              type="lxf.androiddemos.model.MainRecyclerHeader"/>      </data>        <TextView          android:layout_width="match_parent"          android:layout_height="80dp"          android:gravity="center"          android:text="@{item.header}"          android:background="@color/text_orange_bg">        </TextView>  </layout></code></pre>    <p>注意和之前的item布局有个相通之处,就是data标签的name属性值是一样的,然后通过getItemViewType实现我们的不同布局即可。</p>    <pre>  <code class="language-java">datas = new ArrayList<>();  MainRecyclerHeader header = new MainRecyclerHeader();  header.setHeader("我是头部文件");  datas.add(header);  for (int i = 0; i < MainRecyclerItem.items.length; i++) {      MainRecyclerItem item = new MainRecyclerItem();      item.setContent(MainRecyclerItem.items[i]);      datas.add(item);  }      BaseRecyclerBindingAdapter bindingAdapter = new BaseRecyclerBindingAdapter(datas) {              @Override              public int getItemLayoutId(int viewType) {                  return viewType;              }                @Override              public int getItemVariableId() {                  return BR.item;//对应item布局里面data标签中的name              }                @Override              public int getItemViewType(int position) {                  if (position == 0)                      return R.layout.header_recycler_main;                  else                      return R.layout.item_recycler_main;              }          };</code></pre>    <h2>事件绑定</h2>    <p>事件绑定说白了,其实就是一种特殊的变量绑定,或者说是一个方法的调用。</p>    <pre>  <code class="language-java"><?xml version="1.0" encoding="utf-8"?>  <layout xmlns:android="http://schemas.android.com/apk/res/android">        <!--如果本页不需要绑定数据,data标签可以省略-->      <data class="DataBingMain">          <variable              name="user"              type="lxf.androiddemos.model.UserEntity"/>          <variable              name="util"              type="lxf.androiddemos.test.TestUtil"/>      </data>        <LinearLayout          ...          >          <Button              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:onClick="@{util.onBtnClick}"              />          <EditText              android:layout_width="match_parent"              android:layout_height="wrap_content"              android:onTextChanged="@{util.onTextChanged}"              />      </LinearLayout>  </layout>      public class TestUtil {        public void onBtnClick(View view){          Toast.makeText(view.getContext(),"onBtnClick",Toast.LENGTH_SHORT).show();      }      public void onTextChanged(CharSequence s, int start, int before, int count){          System.out.println(s);      }  }</code></pre>    <p>需要注意的是通过onclick这种方式绑定的事件,实现方法中一定要传入view参数(类似于传统的onClick方法),否则编译会报错。</p>    <p>同样的官方文档提到,你也可以用这种方式来绑定一些比较偏门的监听,比如上面的onTextChanged,方法参数必须与传统的onTextChanged参数一模一样,否则编译报错,这种方式可以使你只监听onTextChanged一个方法,而非TextWatcher的三个方法,另外EditText本身是没有android:onTextChanged这个属性的,具体实现原理需要先理解一下什么是databinding的自定义属性,会在后文提到。</p>    <h2>进阶使用</h2>    <h2>数据更新</h2>    <p>在很多情况下,我们需要动态去设置相关数据,DataBinding为我们提供了两种方式来实现它。</p>    <p>方法一</p>    <ol>     <li>实体类继承BaseObservable,或者自己实现Observable</li>     <li>在需要刷新的属性的get方法上添加@Bindable注解,此时会自动生成BR类。(这里有个坑,很多时候BR文件不会自动生成,此时需要重启AS...请让我先默默地日一波dog)</li>     <li>在相应的set方法里调用notifyPropertyChanged(BR.xxx)进行刷新。</li>    </ol>    <pre>  <code class="language-java">package lxf.androiddemos.model;    import android.databinding.BaseObservable;  import android.databinding.Bindable;  import android.view.View;    import lxf.androiddemos.BR;    public class UserEntity extends BaseObservable{      private String name;      private String sex;      private int age;      private int type;        ...        public void addAge(View view) {          setAge(getAge() + 1);      }        @Bindable      public int getAge() {          return age;      }        public void setAge(int age) {          this.age = age;  //        notifyChange();//刷新所有可刷新数据          notifyPropertyChanged(BR.age);      }  }</code></pre>    <p>方法二</p>    <ol>     <li>实体类继承BaseObservable,或者自己实现Observable</li>     <li>使用ObservableField<>,泛型可以填入自己需要的类型,注意必须要初始化。对于基本数据类型也可以直接使用ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble和ObservableParcelable。</li>     <li>通过set和get方法为ObservableField设值和取值</li>    </ol>    <pre>  <code class="language-java">package lxf.androiddemos.model;    import android.databinding.BaseObservable;  import android.databinding.ObservableField;  import android.view.View;    public class UserEntity extends BaseObservable{        public ObservableField<String> address = new ObservableField<>();        public void changeAddress(View view){          address.set("change:" + address.get());      }  }</code></pre>    <h2>自定义属性绑定适配器和回调</h2>    <h3>自动寻找setter</h3>    <p>DataBinding在遇到属性绑定时,会自动去寻找该属性的set方法,找到就会调用,找不到就报错。</p>    <pre>  <code class="language-java"><ImageView      android:layout_width="50dp"      android:layout_height="50dp"      app:imageResource="@{R.mipmap.ic_launcher}"/></code></pre>    <p>比如上面这段代码,我们知道ImageView中是没有imageResource这个属性的,但是有setImageResource(int resId)方法,因此这段代码是可以正常运行的。利用这种特性,可以为一些自定义控件增加setter方法,使其支持DataBinding。</p>    <h3>@BindingMethods</h3>    <p>当xml属性名称和源码中set方法名称不一致时,可以通过这种方式来进行绑定。先看一个官方的实现:</p>    <pre>  <code class="language-java">@BindingMethods({          ...          @BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"),          ...  })</code></pre>    <p>这段代码的意思就是将TextView的android:inputType属性绑定到setRawInputType方法,其实也可以通俗的认为是为原本的setter方法起了一个别名。</p>    <h3>@BindingAdapter</h3>    <p>很多时候,源码中并没有提供set方法,比如ImageView,我们希望通过设置url来达到加载图片的目的,我们可以通过@BindAdapter来实现。</p>    <pre>  <code class="language-java"><?xml version="1.0" encoding="utf-8"?>  <layout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:app="http://schemas.android.com/apk/res-auto">        <!--如果本页不需要绑定数据,data标签可以省略-->      <data class="DataBingMain">          <import type="lxf.androiddemos.R"/>          <variable              name="user"              type="lxf.androiddemos.model.UserEntity"/>      </data>        <LinearLayout          ...          >            <ImageView              android:layout_width="100dp"              android:layout_height="100dp"              app:url="@{user.img}"              app:placeHolder="@{R.mipmap.ic_launcher}"/>      </LinearLayout>  </layout>      public class UserEntity extends BaseObservable{        private String img;        @BindingAdapter(value = {"url","placeHolder"},requireAll = false)      public static void setImage(ImageView imageView ,String url,int placeHolder){          Glide.with(imageView.getContext()).load(url).placeholder(placeHolder).into(imageView);      }  }</code></pre>    <p>这里有几点需要注意:</p>    <ul>     <li>xml文件中一定不要忘记各种类的import(除java.lang包外均需导入),否则你一定会碰到databinding程序包不存在这个错误。</li>     <li>设置@BindAdapter注解的方法需要是static的,否则编译也会报错。</li>     <li>你可以把这个方法设置在一个专门的工具类中,不是说必须要在这个model实体类里。</li>     <li>@BindAdapter包含value和requireAll两个属性,value是一个String[],包含你自定义的属性。requireAll意思是是否需要设置你在value中声明的全部属性,默认为true。如果设定为false,那么没赋值的自定义属性会传默认值。</li>    </ul>    <p>到这里,我们来回头看一下之前在 <strong>事件绑定</strong> 中留下的那个坑——onTextChanged,其实这是官方提前为我们封装好的:</p>    <pre>  <code class="language-java">@BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged",              "android:afterTextChanged", "android:textAttrChanged"}, requireAll = false)      public static void setTextWatcher(TextView view, final BeforeTextChanged before,              final OnTextChanged on, final AfterTextChanged after,              final InverseBindingListener textAttrChanged) {          final TextWatcher newValue;          if (before == null && after == null && on == null && textAttrChanged == null) {              newValue = null;          } else {              newValue = new TextWatcher() {                  @Override                  public void beforeTextChanged(CharSequence s, int start, int count, int after) {                      if (before != null) {                          before.beforeTextChanged(s, start, count, after);                      }                  }                    @Override                  public void onTextChanged(CharSequence s, int start, int before, int count) {                      if (on != null) {                          on.onTextChanged(s, start, before, count);                      }                      if (textAttrChanged != null) {                          textAttrChanged.onChange();                      }                  }                    @Override                  public void afterTextChanged(Editable s) {                      if (after != null) {                          after.afterTextChanged(s);                      }                  }              };          }          final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher);          if (oldValue != null) {              view.removeTextChangedListener(oldValue);          }          if (newValue != null) {              view.addTextChangedListener(newValue);          }      }</code></pre>    <p>可以看到,当on!=null时,会调用传统的onTextChanged方法。</p>    <h3>@BindingConversion</h3>    <p>方法注释,当自定义的属性和setter方法中需要的参数类型不符时进行转换。</p>    <pre>  <code class="language-java"><Button              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:background="@{`#de325e`}"              /></code></pre>    <p>上面这种写法,直接编译是会报错的,因为setBackground接收的是一个Drawable对象,而我们传入的是个string,所以我们此处可以用@BindingConversion来转换一下(PS:我知道传统写法是可以直接传字符串颜色值的,我只是举个简单例子)。</p>    <pre>  <code class="language-java">@BindingConversion      public static Drawable colorToDrawable(String color){          return new ColorDrawable(Color.parseColor(color));      }</code></pre>    <p>DataBinding在碰到这种参数类型不对的问题时,会自动去检索看看有没有相关的@BindingConversion方法,如果有的话则会调用,需要注意,这个方法也需要是static的。</p>    <h2>接口回调</h2>    <h3>model的回调</h3>    <p>当属性值变化时的回调。</p>    <pre>  <code class="language-java">user.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {              @Override              public void onPropertyChanged(Observable sender, int propertyId) {                  if (propertyId == BR.age){                      Toast.makeText(getApplicationContext(),"age刷新了",Toast.LENGTH_SHORT).show();                  }              }          });</code></pre>    <h3>ViewDatabinding的回调</h3>    <p>当ViewDataBinding执行executePendingBindings()尺寸必须再次评估时的回调。可以设置一些view的展示动画等。</p>    <pre>  <code class="language-java">dataBinding.addOnRebindCallback(new OnRebindCallback() {              @Override              public boolean onPreBind(ViewDataBinding binding) {                  return super.onPreBind(binding);              }                @Override              public void onCanceled(ViewDataBinding binding) {                  super.onCanceled(binding);              }                @Override              public void onBound(ViewDataBinding binding) {                  super.onBound(binding);              }          });</code></pre>    <h2>双向绑定</h2>    <h3>基本数据</h3>    <p>双向绑定意思不仅数据绑定UI,同时UI更新时可以刷新数据,语法为@={},举个例子:</p>    <pre>  <code class="language-java"><EditText              android:layout_width="match_parent"              android:layout_height="wrap_content"              android:onTextChanged="@{util.onTextChanged}"              android:text="@={user.address}"              /></code></pre>    <p>这样在程序运行后,editText会自动显示user.address的初始值,改变editText,则user.address也会同步改变,可以想象,如果我们将user.address绑定另一个TextView,则TextView的内容会跟随editText的变化而变化。</p>    <h3>隐式属性监听</h3>    <pre>  <code class="language-java"><ImageView              android:layout_width="100dp"              android:layout_height="100dp"              android:visibility="@{checkbox.checked?View.VISIBLE:View.GONE}"              app:placeHolder="@{R.mipmap.ic_launcher}"              app:url="@{user.img}" />          <CheckBox              android:id="@+id/checkbox"              android:layout_width="wrap_content"              android:layout_height="wrap_content" /></code></pre>    <p>可以看ImageView的visibility属性,通过CheckBox的checked属性来控制自身的显示和隐藏,这是官方给出的一种支持,同时官方还支持下面这些属性:</p>    <ul>     <li><strong>AbsListView</strong> android:selectedItemPosition</li>     <li><strong>CalendarView</strong> android:date</li>     <li><strong>CompoundButton</strong> android:checked</li>     <li><strong>DatePicker</strong> android:year, android:month, android:day (yes, these are synthetic, but we had a listener, so we thought you’d want to use them)</li>     <li><strong>NumberPicker</strong> android:value</li>     <li><strong>RadioGroup</strong> android:checkedButton</li>     <li><strong>RatingBar</strong> android:rating</li>     <li><strong>SeekBar</strong> android:progress</li>     <li><strong>TabHost</strong> android:currentTab (you probably don’t care, but we had the listener)</li>     <li><strong>TextView</strong> android:text</li>     <li><strong>TimePicker</strong> android:hour, android:minute (again, synthetic, but we had the listener)</li>    </ul>    <h3>自定义双向绑定</h3>    <p>双向绑定其实就是正向绑定+反向绑定,前面讲的全部是正向绑定,截下来我们来看看怎么定义反向绑定。</p>    <p>绑定方法(@InverseBindingMethods)</p>    <p>首先先来了解几个名词:</p>    <ul>     <li>@InverseBindingMethods:其实就是元素为@InverseBindingMethod的一个数组,用来注解 <strong>类</strong> 。 <pre>  <code class="language-java">@Target(ElementType.TYPE)  public @interface InverseBindingMethods {    InverseBindingMethod[] value();  }</code></pre> </li>     <li> <p>@InverseBindingMethod:反向绑定方法,用来确定怎么去监听view属性的变化和回调哪一个getter方法。包含以下4个属性:</p>      <ul>       <li>type:包含attribute的view类型。</li>       <li>attribute:支持双向绑定的属性(string格式)。</li>       <li>event:可以省略,用来通知DataBinding系统attribute已经改变,默认为attribute + "AttrChanged"。(UI通知数据)</li>       <li>method:可以省略,用来从view获取数据的方法,不设定的话会自动寻找"is" 或 "get" + attribute方法。(数据刷新)</li>      </ul> </li>    </ul>    <p>event调用时机需要通过@BindingAdapter进行设置。</p>    <ul>     <li>InverseBindingListener:反向绑定监听器,当使用双向绑定时,会在你的layout自动生成的binding类中自动生成一个InverseBindingListener的实现(拗口吗?好像有一点点。。不理解的可以去看看源码)。</li>    </ul>    <p>看完这几个名词是不是已经凌乱了?(话说我当时也差点哭了。。),我们来看个官方例子消化一下:</p>    <pre>  <code class="language-java">@InverseBindingMethods({          @InverseBindingMethod(type = CompoundButton.class, attribute = "android:checked"),  })//1.这里需要双向绑定的是checked属性,event和method都省略了。  public class CompoundButtonBindingAdapter {      ...      //2.设置什么时候调用event      @BindingAdapter(value = {"android:onCheckedChanged", "android:checkedAttrChanged"},              requireAll = false)      public static void setListeners(CompoundButton view, final OnCheckedChangeListener listener,              final InverseBindingListener attrChange) {          if (attrChange == null) {              view.setOnCheckedChangeListener(listener);          } else {              view.setOnCheckedChangeListener(new OnCheckedChangeListener() {                  @Override                  public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {                      if (listener != null) {                          listener.onCheckedChanged(buttonView, isChecked);                      }                      attrChange.onChange();                  }              });          }      }  }        //3.我们在layout中使用双向绑定          <CheckBox              android:id="@+id/checkbox"              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:checked="@={user.checked}"/>        //4.layout的binding类中自动生成的InverseBindingListener实现。      // Inverse Binding Event Handlers      private android.databinding.InverseBindingListener checkboxandroidCheck = new android.databinding.InverseBindingListener() {          @Override          public void onChange() {//这段逻辑其实就是用来更新user实体类中的checked字段的              // Inverse of user.checked.get()              //         is user.checked.set((java.lang.Boolean) callbackArg_0)              boolean callbackArg_0 = checkbox.isChecked();//其实就是method              // localize variables for thread safety              // user.checked != null              boolean checkedUserObjectnul = false;              // user.checked              android.databinding.ObservableField<java.lang.Boolean> checkedUser = null;              // user              lxf.androiddemos.model.UserEntity user = mUser;              // user.checked.get()              java.lang.Boolean CheckedUser1 = null;              // user != null              boolean userObjectnull = false;                userObjectnull = (user) != (null);              if (userObjectnull) {                checkedUser = user.checked;                    checkedUserObjectnul = (checkedUser) != (null);                  if (checkedUserObjectnul) {                  checkedUser.set((java.lang.Boolean) (callbackArg_0));                  }              }          }      };</code></pre>    <p>整个反向绑定的流程下来其实就是:</p>    <ol>     <li>定义需要反向绑定的属性(checked),并配置event(checkedAttrChanged)和method(isChecked)。</li>     <li>系统会自动根据event找到对应的方法(setLinstener),配置好调用时机。</li>     <li>开发者在layout中使用双向绑定。</li>     <li>自动在binding类中生成一个InverseBindingListener的实现。 <p>绑定适配器(@InverseBindingAdapter)</p> 下面再来看个新名词...( ╯□╰ ):</li>     <li>@InverseBindingAdapter:反向绑定适配器,用来注解 <strong>方法</strong> 。只包含attribute和event两个属性,含义同上:      <ul>       <li>attribute:支持双向绑定的属性(string格式)。</li>       <li>event:可以省略,用来通知DataBinding系统attribute已经改变,默认为attribute + "AttrChanged"。需要通过@BindingAdapter进行设置调用时机。</li>      </ul> </li>    </ol>    <p>@InverseBindingAdapter注解的方法本身就相当于获取数据的getter方法(类似于@BindingAdapter注解的方法本身就相当于setter方法)。</p>    <p>官方案例(双向绑定android:text):</p>    <pre>  <code class="language-java">//1.这一步相当于做了两个操作:确定绑定的属性和event;指定getter方法  @InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")      public static String getTextString(TextView view) {          return view.getText().toString();      }  //2.根据event找到对应方法,配置event的调用时机。      @BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged",              "android:afterTextChanged", "android:textAttrChanged"}, requireAll = false)      public static void setTextWatcher(TextView view, final BeforeTextChanged before,              final OnTextChanged on, final AfterTextChanged after,              final InverseBindingListener textAttrChanged) {          final TextWatcher newValue;          if (before == null && after == null && on == null && textAttrChanged == null) {              newValue = null;          } else {              newValue = new TextWatcher() {                  ...                  @Override                  public void onTextChanged(CharSequence s, int start, int before, int count) {                      if (on != null) {                          on.onTextChanged(s, start, before, count);                      }                      if (textAttrChanged != null) {                          textAttrChanged.onChange();                      }                  }                  ....              };          }         ...      }      //3.使用双向绑定              <EditText              android:layout_width="match_parent"              android:layout_height="wrap_content"              android:onTextChanged="@{util.onTextChanged}"              android:text="@={user.address}" />      //4.binding类中自动生成InverseBindingListener的实现。  private android.databinding.InverseBindingListener mboundView10androidT = new android.databinding.InverseBindingListener() {          @Override          public void onChange() {              // Inverse of user.address.get()              //         is user.address.set((java.lang.String) callbackArg_0)              java.lang.String callbackArg_0 = android.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView10);//getter方法              // localize variables for thread safety              // user.address != null              boolean addressUserObjectnul = false;              // user              lxf.androiddemos.model.UserEntity user = mUser;              // user.address              android.databinding.ObservableField<java.lang.String> addressUser = null;              // user.address.get()              java.lang.String AddressUser1 = null;              // user != null              boolean userObjectnull = false;                  userObjectnull = (user) != (null);              if (userObjectnull) {                  addressUser = user.address;                  addressUserObjectnul = (addressUser) != (null);                  if (addressUserObjectnul) {                       addressUser.set((java.lang.String) (callbackArg_0));                  }              }          }      };</code></pre>    <p>一大堆的代码看下来,其实绑定方法和绑定适配器两种方法的最终效果是一样的,实现过程也是大同小异,这里就不赘述了,和上面的绑定方法基本一致。</p>    <p>比葫芦画瓢</p>    <p>我们来自定义实现这样一个效果,点击改变自定义view的颜色,同时将色值在另一个TextView中展示出来(虽然没什么卵用,仅仅当个案例吧),效果图如下(请自觉忽略其他的东西。。):</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/cdf7728d5ecb2f86ba09581b1738e4de.gif"></p>    <p style="text-align:center">效果图</p>    <p>实现过程:</p>    <pre>  <code class="language-java">//1.自定义ColorPicker,并为color属性添加getter和setter方法  public class ColorPicker extends View {      ...      private String mColor;        public String getColor() {          return mColor;      }        public void setColor(String mColor) {          this.mColor = mColor;          paint.setColor(Color.parseColor(mColor));          invalidate();      }        ...  }  //2.自定义反向绑定  @InverseBindingMethods({          @InverseBindingMethod(type = ColorPicker.class,attribute = "color")  })  public class ColorPickerAdapter {        @BindingAdapter(value = {"colorAttrChanged"},requireAll = false)      public static void setListener(ColorPicker picker, final InverseBindingListener attrChange){          if (attrChange!=null){              picker.setOnColorChangeListener(new ColorPicker.OnColorChangeListener() {                  @Override                  public void onColorChange(ColorPicker picker, String color) {                      //...                        attrChange.onChange();                  }              });          }      }  }  //3.在layout中使用双向绑定  <lxf.androiddemos.test.ColorPicker       android:layout_width="100dp"       android:layout_height="100dp"       app:color="@={user.color}" /></code></pre>    <p>上面给出了关键代码,刚接触DataBinding的萌新如果理解不了可以去文末下载Demo看看,只是一个很简单的案例,应该没什么问题。</p>    <p>接下来我们用@InverseBindingAdapter来实现同样的效果:</p>    <pre>  <code class="language-java">public class ColorPickerAdapter {        @InverseBindingAdapter(attribute = "color")      public static String getColor(ColorPicker picker){          return picker.getColor();      }        @BindingAdapter(value = {"colorAttrChanged"},requireAll = false)      public static void setListener(ColorPicker picker, final InverseBindingListener attrChange){          if (attrChange!=null){              picker.setOnColorChangeListener(new ColorPicker.OnColorChangeListener() {                  @Override                  public void onColorChange(ColorPicker picker, String color) {                      //...                        attrChange.onChange();                  }              });          }      }  }</code></pre>    <p>另外关于一些情况下双向绑定存在的死循环问题,只要在setter方法中判断一下新老值不同即可。</p>    <h2>依赖注入</h2>    <p>DataBindingComponent,一般用于一个@BindingAdapter方法需要有多种实现时(比如说测试。。),我们来看一下前面那个修改年龄age的例子:</p>    <pre>  <code class="language-java">//原来的方式    @BindingAdapter(value = {"url","placeHolder"},requireAll = false)      public static void setImage(ImageView imageView , String url, int placeHolder){          ImgLoadUtil.load(imageView,url,placeHolder);      }    //运用DataBindingComponent  //1.如果需要多种实现,可以先建一个抽象的adapter,注意方法为非静态的  public abstract class AppAdapter {        @BindingAdapter(value = {"url","placeHolder"},requireAll = false)      public abstract void setImage(ImageView imageView , String url, int placeHolder);  }  //2.添加抽象adapter的实现,这里我们只写了一个  public class ImgAdapter extends AppAdapter {      @Override      public void setImage(ImageView imageView, String url, int placeHolder) {          ImgLoadUtil.load(imageView,url,placeHolder);      }  }  public class Img2Adapter extends AppAdapter {      @Override      public void setImage(ImageView imageView, String url, int placeHolder) {          ...      }  }  //3.添加DataBindingComponent的实现(非静态的@BindingAdapter注解方法会自动在DataBindingComponent中生成相应的getter方法)。  public class MyComponent implements android.databinding.DataBindingComponent {      @Override      public AppAdapter getAppAdapter() {          return new ImgAdapter();      }  }  public class My2Component implements android.databinding.DataBindingComponent {      @Override      public AppAdapter getAppAdapter() {          return new Img2Adapter();      }  }  //4.Activity中调用          //DataBingMain dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_databinding);          //DataBindingUtil.setDefaultComponent(new MyComponent());          DataBingMain dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_databinding,new MyComponent());</code></pre>    <p>最终效果是一模一样的。</p>    <h2>遇到的坑</h2>    <ul>     <li> <p>Error:(8, 36) 错误: 程序包lxf.androiddemos.databinding不存在</p> <p>遇到这种情况一般都是xml中的问题,比如data标签中引入的包名不对,或者是布局里面使用了什么错误的属性,等等。。。数据量大的时候,这种错误一般比较难找,简直就是日了dog。</p> </li>     <li> <p>需要更新数据时,为getter方法设置@Bindable,很多时候BR文件不会生成,需要重启AS,默默地再日一波dog。</p> </li>     <li>最好不要使用clean project,否则R文件和BR文件会被清掉,R文件会自动重新生成,至于BR文件...那只dog,麻烦你再过来一下。</li>    </ul>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/05b9838a1949</p>    <p> </p>    
 本文由用户 hkxf9949 自行上传分享,仅供网友学习交流。所有权归原作者,若您的权利被侵害,请联系管理员。
 转载本站原创文章,请注明出处,并保留原始链接、图片水印。
 本站是一个以用户分享为主的开源技术平台,欢迎各类分享!
 本文地址:https://www.open-open.com/lib/view/open1483079134021.html
MVVM模式 安卓开发 Android开发 移动开发