精华内容
参与话题
问答
  • DataBinding最详细使用

    万次阅读 2018-08-29 11:30:14
    Google开源的数据绑定框架, 实现了MVVM...DataBinding会自动在build目录下生成类. 因为被集成进AndroidStudio所以不需要你手动编译会实时编译, 并且支持大部分代码补全. 启用DataBinding android{ dataBinding {...

    Google开源的数据绑定框架, 实现了MVVM架构, 增强了xml的功能, 大幅度精简了java代码量, 并且代码可读性更高, 对性能的影响基本为零.

    DataBinding会自动在build目录下生成类. 因为被集成进AndroidStudio所以不需要你手动编译会实时编译, 并且支持大部分代码补全.

    启用DataBinding

    android{
          dataBinding {
            enabled = true;
        }
    }

    因为怕你们没注意到我写在文章开头

    • 我想强调的是XML只做赋值或者简单的三元运算或者判空等不要做复杂运算;
    • 逻辑运算在Model中
    • 有时候可以偷懒将Activity当作ViewModel来使用

    DataBinding的强大是毋庸置疑, 只会更方便(抛弃MVP吧);

    鉴于文章篇幅, 后面我将会出一篇文章以及开源库告诉大家如何实现DataBinding是如何让RecyclerView一行代码写通用适配器(无需写实现类)

    一行代码实现多类型/添加头布局脚布局/点击事件;

    布局

    布局文件

    <layout>
    
        <data>
            <variable
                name="user"
                type="com.liangjingkanji.databinding.pojo.UserBean"/>
        </data>
    
        <RelativeLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context="com.liangjingkanji.databinding.MainActivity">
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.userName}"
                />
    
        </RelativeLayout>
    
    </layout>

    layout

    布局根节点必须是<layout> . 同时layout只能包含一个View标签. 不能直接包含<merge>

    data

    <data>标签的内容即DataBinding的数据. data标签只能存在一个.

    variable

    通过<variable>标签可以指定类, 然后在控件的属性值中就可以使用

    <data>
        <variable name="user" type="com.liangfeizc.databindingsamples.basic.User" />
    </data>

    通过DataBinding的setxx()方法可以给Variable设置数据. name值不能包含_下划线

    import

    第二种写法(导入), 默认导入了java/lang包下的类(String/Integer). 可以直接使用被导入的类的静态方法.

    <data>
      <!--导入类-->
        <import type="com.liangfeizc.databindingsamples.basic.User" />
      <!--因为User已经导入, 所以可以简写类名-->
        <variable name="user" type="User" />
    </data>

    使用类

    <TextView
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="@{user.userName}"
              />
    <!--user就是在Variable标签中的name, 可以随意自定义, 然后就会使用type中的类-->

    Tip: user代表UserBean这个类, 可以使用UserBean中的方法以及成员变量. 如果是getxx()会自动识别为xx. 注意不能使用字符串android, 否则会报错无法绑定.

    class

    <data>标签有个属性<class>可以自定义DataBinding生成的类名以及路径

    <!--自定义类名-->
    <data class="CustomDataBinding"></data>
    
    <!--自定义生成路径以及类型-->
    <data class=".CustomDataBinding"></data> <!--自动在包名下生成包以及类-->

    Tip:注意没有代码自动补全. 自定义路径Module/build/generated/source/apt/debug/databinding/目录下, 基本上不需要自定义路径

    默认:

    public class MainActivity extends AppCompatActivity {
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        // ActivityMainBinding这个类根据布局文件名生成(id+Binding)
        ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    
        UserBean userBean = new UserBean();
        userBean.setUserName("姜涛");
    
        // setUser这个方法根据Variable标签的name属性自动生成
        viewDataBinding.setUser(userBean);
      }
    }

    alias

    <variable>标签如果需要导入(import)两个同名的类时可以使用alias属性(别名属性)

    <import type="com.example.home.data.User" />
    <import type="com.examle.detail.data.User" alias="DetailUser" />
    <variable name="user" type="DetailUser" />

    include

    在include其他布局的时候可能需要传递变量(variable)值过去

    <variable
              name="userName"
              type="String"/>
    
    ....
    
    <include
             layout="@layout/include_demo"
             bind:userName="@{userName}"/>

    include_demo

        <data>
    
            <variable
                name="userName"
                type="String"/>
        </data>
    
    ...
    
    android:text="@{userName}"

    两个布局通过includebind:<变量名>值来传递. 而且两者必须有同一个变量

    DataBinding不支持merge标签

    自动布局属性

    DataBinding对于自定义属性支持非常好, 只要View中包含setter方法就可以直接在布局中使用该属性(这是因为DataBinding的库中官方已经帮你写好了很多自定义属性)

    public void setCustomName(@NonNull final String customName) {
        mLastName.setText("吴彦祖");
      }

    然后直接使用(但是IDE没有代码补全)

    app:customName="@{@string/wuyanzu}"

    但是setter方法只支持单个参数. app:这个命名空间可以随意

    数据双向绑定

    视图跟随数据刷新

    BaseObservable

    如果需要数据变化是视图也跟着变化则需要使用到以下两种方法

    有两种方式:

    1. 继承BaseObservable

      public class ObservableUser extends BaseObservable {
         private String firstName;
         private String lastName;
      
         @Bindable
         public String getFirstName() {
             return firstName;
         }
      
       // 注解才会自动在build目录BR类中生成entry, 要求方法名必须以get开头
         @Bindable
         public String getLastName() {
             return lastName;
         }
      
         public void setFirstName(String firstName) {
             this.firstName = firstName;
             notifyPropertyChanged(BR.firstName);
         }
      
         public void setLastName(String lastName) {
             this.lastName = lastName;
             notifyPropertyChanged(BR.lastName); // 需要手动刷新
         }
      }

    还可以监听属性改变事件

    ObservableUser.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
      @Override
      public void onPropertyChanged(Observable observable, int i) {
    
      }
    });

    属性第一次改变时会回调两次, 之后都只回调一次. 如果使用notifyChange()不会得到id(即i等于0). 使用

    notifyPropertyChanged(i)就可以在回调里面得到id.

    BaseObservable和Observable的区别:

    1. BaseObservable是实现了Observable的类, 帮我们实现了监听器的线程安全问题.
    2. BaseObservable使用了PropertyChangeRegistry来执行OnPropertyChangedCallback
    3. 所以我不推荐你直接实现Observable.

    ObservableField

    databinding默认实现了一系列实现Observable接口的字段类型

    BaseObservable,
    ObservableBoolean,
    ObservableByte,
    ObservableChar,
    ObservableDouble,
    ObservableField<T>,
    ObservableFloat,
    ObservableInt,
    ObservableLong,
    ObservableParcelable<T extends Parcelable>,
    ObservableShort,
    ViewDataBinding

    示例

    public class PlainUser {
      public final ObservableField<String> firstName = new ObservableField<>();
      public final ObservableField<String> lastName = new ObservableField<>();
      public final ObservableInt age = new ObservableInt();
    }

    对于集合数据类型ObservableArrayMap/ObservableArrayLis/ObjservableMap等集合数据类型

    ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
    user.put("firstName", "Google");
    user.put("lastName", "Inc.");
    user.put("age", 17);

    使用

    <data>
        <import type="android.databinding.ObservableMap"/>
        <variable name="user" type="ObservableMap<String, Object>"/>
    </data><TextView
       android:text='@{user["lastName"]}'
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
    <TextView
       android:text='@{String.valueOf(1 + (Integer)user["age"])}'
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>

    Tip:

    1. 还支持ObservableParcelable<Object>序列化数据类型
    2. 上面说的这两种只会视图跟随数据更新, 数据并不会跟随视图刷新.
    3. ObservableField同样支持addOnPropertyChangedCallback监听属性改变

    数据跟随视图刷新

    通过表达式使用@=表达式就可以视图刷新的时候自动更新数据, 但是要求数据实现以下两种方式修改才会触发刷新

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="textNoSuggestions"
        android:text="@={model.name}"/>

    这种双向绑定存在一个很大的问题就是会死循环. 数据变化(回调监听器)触发视图变化, 然后视图又会触发数据变化(再次回调监听器), 然后一直循环, 设置相同的数据也视为数据变化.

    所以我们需要判断当前变化的数据是否等同于旧数据

    public class CustomBindingAdapter {
    
      @BindingAdapter("android:text") public static void setText(TextView view, CharSequence text) {
        CharSequence oldText = view.getText();
    
        if (!haveContentsChanged(text, oldText)) {
          return; // 数据没有变化不进行刷新视图
        }
        view.setText(text);
      }
    
    
      // 本工具类截取自官方源码
      private static boolean haveContentsChanged(CharSequence str1, CharSequence str2) {
        if ((str1 == null) != (str2 == null)) {
          return true;
        } else if (str1 == null) {
          return false;
        }
        final int length = str1.length();
        if (length != str2.length()) {
          return true;
        }
        for (int i = 0; i < length; i++) {
          if (str1.charAt(i) != str2.charAt(i)) {
            return true;
          }
        }
        return false;
      }
    }

    Tip:

    1. 根据我上面说的, 监听器至少回调两次(数据->视图, 视图-> 数据)

    2. 以下这种是无效的, 因为String参数传递属于引用类型变量并不是常量, 需要用equals()

      // 本段截取官方源码, 我也不知道这sb为什么这么写
      if (text == oldText || (text == null && oldText.length() == 0)) {
       return; 
      }
      
      /**/

      正确

      if (text == null || text.equals(oldText) || oldText.length() == 0) {
       return;
      }

    总结就是如果没有默认实行的控件属性使用双向数据绑定 就需要你自己实现BindingAdapter注解

    注解

    @Bindable

    用于数据更新自动刷新视图. 后面提.

    @BindingAdapter

    用于标记方法. 前面提到了DataBinding自定义属性自动识别setter.

    如果我们需要自定义xml, 就需要修改View的源码 ,但是DataBinding还有第二种方法相当于可以将setter方法抽取出来, 并且同时支持多个属性.

    图片加载框架可以方便使用此方法.

    @BindingAdapter(value = { "imageUrl", "error" }, requireAll = false)
      public static void loadImage(ImageView view, String url, Drawable error) {
        Glide.with(view.getContext()).load(url).into(view);
      }
    1. 修饰方法, 要求方法必须public static
    2. 方法参数第一个要求必须是View
    3. 方法名不作要求
    4. 最后这个boolean类型是可选参数. 可以要求是否所有参数都需要填写. 默认true.
    5. 如果requireAll为false, 你没有填写的属性值将为null. 所以需要做非空判断.

    使用:

    <ImageView
               android:layout_width="match_parent"
               android:layout_height="200dp"
               app:error="@{@drawable/error}"
               wuyanzu:imageUrl="@{imageUrl}"
               app:onClickListener="@{activity.avatarClickListener}"
               />

    可以看到命名空间可以随意, 但是如果在BindingAdapter的数组内你定义了命名空间就必须完全遵守

    例如:

    // 这里省略了一个注解参数.   
    @BindingAdapter({ "android:imageUrl", "error" })
      public static void loadImage(ImageView view, String url, Drawable error) {
        if(url == null) return;
        Glide.with(view.getContext()).load(url).into(view);
      }

    Tip: 如果你的数据初始化是在异步的. 会回调方法但是数据为null(成员默认值). 所以我们必须要首先进行判空处理.

    @BindingMethods

    如果你想自定义一个属性并且将他和这个View内部的函数关联就必须使用这个特性;

    该注解属于一个容器. 内部参数是一个@BindingMethod数组, 只能用于修饰类;

    任意类都可以, 类可以为空

    官方示例:

    @BindingMethods({
            @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:indeterminateTint", method = "setIndeterminateTintList"),
            @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:progressTint", method = "setProgressTintList"),
            @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:secondaryProgressTint", method = "setSecondaryProgressTintList"),
    })
    public class ProgressBarBindingAdapter {
    }

    @BindingMethod

    该注解必须有三个属性

    1. type: 字节码
    2. attribute: 属性
    3. method: 方法

    会在指定的字节码(type)中寻找方法(method), 然后通过你创建的布局属性(Attribute)来回调方法

    如果属性名和@BindingAdapter冲突会报错

    Tip: 可以注意到该注解只是单纯地关联已有的方法, 并不能新增方法. 所以全都是注解的空类.

    @BindingConversion

    属性值自动进行类型转换

    1. 只能修饰public static方法.
    2. 任意位置任意方法名都不限制
    3. DataBinding自动匹配被该注解修饰的方法和匹配参数类型
    4. 返回值类型必须和属性setter方法匹配, 且参数只能有一个
    5. 要求属性值必须是@{}DataBinding表达式

    官方示例:

    public class Converters {
        @BindingConversion
        public static ColorDrawable convertColorToDrawable(int color) {
            return new ColorDrawable(color);
        }
        @BindingConversion
        public static ColorStateList convertColorToColorStateList(int color) {
            return ColorStateList.valueOf(color);
        }
    }

    设置布局中TextView的背景,

    android:background="@{`吴彦祖`}"

    可以看到我给背景随意设置一个字符串. 这样就不会匹配Background的int参数类型. 然后DataBinding就会检索匹配该类型的@BindingConversion方法. 然后转换.

    注意android:text如果想用int自动转String是不可以的, 因为int值会被识别为resource id. @BindingConversion无法工作.

    @InverseMethod

    在android studio3.0提供inverse系列的新注解, 全部都是针对数据双向绑定.

    在数据和视图的数据不统一时可以使用该注解@InverseMethod解决数据转换的问题

    例如数据模型存储用户的id但是视图不显示id而是显示用户名(数据和视图的类型不一致), 我们就需要在两者之间转换.

    需要创建public static两个方法, 我们简称为”转换方法(convertion method)”和”反转方法(inverse method)”

    • 转换方法与反转方法的参数数量必须相同
    • 转换方法的最终参数的类型与反转方法的返回值必须相同

    转换函数: 是刷新视图的时候使用 (决定视图显示数据) 会回调两次(文章后面详细解释双向绑定的时候可以知道为何), 可以理解为getter函数;

    反转函数: 是刷新数据的时候使用 (决定实体存储数据), 可以理解为setter函数;

    简单示例:

    在用户id和用户名之间转换. 存储id但是显示的时候显示用户名

      @InverseMethod("toID") public static String toName(TextView view, int id) {
        if (id == 1) {
          return "吴彦祖";
        }
        return "";
      }
    
      public static int toID(TextView view, String name) {
        if (name.equals("吴彦祖")) {
          return 1;
        }
        return 0;
      }

    使用

        <TextView
            android:id="@+id/iv"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:text="@={MyInverseMethod.toName( iv, data.id)}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            />

    注意和BindingAdapter不同, 参数有View表达式就必须加上View的id

    Tip:

    在这个注解之前其实都是通过修改实体的setter和getter方法达到类型的转换. 但是这样会侵入整个实体类

    我使用的gson类都是自动生成的我并不想去手动修改任何方法.

    @InverseBindingAdapter

    参数:

    • String attribute 属性值(必填)
    • String event 非必填, 默认值 属性值 + AttrChanged后缀

    介绍

    • 作用于方法,方法须为公共静态方法。
    • 方法的第一个参数必须为View类型

    创建一个函数使用@InverseBindingAdapter修饰; 该函数会在UI变化的时候回调, 返回的数据会

    @InverseBindingAdapter(attribute = "good", event = "goodAttrChanged")
    public static String getTextString(TextView view) {
        Log.i("日志", "(DataBinding.java:20) ___ 反转" + view.getText());
        return "吴彦祖" + view.getText();
    }

    event: 该属性需要你创建一个@BindingAdapter

    @BindingAdapter("goodAttrChanged")
    public static void setGoodChanged(TextView textView, InverseBindingListener inverseBindingListener) {
        Log.d("日志", "(DataBinding.java:24) ___ 属性监听事件 = ");
    }

    在你绑定DataBinding时候回自动调用这个数据变更方法, (这个数据变更方法创建的属性并不能在xml中使用)

    @InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
    public static String getTextString(TextView view) {
        return view.getText().toString();
    }

    android:text属性使用@={}双向绑定表达式. 数据变化触发视图刷新是回调setter方法

    数据变更方法(官方源码简化版):

     @BindingAdapter(value = {"android:textAttrChanged"}, requireAll = false)
      public static void setTextWatcher(TextView view, final InverseBindingListener textAttrChanged) {
    
        // 创建一个文字变化监听器
        final TextWatcher newValue;
    
        // 如果全部为null不要监听器
        if (textAttrChanged == null) {
          newValue = null;
        } else {
          newValue = new TextWatcher() {
    
            @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    
            }
            @Override public void onTextChanged(CharSequence s, int start, int before, int count) {
              if (textAttrChanged != null) {
                // 通知刷新
                textAttrChanged.onChange();
              }
            }
    
            @Override public void afterTextChanged(Editable s) {
    
            }
          };
        }
        // 如果视图已经有一个监听器就先删除
        final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher);
        if (oldValue != null) {
          view.removeTextChangedListener(oldValue);
        }
    
        // 给视图添加监听器
        if (newValue != null) {
          view.addTextChangedListener(newValue);
        }
      }

    这里用到一个InverseBindingListener

    public interface InverseBindingListener {
        /**
         * Notifies the data binding system that the attribute value has changed.
         */
        void onChange();
    }

    总结就是你只要通知

    @InverseBindingMethods

    和BindingMethods相似, 但是注解参数是@InverseBindingMethod

    如果说BindingMethods是关联setter方法和自定义属性, 那么InverseBindingMethods就是关联getter方法和自定义属性;

    setter是更新视图的时候使用, 而getter方法是更新数据时候使用的

    必须与@BindingAdapter配合使用

    • 修饰类

    示例:

    @InverseBindingMethods({
            @InverseBindingMethod(type = RadioGroup.class, attribute = "android:checkedButton", method = "getCheckedRadioButtonId"),
    })
    public class RadioGroupBindingAdapter {
        @BindingAdapter("android:checkedButton")
        public static void setCheckedButton(RadioGroup view, int id) {
            if (id != view.getCheckedRadioButtonId()) {
                view.check(id);
            }
        }

    @InverseBindingMethod

    参数:

    • Class type 控件的字节码

    • String attribute 属性

    • String event 默认值是属性加AttrChanged后缀作为默认值

    • String method 默认值 attribute的getter (例: getMethod

    在自动生成DataBinding代码中可以看到

        private android.databinding.InverseBindingListener ivandroidTextAttr = new android.databinding.InverseBindingListener() {
            @Override
            public void onChange() {
                // Inverse of data.name
                //         is data.setName((java.lang.String) callbackArg_0)
                java.lang.String callbackArg_0 = com.liangjingkanji.databinding.MyInverseBindingAdapter.getTextString(iv);  // 拿到变化的属性
                // localize variables for thread safety
                // data != null
                boolean dataJavaLangObjectNull = false;
                // data.name
                java.lang.String dataName = null;
                // data
                com.liangjingkanji.databinding.Bean data = mData; // 拿到数据
    
    
    
                dataJavaLangObjectNull = (data) != (null);
                if (dataJavaLangObjectNull) {
    
    
    
    
                    data.setName(((java.lang.String) (callbackArg_0))); // 存储到数据
                }
            }
        };

    所以如果你没用重写Inverse的数据变更方法将无法让视图通知数据刷新.

    // 该方法会在绑定布局的时候回调
        @Override
        protected void executeBindings() {
            long dirtyFlags = 0;
            synchronized(this) {
                dirtyFlags = mDirtyFlags;
                mDirtyFlags = 0;
            }
            java.lang.String dataName = null;
            com.liangjingkanji.databinding.Bean data = mData;
    
            if ((dirtyFlags & 0x1aL) != 0) {
    
    
    
                    if (data != null) {
                        // read data.name
                        dataName = data.getName();
                    }
            }
            // batch finished
            if ((dirtyFlags & 0x1aL) != 0) {
                // api target 1
    
                com.liangjingkanji.databinding.MyInverseBindingAdapter.setText(this.iv, dataName);
            }
            if ((dirtyFlags & 0x10L) != 0) {
                // api target 1
    
              // 重点是这段代码, 将上面创建的监听器传入setTextWatcher方法
                com.liangjingkanji.databinding.MyInverseBindingAdapter.setTextWatcher(this.iv, (com.liangjingkanji.databinding.MyInverseBindingAdapter.BeforeTextChanged)null, (com.liangjingkanji.databinding.MyInverseBindingAdapter.OnTextChanged)null, (com.liangjingkanji.databinding.MyInverseBindingAdapter.AfterTextChanged)null, ivandroidTextAttr);
            }
        }

    总结

    @BindingBuildInfo@Untaggable这两个注解是DataBinding自动生成Java类时使用的.

    • Bindable

      设置数据刷新视图. 自动生成BR的ID

    • BindingAdapter

      设置自定义属性. 可以覆盖系统原有属性

    • BindingMethod/BindingMethods

      关联自定义属性到控件原有的setter方法

    • BindingConversion

      如果属性不能匹配类型参数将自动根据类型参数匹配到该注解修饰的方法来转换

    • InverseMethod

      负责实现视图和数据之间的转换

    • InverseBindingAdapter

      视图通知数据刷新的

    • InverseBindingMethod/InverseBindingMethods

      视图通知数据刷新的(如果存在已有getter方法可用的情况下)

    建议参考官方实现源码:

    DataBindingAdapter

    表达式

    @{}里面除了可以执行方法以外还可以写表达式, 并且支持一些特有表达式

    • 算术 + - / * %
    • 字符串合并 +
    • 逻辑 && ||
    • 二元 & | ^
    • 一元 + - ! ~
    • 移位 >> >>> <<
    • 比较 == > < >= <=
    • Instanceof
    • Grouping ()
    • 文字 - character, String, numeric, null
    • Cast
    • 方法调用
    • Field 访问
    • Array 访问 []
    • 三元 ?:

    避免空指针

    variable的值即使设置null或者没有设置也不会出现空指针异常.

    这是因为官方已经用DataBinding的@BindingAdapter注解重写了很多属性. 并且里面进行了判空处理.

    <variable
        name="userName"
        type="String"/>
    
    .....
    
    android:text="@{userName}"

    不会出现空指针异常.

    dataBinding.setUserName(null);

    并且还支持特有的非空多元表达式

    android:text="@{user.displayName ?? user.lastName}"

    就等价于

    android:text="@{user.displayName != null ? user.displayName : user.lastName}"

    还是需要注意数组越界的

    集合

    集合不属于java.lang*下, 需要导入全路径.

    <variable
              name="list"
              type="java.util.List&lt;String&gt;"/>
    
    <variable
              name="map"
              type="java.util.Map<String, String>"/>

    上面这种写法会报错

    Error:与元素类型 "variable" 相关联的 "type" 属性值不能包含 '<' 字符。

    因为<符号需要转义.

    常用转义字符

    ​ 空格 &nbsp; &#160;

    < 小于号 &lt; &#60;

    > 大于号 &gt; &#62;

    & 与号 &amp; &#38;
    ” 引号 &quot; &#34;
    ‘ 撇号 &apos; &#39;
    × 乘号 &times; &#215;
    ÷ 除号 &divide; &#247;

    正确写法

    <variable
              name="list"
              type="java.util.List&lt;String&gt;"/>
    
    <variable
              name="map"
              type="java.util.Map&lt;String, String&gt;"/>

    集合和数组都可以用[]来得到元素

    android:text="@{map["firstName"]}"

    字符串

    如果想要在@{}中使用字符串, 可以使用三种方式

    第一种:

    android:text='@{"吴彦祖"}'

    第二种:

    android:text="@{`吴彦祖`}"

    第三种:

    android:text="@{@string/user_name}"

    同样支持@color或@drawable

    格式化字符串

    首先在strings中定义<string>

    <string name="string_format">名字: %s  性别: %s</string>

    然后就可以使用DataBinding表达式

    android:text="@{@string/string_format(`吴彦祖`, `男`)}"

    输出内容:

    名字: 吴彦祖 性别: 男

    默认值

    如果Variable还没有复制就会使用默认值显示.

    android:text="@{user.integral, default=`30`}"

    上下文

    DataBinding本身提供了一个名为context的Variable. 可以直接使用. 等同于View的getContext().

    android:text="@{context.getApplicationInfo().toString()}"

    引用其他控件

              <TextView
                  android:id="@+id/datingName"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_centerVertical="true"
                  android:layout_marginLeft="8dp"
                  android:layout_toRightOf="@id/iv_dating"
                  android:text="活动"
                  />
    
    /...
    <TextView
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_centerVertical="true"
                  android:layout_marginLeft="8dp"
                  android:layout_toRightOf="@id/iv_order"
                  android:text="@{datingName.text}"
                  />

    引用包含_的控件id是可以直接忽略该符号. 例如tv_name直接写tvName.

    谢谢 lambda 指出错误

    不论顺序都可以引用

    使用Class

    如果想用Class作为参数传递, 那么该Class不能直接通过静态导入来使用. 需要作为字段常量来使用

    事件绑定

    事件绑定分为两种:

    1. 方法引用
    2. 监听绑定

    对于默认的事件需要书写同样的参数的方法才能接受到, 否则报错. 例如onClick()方法必须有View参数.

    方法引用

    public class MyHandlers {
      // 注意必须要传View参数
        public void onClickFriend(View view) { ... }
    }

    直接通过View的属性来调用类方法

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <data>
        <variable
                  name="activity"
                  type="com.liangjingkanji.databinding.MainActivity"/>
      </data>
    
      <LinearLayout
                    android:orientation="vertical"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">
    
        <TextView android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="@{user.firstName}"
                  android:onClick="@{activit::click}"/>
    
      </LinearLayout>
    </layout>

    Tip: activity.clickactivity::click都属于方法调用, 但是如果是activity.click()就会报错. 因为对于默认事件需要统一参数. 必须加上activity.click(View v)

    监听绑定

    上面提到的都不能向回调里面传递自定义参数. 而如果使用

    android:onClick="@{()->activity.click(text)}"

    就可以自定义回调参数了

    ActivityMainBinding dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); 
    
    dataBinding.setActivity(this);
    dataBinding.setText("吴彦祖"); // 顺序无所谓

    然后在布局文件中使用Lambda

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <data>
        <variable
                  name="text"
                  type="String"/>
    
        <variable
                  name="activity"
                  type="com.liangjingkanji.databinding.MainActivity"/>
      </data>
      <LinearLayout 
                    android:layout_width="match_parent"                             
                    android:layout_height="match_parent">
        <Button 
                android:layout_width="wrap_content"                                             android:layout_height="wrap_content"
                android:onClick="@{()->activity.click(text)}" />
      </LinearLayout>
    </layout>

    DataBinding组件

    ViewDataBinding

    自动生成的DataBinding类都继承自该类. 所以都拥有该类的方法

    void    addOnRebindCallback(OnRebindCallback listener)
    // 添加绑定监听器, 可以在Variable被设置的时候回调
    
    void    removeOnRebindCallback(OnRebindCallback listener)
    // 删除绑定监听器
    
    View    getRoot()
    // 返回被绑定的视图对象
    
    abstract void   invalidateAll()
    // 使所有的表达式无效并且立刻重新设置表达式. 会重新触发OnRebindCallback回调(可以看做重置)
    
    
    abstract boolean    setVariable(int variableId, Object value)
    // 可以根据字段id来设置变量
    
    void    unbind()
    // 解绑布局, ui不会根据数据来变化, 但是监听器还是会触发的

    这里有三个方法需要重点讲解:

    abstract boolean    hasPendingBindings()
    // 当ui需要根据当前数据变化时就会返回true(数据变化后有一瞬间)
    
    void    executePendingBindings()
    // 强制ui立刻刷新数据, 

    当你改变了数据以后(在你设置了Observable观察器的情况下)会马上刷新ui, 但是会在下一帧才会刷新UI, 存在一定的延迟时间. 在这段时间内hasPendingBindings()会返回true. 如果想要同步(或者说立刻)刷新UI可以马上调用executePendingBindings().

    OnRebindCallback:

    该监听器可以监听到布局绑定的生命周期

        mDataBinding.addOnRebindCallback(new OnRebindCallback() {
          /**
           * 绑定之前
           * @param binding
           * @return 如果返回true就会绑定布局, 返回false则取消绑定
           */
          @Override public boolean onPreBind(ViewDataBinding binding) {
            return false;
          }
    
          /**
           * 如果取消绑定则回调该方法(取决于onPreBind的返回值)
           * @param binding
           */
          @Override public void onCanceled(ViewDataBinding binding) {
            super.onCanceled(binding);
          }
    
          /**
           * 绑定完成
           * @param binding
           */
          @Override public void onBound(ViewDataBinding binding) {
            super.onBound(binding);
          }
        });

    DataBinding也有个数据变更监听器, 可以监听Variable的设置事件

    mDataBinding.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
    
      /**
           * 会在DataBinding设置数据的时候回调
           * @param sender DataBinding生成的类
           * @param propertyId Variable的id
           */
      @Override public void onPropertyChanged(Observable sender, int propertyId) {
        ActivityMainBinding databinding = (ActivityMainBinding) sender;
        switch (propertyId) {
          case BR.data:
            Log.d("日志", "(MainActivity.java:54) ___ Result = " + databinding.getData().getName());
            break;
          case BR.dataSecond:
    
            break;
        }
      }
    });

    DataBindingUtil

    DataBinding不仅可以绑定Activity还可以绑定视图内容(View)

    
    // 视图
    static <T extends ViewDataBinding> T    bind(View root)
    
    static <T extends ViewDataBinding> T    bind(View root, 
                                                 DataBindingComponent bindingComponent)
    
    
    // 布局
    static <T extends ViewDataBinding> T    inflate(LayoutInflater inflater, 
                                                    int layoutId, 
                                                    ViewGroup parent, 
                                                    boolean attachToParent, DataBindingComponent bindingComponent) // 组件
    
    static <T extends ViewDataBinding> T    inflate(LayoutInflater inflater,
                                                    int layoutId, 
                                                    ViewGroup parent, 
                                                    boolean attachToParent)
    
    // activity
    static <T extends ViewDataBinding> T    setContentView(Activity activity, 
                                                           int layoutId)
    
    static <T extends ViewDataBinding> T    setContentView(Activity activity,
                                                           int layoutId, DataBindingComponent bindingComponent)

    还有两个不常用的方法, 检索视图是否被绑定, 如果没有绑定返回nul

    static <T extends ViewDataBinding> T    getBinding(View view)
    
    // 和getBinding不同的是如果视图没有绑定会去检查父容器是否被绑定
    static <T extends ViewDataBinding> T    findBinding(View view)

    其他的方法

    // 根据传的BR的id来返回字符串类型. 可能用于日志输出
    static String   convertBrIdToString(int id)

    例如BR.name这个字段对应的是4, 就可以使用该方法将4转成”name”

    DataBindingComponent

    每个DataBinding都可以拥有一个组件或者说设置一个默认的全局组件

    创建一个Component的步骤:

    1. 创建一个MyDataBindingComponent实现接口DataBindingComponent
    2. 创建MyBindingAdapter类, 用@BindingAdapter修饰其成员方法(不需要静态)
    3. 在MyDataBindingComponent写入一个get**方法()来返回该MyBindingAdapter
    public class MyDefaultComponent implements DataBindingComponent {
    
      public MyBindingAdapter mAdapter = new MyBindingAdapter();
    
      public MyBindingAdapter getMyBindingAdapter() {
        return mAdapter;
      }
    
      class MyBindingAdapter {
        @BindingAdapter("android:text") public void setText(TextView textView, String text) {
        /*省略*/
          textView.setText(text);
        }
      }
    }

    设置默认组件都是由DataBindingUtils设置, 但是方法也有所不同

    static DataBindingComponent getDefaultComponent()
    
    static void setDefaultComponent(DataBindingComponent bindingComponent)

    以上这种设置必须在绑定视图之前设置, 并且是默认全局的, 只需要设置一次.

    static <T extends ViewDataBinding> T    setContentView(Activity activity,
                                                           int layoutId, DataBindingComponent bindingComponent)

    类似于上面这种在绑定视图的同时来设置组件需要每次绑定视图都设置, 否则就会报错.

    或者你可以将@BindingAdapter注解的方法变为Static修饰.

    另外不仅仅是@BindingAdapter可以设置成组件, @InverseBindingAdapter同样可以

    注意

    1. 可以使用include不过不能作为root布局. merge不能使用
    2. 如果没有自动生成DataBinding类可以先写个variable(或者make module下)
    3. 即使你没有绑定数据(你可能会在网络请求成功里面绑定数据), 但是只要视图创建完成就会自定绑定数据. 这个时候数据是空对象. 空对象的字段也会有默认值(String的默认值是NULL, TextView就会显示NULL); 并且如果你用了三元表达式, 空对象的三元表达式都为false; 所以建议不要考虑空对象的情况;
    4. 如果你给一个要求值是布尔类型值的自定义属性(BindingAdapter)赋值一个函数, 空指针的情况会返回false;

    推荐插件

    关于DataBinding我推荐使用插件生成, 方便快捷很多;

    DataBindingModelFormatter

    快捷生成实现Observable的数据模型

    DataBindingSupport

    自动生成DataBinding所需的XML格式

    展开全文
  • Android DataBinding基本用法

    千次阅读 2018-09-09 21:52:31
    一个Demo掌握DataBinding的基本用法 前言 DataBinding 是Google推出的一个支持库,它的作用是:通过layout文件绑定app逻辑和UI的更新,尽可能地减少编码工作。 说定义似乎有些隐晦,还是通过learning by doing的...

    Learning By Doing

    一个Demo掌握DataBinding的基本用法

    前言

    DataBinding 是Google推出的一个支持库,它的作用是:通过layout文件绑定app逻辑和UI的更新,尽可能地减少编码工作。

    说定义似乎有些隐晦,还是通过learning by doing的方式来学习吧。

    这篇文章包括以下几方面的内容:

    • 通过layout来绑定app逻辑
    • layout里包含include标签的用法
    • 点击事件的处理
    • 使用Observablues动态更新UI
    • 使用ObervablueFields动态更新UI

    我会按照上面列的顺序,一步步在Demo里面实现对应的功能

    所有的源码都包含在这篇文章里,需要Demo的可以评论留言

    由于是Learning By Doing,所以不会有理论的讲解,关于为什么是layout便签,为什么支持这样调用等问题,请去Android Developer 看官方文档,或者去Stack Overflow 提问

    通过layout来绑定app逻辑

    注意:

    首先打开Andriod Studio,并创建一个新的项目,然后再正式开始我们的DataBinding之旅。

    加入Databinding

    在app/gradle文件中加入:

    Databinding{
        Eanbled = true
    }

    就可以使用DataBinding了,前提是你的Android Studio是2.1.3以上的版本(目前最新版已经是3.1.4了~)

    编写Bean

    这里我们用User类来演示

    public class User {
    
        private String name;
        private String email;
    
        public String getName() {
            return name == null ? "" : name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getEmail() {
            return email == null ? "" : email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
    }

    Android Stuido: command/ctrl + n 快捷键 可以快速创建构造方法,gettet/setter方法等

    编写layout文件

    打开activity_main.xml 文件,把根目录改成layout,并且加入data 和 variable 标签

    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:bind="http://schemas.android.com/tools">
    
        <data>
            <variable
                name="user"
                type="com.blue.androidlearningcode.databinding.User"/>
    
        </data>
    
         <TextView
                android:id="@+id/name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.name}" />
    
            <TextView
                android:id="@+id/email"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.email}" />
    </layout>
    • layout data variables的官方定义:

      The Data Binding Library automatically generates the classes required to bind the views in the layout with your data objects. The library provides features such as imports, variables, and includes that you can use in your layouts.

    • data:用于定义在DataBinding里面需要用到的数据对象(data objects)

    • variable:比data更小的层级,通常是class,我们这里就是刚刚写的User类
    • textview: 通过@{user.xxx}来绑定user里面的属性

    在代码中绑定xml里面的控件

    回到MainActivity.class文件中,现在我们需要做的就是把刚刚写的layout文件,和生成的DataBinding绑定起来。

    首先,让我们愉快的 clean & rebuild project

    为什么呢? 因为DataBinding需要在Build的过程中生成绑定类

    ps:实际开发中,如果项目过大,可能Restart Android Studio比较快。。

    OK,如果一切顺利的话,就开始写MainActivity的代码吧,在onCreate方法中:

      ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
            user = new User();
            user.setName("BlueLzy");
            user.setEmail("bluehobert@gmail.com");
    
            binding.setUser(user);
    • ActivityMainBinding:这货就是Build完生成出来的东西
    • binding.setUser():绑定对应的bean

    好了,run一下,应该就能看到在两个TextView中分别出现了name和email的value了

    layout里包含include标签的用法

    上面的用法其实就是DataBinding最基本的用法,通过xml和class的绑定,减少我们在class里面编写的代码量。

    但是理想很丰满,现实很骨感,项目中总是会有许多意想不到的需求,例如:我要在layout里面使用include标签,咋整?

    如果直接把include标签加入到父布局,然后运行程序,你会发现是不行的,我们有些小细节要处理。

    So,let‘s do it.

    新建content_data_binding.xml

    在layout文件夹下新建一个布局文件,用于存放我们需要用到的view

    <?xml version="1.0" encoding="utf-8"?>
    <layout
        xmlns:android="http://schemas.android.com/apk/res/android" >
    
        <data>
            <variable
                name="user"
                type="com.blue.androidlearningcode.databinding.User"/>
        </data>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
    
            <TextView
                android:id="@+id/name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.name}" />
    
            <TextView
                android:id="@+id/email"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.email}" />
    
        </LinearLayout>
    
    </layout>

    可以看到,其实我们就是把activity_main.xml的两个TextView搬到了这个新的layout中。

    修改activity_main.xml

    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:bind="http://schemas.android.com/tools">
    
        <data>
            <variable
                name="user"
                type="com.blue.androidlearningcode.databinding.User"/>
    
        </data>
    
            <include
                android:id="@+id/content"
                layout="@layout/content_data_binding"
                bind:user="@{user}" />
    
    </layout>

    其实使用include标签要注意的地方就是:

    1. 父布局的include标签需要加上: bind:user=”@{user}” ,通过这行代码来关联bean和layout
    2. 子布局也需要加上layout,data,variable等标签

    OK,再次运行程序,我们会发现效果和上面的一样,显示出来了name和email的值。

    点击事件的处理

    上面的Demo只是静态的,还无法对view的onClick,onLongClick 或者其他方法进行响应,那么接下来我们首先处理的就是onClick事件

    修改MainActivity

    创建MyClickHandlers内部类

    在FAB点击的时候弹出Toast

       public class MyClickHandlers {
    
            public void onFabClicked(View view){
                Toast.makeText(getApplicationContext(), "FAB clicked!", Toast.LENGTH_SHORT).show();
            }
        }

    修改activity_main.xml

    我们在这里使用FAB(FloatingActionButton)来演示:

    <?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"
        xmlns:bind="http://schemas.android.com/tools">
    
        <data>
            <variable
                name="user"
                type="com.blue.androidlearningcode.databinding.User"/>
    
            <variable
                name="handlers"
                type="com.blue.androidlearningcode.DataBindingTestActivity.MyClickHandlers"/>
        </data>
    
        <android.support.design.widget.CoordinatorLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
    
            <include
                android:id="@+id/content"
                layout="@layout/content_data_binding"
                bind:user="@{user}" />
    
            <android.support.design.widget.FloatingActionButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom|end"
                android:layout_margin="20dp"
                app:srcCompat="@android:drawable/ic_dialog_info"
                android:onClick="@{handlers::onFabClicked}"/>
        </android.support.design.widget.CoordinatorLayout>
    
    </layout>

    绑定MyClickHandlers

    又来到了clean & rebuild 时刻,在onCreate()里面进行绑定

    MyClickHandlers handlers = new MyClickHandlers();
    binding.content.setHandlers(handlers);  // binding content layout click event
    • 为什么上面的User是binding.setUser(),而这里是binding.content.setHandlers()呢:

      这个问题很有趣,细心的童鞋应该是可以发现的,原因在xml的代码里

    OK,let‘s run it!!!

    Sep-09-201820-50-31.gif

    我们虽然没有在代码里面重写onClickListener和onClick方法,但是通过DataBinding的方式还是成功实现了view的onClick(),至于上面的name和email的更新,就是下面要讲的了。

    使用Observablues动态更新UI

    推动人类进步的,其实是懒惰。为了能够少写两行代码,人类总是可以发明出各种新技术。例如:Observablues

    Question:有没有办法在数据变化的时候让UI自动刷新呢?

    答案当然是有的,let me show you:

    修改User类

    public class User extends BaseObservable{
    
        private String name;
        private String email;
    
        @Bindable
        public String getName() {
            return name == null ? "" : name;
        }
    
        public void setName(String name) {
            this.name = name;
            notifyPropertyChanged(BR.name);
        }
    
        @Bindable
        public String getEmail() {
            return email == null ? "" : email;
        }
    
        public void setEmail(String email) {
            this.email = email;
            notifyPropertyChanged(BR.email);
        }
    
    }
    • User继承BaseObservable
    • 每个getter()加上注解@Bindable
    • 每个setter()加上notifyPropertyChanged(BR.xxx); - BR在rebuild后自动生成

    修改MainActivity

    我们只需要改一个地方:onFabClicked()

            public void onFabClicked(View view){
                Toast.makeText(getApplicationContext(), "FAB clicked!", Toast.LENGTH_SHORT).show();
                user.setName("BlueLzyzzz");
                user.setEmail("blueblueblue@163.com");
            }

    我们并没有给textview设置text,而是给user的name和email重新赋值,看一下在FAB点击的时候UI会不会跟随data进行动态刷新。

    run the project 。。。。。and 回头看上面的动图,哈哈哈~

    使用ObervablueFields动态更新UI

    其实上面已经把DataBinding的基本用法讲的差不多了,通过实践的方式一步步学习DataBinding,我觉得还是比较快速和容易上手的。

    至于ObervablueFields,它其实也是为了能够减少代码量,当一个Bean没有那么多属性的时候,我们不需要写这么多的get和set方法,使用ObervablueFields只需要通过两行代码即可实现相同效果。

    下面是Demo:

    修改User类

    加上age属性:

        // 使用ObservableField
        public static ObservableField<String> age = new ObservableField<>();
    
        public static ObservableField<String> getAge() {
            return age;
        }
    

    修改content_data_binding.xml

    多加一个TextView

            <TextView
                android:id="@+id/age"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.age}" />

    修改MainActivity

    修改onFabClicked()

            public void onFabClicked(View view){
                Toast.makeText(getApplicationContext(), "FAB clicked!", Toast.LENGTH_SHORT).show();
                user.setName("BlueLzyzzz");
                user.setEmail("blueblueblue@163.com");
                User.age.set("20");
            }

    其实就是多加了一行,User.age.set(“20”); 因为age是静态变量,所以可以直接引用。

    效果嘛。。还是回头看动图吧~~~

    在实际项目中,通常一个API返回的Bean都会有十几个属性,多的可能有几十个,我们不可能一个个去写getter和setter还有@Bindable,这时候可以通过ObervablueFields来减少工作量,也可以通过插件来自动生成需要的代码。


    展开全文
  • DataBinding快速入门

    2019-09-06 09:55:40
    循序渐进,注重实践,帮助同学们系统的掌握DataBinding的基本使用
  • Android官方DataBinding(二):动态数据更新notifyPropertyChanged附录文章1使用了最简单的Android官方DataBinding技术,所以写的例子以简单明了能说明问题即可。但是附录文章1的数据不能更新,即如果当用户的数据...
    Android官方DataBinding(二):动态数据更新notifyPropertyChanged


    附录文章1使用了最简单的Android官方DataBinding技术,所以写的例子以简单明了能说明问题即可。但是附录文章1的数据不能更新,即如果当用户的数据User的内部变量发生变化时候,不能反映到View层,即TextView不能发生变化,如果是这样,代码是没有意义的,因为现实的开发场景,User的数据肯定要动态变化的,并且动态变化的数据model肯定要反映在view上。本例就继续使用Android官方DataBinding技术,实现动态数据变化更新到View。
    本例要实现一个目的就是,假设附录文章1的User数据模型变化,则变化内容实时更新同步到绑定的view上。
    改造附录文章1的部分内容实现上述目标。
    (1)在User的数据模型定义上:
    package zhangphil.test;
    
    import android.databinding.BaseObservable;
    import android.databinding.Bindable;
    
    /**
     * Created by Phil on 2017/8/17.
     */
    
    public class User extends BaseObservable {
        private String id;
        private String name;
        private String blog;
    
        public User() {
    
        }
    
        public User(String id, String name, String blog) {
            this.id = id;
            this.name = name;
            this.blog = blog;
        }
    
        public void setId(String id) {
            this.id = id;
            notifyPropertyChanged(BR.id);
        }
    
        @Bindable
        public String getId() {
            return this.id;
        }
    
    
        public void setName(String name) {
            this.name = name;
            notifyPropertyChanged(BR.name);
        }
    
        @Bindable
        public String getName() {
            return this.name;
        }
    
        public void setBlog(String blog) {
            this.blog = blog;
            notifyPropertyChanged(BR.blog);
        }
    
        @Bindable
        public String getBlog() {
            return this.blog;
        }
    }
    

    其实变化比较小,在get方法上增加注解@Bindable,在User的set方法内用notifyPropertyChanged通知数据变化,数据模型变化后由Android系统通知get然后刷新view。


    (2)和附录文章1相比,在xml布局里面为TextView增加id:
    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    
        <data>
    
            <variable
                name="user"
                type="zhangphil.test.User" />
        </data>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
    
            <TextView
                android:id="@+id/id"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.id}"
                android:textColor="@android:color/holo_red_light" />
    
            <TextView
                android:id="@+id/name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.name}"
                android:textColor="@android:color/holo_red_light" />
    
            <TextView
                android:id="@+id/blog"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.blog}"
                android:textColor="@android:color/holo_red_light" />
        </LinearLayout>
    </layout>

    这里增加id,是为了后面通过ActivityMainBinding拿到具体的TextView对象,切换到原始的TextView对象操作。


    (3)上层Java代码写一个简单的演示例子,在一个线程内每隔2秒更新一次User的数据字段,当id值自增长到5时候,切换到原始TextView的setText方法实现设置:
    package zhangphil.test;
    
    import android.databinding.DataBindingUtil;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.widget.TextView;
    
    import zhangphil.test.databinding.ActivityMainBinding;
    
    public class MainActivity extends AppCompatActivity {
        private String name = "zhangphil";
        private int id = 1;
        private String blog = "http://blog.csdn.net/zhangphil";
    
        private User user = new User();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
            binding.setUser(user);
    
            final TextView idView = binding.id;
            final TextView nameView = binding.name;
            final TextView blogView = binding.blog;
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                if (id == 5) {
                                    idView.setText("zhang");
                                    nameView.setText("phil");
                                    blogView.setText("blog");
                                } else {
                                    user.setId(String.valueOf(id++) + " " + System.currentTimeMillis());
                                    user.setName(name + " " + System.currentTimeMillis());
                                    user.setBlog(blog + " " + System.currentTimeMillis());
                                }
                            }
                        });
    
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }
    
    本例之所以演示id==5时候,回退到原始的TextView进行操作,是考虑到在某些必要时候,需要拿到View进行显示/隐藏操作。


    代码运行结果:



    当线程运行到id=5时候:



    附录:
    1,《Android官方DataBinding简例(一)》链接:http://blog.csdn.net/zhangphil/article/details/77322530 
    展开全文
  • 不得不说AS更新到3.4后修复了一些隐藏的Bug并加入了一些新东西,如Bundle打包,DataBinding的写法改动 等等,这些突然让笔者觉得陌生,但历史的车轮滚滚向前,没办法只能跟紧脚步,落后就只能被淘汰,这里着重讲一下...

    前言:
    不得不说AS更新到3.4后修复了一些隐藏的Bug并加入了一些新东西,如Bundle打包,DataBinding的写法改动 等等,这些突然让笔者觉得陌生,但历史的车轮滚滚向前,没办法只能跟紧脚步,落后就只能被淘汰,这里着重讲一下Databinding改动后的一些写法。

    加入了generateStubs,不需要再引入databinding了
    build_gradle写法

    apply plugin: 'com.android.application'
    apply plugin: 'kotlin-android'
    apply plugin: 'kotlin-android-extensions'
    apply plugin: 'kotlin-kapt'
    
    android {
        ···
        defaultConfig {
      ·		  ···
        }
        
        dataBinding {
            enabled true
        }
    
    }
    kapt {
        generateStubs = true
    }
    

    可以看到

    1. ActivityMainBinding是编译器生成的
    2. 使用的androidx的包
    3. 没有熟悉的setContentView()了

    Activity 用法

    import androidx.appcompat.app.AppCompatActivity
    import xxx.xxx.xxx.databinding.ActivityMainBinding
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(ActivityMainBinding.inflate(layoutInflater, null, false).root)
        }
    }
    

    Fragment 用法

    import android.os.Bundle
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import androidx.fragment.app.Fragment
    import xxx.xxx.xxx.databinding.FragmentNiceBinding
    
    class NiceFragment : Fragment() {
        private lateinit var binding: FragmentNiceBinding
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
            binding = FragmentNiceBinding.inflate(inflater, container, false)
            binding.setLifecycleOwner(viewLifecycleOwner)
            binding.executePendingBindings()
            return binding.root
        }
    }
    

    那么有人会问:那setContentView没有了 是怎么关联布局文件的呢?
    查看源码可以知道

    1. activity_main.xml文件已经被封装到ActivityMainBinding 这个类里面了
    2. inflate封装的正是DataBindingUtil,也就是进一步简化了代码
    3. ActivityMainBinding的名字也是根据activity_main来的
      因此你会发现无论
      ActivityxxxBinding inflate 到 xxxFragment
      还是
      FragmentxxxBinding inflate 到 xxxActivity
      都没毛病

    Activity的inflate

      @NonNull
      public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
          @Nullable ViewGroup root, boolean attachToRoot, @Nullable DataBindingComponent component) {
        return DataBindingUtil.<ActivityMainBinding>inflate(inflater, dae.rounder.R.layout.activity_main, root, attachToRoot, component);
      }
    

    Fragment的inflate

      @NonNull
      public static FragmentPBinding inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup root,
          boolean attachToRoot, @Nullable DataBindingComponent component) {
        return DataBindingUtil.<FragmentPBinding>inflate(inflater, dae.rounder.R.layout.fragment_p, root, attachToRoot, component);
      }
    

    剩下对之前文章的补充

    onclick的三种写法

    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    
        <data>
    
            <import type="xxx.MainViewModel"/>
    
            <variable
                    name="mainViewModel"
                    type="MainViewModel"/>
        </data>
    
        <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:onClick="@{mainViewModel::expend}"
                android:orientation="vertical">
            <TextView android:layout_width="match_parent"
                      android:onClick="@{() -> mainViewModel.expend_()}"
                      android:layout_height="300dp" android:text="第一部分"/>
            <TextView android:layout_width="match_parent"
                      android:onClick="@{(v) -> mainViewModel.expend__(v)}"
                      android:layout_height="300dp" android:text="第二部分"/>
        </LinearLayout>
    </layout>
    
    

    可以发现

    1. 第二种方法可避免编译器未使用的警告
    2. 第三种方法则可以传参
        fun expend(v: View) {
            Timber.e("expend" + v::class.java.simpleName)
        }
    
        fun expend_() {
            Timber.e("expend_")
        }
    
        fun expend__(v: View) {
            Timber.e("expend__${(v as TextView).text}")
        }
    
    展开全文
  • DataBinding

    2020-07-16 15:58:45
    项目文件名为DataBinding 2、界面及功能 功能:按BUTTON按钮后,TextView中的值会加1,且APP后台运行或翻转屏幕时,TextView中的数值保持不变 3、strings.xml文件内容 <resources> <string name="app_...
  • Android DataBinding 你好!

    千次阅读 2019-02-26 20:52:09
    一:使用DataBinding的第一步,只需要在app/build.gradle开启它 android { compileSdkVersion 28 defaultConfig { applicationId "azhon.com.viewbinding" //.... } //开启dataBinding ...
  • DataBinding2

    2019-12-10 11:59:55
    DataBinding的核心是数据驱动View 即是:数据变化,视图自动变化,DataBinding同时也实现了双向驱动(双向绑定),即是当View的属性变化时,其对应的绑定的数据也会发生变化 1.单项绑定 单项绑定是 当数据改变时和数据绑定...
  • DataBinding的使用(一)

    千次阅读 2019-10-14 13:58:25
    DataBingding个人使用很久了,觉得很好用且方便,让自己的代码更加简单且少,读取来更有可读性。...DataBinding的基础学习地址为:DataBinding中文官方文档 使用注意细节 虽然DataBinding使用简单,但是...
  • DataBinding的用法介绍

    2020-05-20 15:02:15
    DataBinding的用法介绍 DataBinding是Google 发布的一个数据绑定框架,它能够让开发者减少重复性非常高的代码,如findViewById 这样的操作。其核心优势是解决了数据分解映射到各个view的问题,在MVVM框架中,实现的...
  • DataBinding编写spinner

    2017-12-01 03:13:54
    各位大佬,小弟刚开始学习DataBinding,可以实现DataBinding对textview的操作,但是我看到网上DataBindingspinner的操作很少找不到,实际工作中要用,我模拟了一下,但是不成功,不知道自己错在哪里,希望大佬们...
  • Android DataBinding 详解

    千次阅读 2018-03-28 22:11:12
    前几天小试牛刀写了一篇 Android DataBinding 初探,只是简单的介绍了一下 DataBinding 的几个小问题,并没有特别详细的去介绍 DataBinding 的更多方法,这几天看了一下 DataBinding 的官网的相关内容,觉得有必要...
  • DataBinding原理分析

    千次阅读 2018-03-18 15:42:02
    现在公司用的框架是MVVM,用到MVVM肯定少不了databinding。 最近业务上遇到一个问题,需要动态生成一张图片,并将图片分享出去。我的思路就是写一个xml动态生成一个View,利用databinding绑定这个View,然后获取...
  • Databinding的使用

    千次阅读 2018-01-30 19:03:52
    这段有时间就看了下Databinding,简单记录一下Databinding的使用方式! 一、Databinding简单尝试 首先在Module app下build.gradle中配置databinding android{ ... dataBinding { enabled true } ... } ...
  • DataBinding源码深入分析

    千次阅读 2019-04-17 19:39:39
    在使用databinding时,更新UI界面,如果是使用普通变量的,那么在变量发生变化的时候,UI界面并不会发生变化 一、数据绑定 视图跟随数据刷新 数据变化是视图也跟着变化则需要使用到以下两种方法 继承BaseObservable...
  • 玩转Android之MVVM开发模式实战,炫酷的DataBinding

    万次阅读 多人点赞 2016-07-31 17:42:47
    C# 很早就有了MVVM的开发模式,Android手机中的MVVM一直到去年Google的I\O大会上才推出,姗姗来迟。MVVM这中开发模式的优点自不必多说,可以实现视图和逻辑代码的解耦,而且,按照Google的说法,使用了MVVM的开发...
  • Android Databinding 详解

    千次阅读 2020-10-13 15:03:43
    Android Databinding 详解 ​ DataBinding 是Google官方发布的一个框架,用来进行数据绑定。DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity / Fragment 内的代码,数据能够单向或双向...
  • Databinding 使用

    2018-06-12 11:19:01
    第一:https://blog.csdn.net/qby_nianjun/article/details/79198166一、Databinding简单尝试 首先在Module app下build.gradle中配置databindingandroid{ ... dataBinding { enabled true } ... }1234...

空空如也

1 2 3 4 5 ... 20
收藏数 18,222
精华内容 7,288
关键字:

databinding