Android DataBinding 使用教程(二)

上一篇:Android DataBinding 使用教程(一)

在上一篇教程中,讲述了一些 DataBinding 的简单用法,是大家可以对DataBinding 有一个基本了解。本篇我将为大家介绍一些高级用法,比如点击事件的设置,注解的使用等。

事件绑定

前一篇说了那么多的属性绑定,相信很多人对 DataBinding 关于事件处理 也很有兴趣吧。

在事件处理中,DataBinding 同样允许有我们使用表达式来进行绑定。除了少数几个,需要绑定的事件处理 属性 是由它的监听器里的方法名称。"View.OnLongClickListener 中的onLongClick()"方法,那么该事件的绑定名称就是 "onLongClick"。处理事件有两种方法:

  1. 方法引用:可以直接引用  和 原本需要实现该Listener 中监听方法签名(参数和返回值) 一致的方法。如果我们使用了方法引用,那么DataBinding 会将 方法引用和持有者对象 封装成一个Listener,绑定到对应的 View 上。如果表达式为空,那么数据绑定不会监听,而是将监听设置为空。
  2. 监听绑定:和方法引用不一样,DataBinding 会总是设置一个 View 上的监听,当事件分派时,DataBinding 就会执行该Lambda表达式。

方法引用

以简单的 “onClick” 为例, Button的点击事件 方法名成是:public void onClick(View v),那么我们在类里需要写一个 签名一样的方法。

public class EventHandler {
    public void onBtnClick(View v) {
        Log.i("DataBinding", "heiheihei");
    }
}
<data>
        <variable
            name="eventHandler"
            type="com.example.myapplication.EventHandler" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <Button
            android:id="@+id/dataBindingBtn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{eventHandler.onBtnClick}"
            android:text="数据绑定按钮" />
    </LinearLayout>

自定义了一个方法处理类,里面有一个方法 void onBtnClick(View v),在布局中 给 Button 绑定点击事件 “android:onClick=@{eventHandler.onBtnClick}”,除了这样写,还可以写成:android:onClick=@{eventHandler::onBtnClick}。请大家记住,如果表达式没写对或者格式不对,那么在编译处理时,就会报错。

监听绑定

监听绑定是事件发生时执行绑定表达式。在监听绑定中,只要方法的返回值 和 原Listener 中需要实现的方法的返回值一直就可以。

public class EventHandler {

    public void onBtnClick() {
        Log.i("DataBinding", "heiheihei");
    }
}
 <Button
            android:id="@+id/dataBindingBtn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{() -> eventHandler.onBtnClick()}"
            android:text="数据绑定按钮" />

监听绑定仅允许 Lambda 表达式。可以忽略原来Listener中的参数,但是返回类型需要一致。如果需要参数,则可以这样

public class EventHandler {

    public void onBtnClick(View v, People people) {
        Log.i("DataBinding", people.getName());
    }
}
 <Button
            android:id="@+id/dataBindingBtn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{(view) -> eventHandler.onBtnClick(view,peopleInfo)}"
            android:text="数据绑定按钮" />

区别:方法引用是在 数据绑定时创建的监听,而监听绑定是在事件触发时对表达式求值进行绑定。

避免混淆监听

绝大多数的点击事件是 onClick(View v),但是也有一些控件的点击事件 监听方法不是 这个,比如:

扫描二维码关注公众号,回复: 8705818 查看本文章

注解

@BindingConverison   

可是使用这个注解 对 数据类型 进行转换。

举个栗子,将 所有的 表达式返回的 string 进行更改:

  <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{peopleInfo.name}"
            android:textSize="25sp" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{peopleInfo.age}"
            android:textSize="25sp" />
  activityMainBinding.setPeopleInfo(new People("张三", "22岁"));
 @BindingConversion
    public static String parseString(String string) {
        return string + "heiheihei";
    }

这个被注解的方法可以随便写在 某一个类里,这里DataBinding 检测到 表达式返回的 类型 和 被注解方法里的参数类型 一致,就会更新成注解方法的返回值。

接下来我们看看转换的类型:

  <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@{@color/colorPrimary}"
            android:text="@{peopleInfo.name}"
            android:textSize="25sp" />
@BindingConversion
    public static ColorDrawable colorToDrawable(int color) {
        Log.i("databinding","调用转换方法");
        return new ColorDrawable(color);
    }

这里就是把一个 整型的Color 值 转换成了  ColorDrawable。
 
@BindingAdapter

当我们在控件的属性上 写下一个  Databinding  表达式,那么它会自动去找 有没有 set属性的 方法,前提是表达式返回的类型要和 方法的参数类型一致。但是属性的命名空间可以不一样。

比如说,android:text 这个属性,如果我们传入的 DataBinding  表达式 返回的是 Int,那么 数据绑定就会自动去 控件里 找  setText( Int ) 这个方法,如果表达式返回的是 String,就会去找 setText(String) 这个方法。  如果一个控件没有很多属性,但是有很多 setXxx() 方法,那么我们也可以在布局里 使用 setXxxx() 中的 “Xxx” 属性值,前提参数类型要一致。

除了上述所说的 自动属性设置方法,那么我们想给 控件 增加 属性,怎么办? 回答改控件代码的请走开,源码怎么改,又有人说 那就用包装器模式,这个还靠点谱,但是每次都用一个包装器类 给控件增加属性,是不是有点繁琐。我们可以使用 @BindingAdapter 注解。

  <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{peopleInfo.name}"
            android:textSize="25sp"
            app:textColorRes="@{R.color.colorAccent}" />
@BindingAdapter(value = {"textColorRes"})
    public static void setTextColorRes(TextView textView, int resId) {
        textView.setTextColor(textView.getResources().getColor(resId));
    }

可以在任何一个Java 类中写下 这个 绑定方法,下面我们来说说 这个绑定方法:

  • 方法必须是 public static 开头
  • 方法名称可以随便起名
  • 参数中 第一个参数必须是 控件或其父类 类型
  • 最后还有一个 requireAll,表明 所有的属性 必不可少,默认是true。如果是false,在使用参数时 需要判空,因为有些参数没有配置。
@BindingAdapter(value = {"textColorRes", "textContent"}, requireAll = false)
    public static void setTextColorRes(TextView textView, int resId, String content) {
        textView.setTextColor(textView.getResources().getColor(resId));
        if (!TextUtils.isEmpty(content)) {
            textView.setText(content);
        }
    }

@BindingMethods  @BindingMethod

当我们的控件有很多 setXxx() 设置属性的方法时,但是我们在 XML 中写的属性名字 与 setter后面的字段  不一样,怎么办呢,我们需要 将属性名字 和 setter 方法 绑定起来,这就用到了上面的注解。

@BindingMethods({@BindingMethod(type = TextView.class, attribute = "content", method = "setContentTxt")})
public class CustomText extends TextView {
    public CustomText(Context context) {
        super(context);
    }

    public CustomText(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public void setContentTxt(CharSequence text) {
        this.setText(text);
    }

}

自定义了 控件 CustomText,通过注解 将 属性(“content”) 和 方法(setContentTxt)绑定起来。注意了,这个 @BindingMethods 注解里可以放很多 @BindingMethod,还有,这个 注解不光可以加在相对应的控件类上方,也可以放在其他任何类上面。

 <com.example.myapplication.CustomText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:content="@{peopleInfo.name}" />

看到这里,好奇心强的同学就问了,如果控件里 有一个setXxx()方法,对应的属性是“Xxx”,这是如果 你再给这个属性 配置到另一个 方法上,那么databinding 会执行哪个方法呢?答案是 会执行 setXxx()方法。

前面的注解都是单向的,即 数据改变通知 UI 更新,但是有时候,我们也需要 在一些可以操作的UI 控件上改变了状态后,数据也希望是立即更新,这就是双向绑定。

接下来我们看看双向绑定的注解。

@InverseBindingAdapter

public class CustomSeekBar extends SeekBar {
    private static InverseBindingListener mInverseBindingListener;

    public CustomSeekBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public CustomSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public CustomSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }


    private void initView() {
        this.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                //UI 驱动 数据
                if (null != mInverseBindingListener) {
                    mInverseBindingListener.onChange();
                }

            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
    }

    @BindingAdapter(value = {"customProgress"})
    public static void setSeekBarProgress(CustomSeekBar customSeekBar, int progress) {

        if (progress == customSeekBar.getProgress()) {
            return;
        }
        customSeekBar.setProgress(progress);
    }

    @InverseBindingAdapter(attribute = "customProgress", event = "progressAttrChanged")
    public static int getSeekBarProgress(CustomSeekBar customSeekBar) {
        return customSeekBar.getProgress();
    }

    @BindingAdapter(value = "progressAttrChanged", requireAll = false)
    public static void setProgressAttrChanged(CustomSeekBar changed, InverseBindingListener inverseBindingListener) {

        if (null != inverseBindingListener) {

            mInverseBindingListener = inverseBindingListener;
        }
    }
}
 <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <com.example.myapplication.CustomSeekBar
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:max="100"
            app:customProgress="@={data.progress}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="30dp"
            android:layout_gravity="center_horizontal"
            android:text="@{String.valueOf(data.progress)}"
            android:textSize="25sp" />
    </LinearLayout>
  • 双向绑定 请使用 "@={ xxx }"
  • 数据 驱动 UI,之前的 @BindingAdapter 必不可少
  • UI 驱动 数据,数据绑定 会为我们自动产生一个 InverseBindingListener 的继承者,并实现了 onChange() 方法。这个onChange()方法就是用来更新 数据 的。
  • @InverseDataBinding() 中 ,event=“” 是非必填的,默认就是 “xxxAttrChanged”,这里我把它设置为 “progressAttrChanged”,所以下面那个@BindingAdapter 中的 value属性 也是“progressAttrChanged”,如果前面不设置,默认就是 “customProgressAttrChanged”。
  • 当UI 改变了,我们需要调用  InverseBindingListener 中的 onChange() 方法来 更新  数据。

@InverseBindingMethods    @InverseBindingMethod

@InverseBindingMethods({@InverseBindingMethod(type = CustomSeekBar.class, attribute = "customProgress",
        event = "customProgressAttrChanged")})
public class CustomSeekBar extends SeekBar {
    private InverseBindingListener mInverseBindingListener;

    public CustomSeekBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public CustomSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public CustomSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }


    private void initView() {
        this.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                //UI 驱动 数据
                if (null != mInverseBindingListener) {
                    mInverseBindingListener.onChange();
                }

            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
    }

    public void setCustomProgress(int progress) {

        if (progress != getProgress()) {
            super.setProgress(progress);
        }

    }

    public int getCustomProgress() {
        return this.getProgress();
    }


    public void setCustomProgressAttrChanged(InverseBindingListener inverseBindingListener) {

        if (null != inverseBindingListener) {

            mInverseBindingListener = inverseBindingListener;
        }
    }
}

这两个注解 是写在类上面的,只不过把双向绑定的各个属性 集合在一起了,这里要注意一点,就是布局中的 属性值 要和方法里 setter 和 getter 的属性名字 一致,否则会编译出错。

发布了6 篇原创文章 · 获赞 2 · 访问量 412

猜你喜欢

转载自blog.csdn.net/tocong2015/article/details/103457483