findViewById替代方案:Android Jetpack MVVM之BindingAdapter

本文的目标是阐述BindingAdapter使用方法并以点击事件为例

在介绍它之前,我们先讲一下,布局文件中的系统属性android:onClick="" ,不知道你有没有用过,其中双引号中间就是你要触发的事件名称。不需要用findViewById获取对应控件,然后再监听click事件,若是你用过请略过,若是没有过,我们看看怎么用的。

举个例子:
在布局文件中设置android:onClick=“clickEvent”

<ImageView    
        android:layout_width="wrap_content"    
        android:layout_height="wrap_content"    
        android:onClick="clickEvent"   
        android:src="@drawable/tes" />

在activity中新建一个clickEvent函数:

public void clickEvent(View v) {   
    Toast.makeText(this, "test", Toast.LENGTH_LONG).show();
}

这样就直接可以监听ImageView的单击事件,这样是不是很方便,为啥要介绍这个呢,是因为本文BindingAdapter也要采用同样的方式来实现,只是我们不采用系统的定义的属性,而是自己定义属性。

首先要讲一下attrs.xml,它是res/values目录下的一个文件,为什么讲它?因为它跟bindingadapter紧密相连。

  • 自定义属性
    自定义性有两个部分组成,第一,属性名,也就是名字,第二,属性值,也就是属性格式format。看下面的一段代码:

<resources>
    <attr name="backColor" format="color">
        <enum name="redColor" value="1" />
    <declare-styleable name="MyView">
        <attr name="backColor"/>
    declare-styleable>
resources>

以attr开头的代表着具体某个属性。

以declare-styleable代表的某个组。例如我想为一个view自定义一个属性,假设这个view为MyView,则MyView下面定义所有属性都属于MyView组,凡是继承它的都可以使用。当然也可以为系统控件加上自定义属性。

系统属性和自定义属性是有区别的,属性名前面有“android:”都是系统属性。自定属性名没有没有什么好说的,尽量不要和系统定义的属性名雷同。

若是和系统定义的属性一样,不设置format的话,就沿用系统的。若是设置的话,就是对系统属性的重定义,但是有些系统属性格式是无法修改的。

简单介绍一下属性的format:

reference 引用
color 颜色
boolean 布尔
integer 整型
dimension
float 浮点型
string 字符型
fraction 百分比
enum 枚举型
flag

自定义属性搞定了,其实没什么难度,那么接下来,我们要看看如何自定义属性和BindingAdapter建立连接的呢。其实也很简单,还是以一个例子来说明

  • 首先,自定义属性,我们给系统的控制View自定义onClickCommand属性,只要属于View这个组的都可以使用这个属性。
<resources>
       <declare-styleable name="View">
           <attr name="onClickCommand" format="reference" />    
       declare-styleable>
 resources>

这个属性name,就是BindingAdapter的关键。

  • 其次,BindingAdapter

它的一般格式为:

@BindingAdapter(value={“属性名1”,“属性名2”})
public static void 函数名(参数){
//函数内容
}

具体的实例如下:新建一个文件为ViewAdapter.java,并类中添加如下代码

@BindingAdapter(value = {"onClickCommand"}, requireAll = false)
public static void click(View view, final ClickCallBack bindingCommand) {    
    view.setOnClickListener(new View.OnClickListener() {        
        @Override        
        public void onClick(View view) {           
        if (bindingCommand != null) {                
           bindingCommand.clickEvent();               
           Toast.makeText(view.getContext(), "it works", Toast.LENGTH_LONG).show();           
        } else {                
            Toast.makeText(view.getContext(), "binding command is null", Toast.LENGTH_LONG).show();           
        }        
    }    
 });
}

BindingAdapter中value要和attrs.xml中自定义属性保持一致,否则绑定不成功。

绑定的函数一定要是static的,函数名没有具体的要求,函数里面的第一个参数为要绑定对象,我们绑定的为View,那就是View;

后面的参数就是各个属性,在布局文件中一定要按顺序进行,否则可能会报错。

还需要在布局文件中应用这个属性

<ImageView
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
        android:src="@drawable/tes"   
        binding:onClickCommand="@{viewmodel.mBindingCommand}" 
/>

到目前为止,代码层面是没有问题,但是还不能用,因为还要binding。

ActivityMainBinding binding = DataBindingUtil.setContentView(this, getLayoutResId()); 

其中ActivityMainBinding是系统自动生成的,这一步实现布局文件和属性之间进行绑定,也就是可以实现单击事件了。

最后,我们不仅要是实现事件绑定还能在ViewModel中回调,因为需要对事件进行业务的处理

在ViewModel中,需要具体实现BindingAdapter的回到,如下:

public ClickCallBack mBindingCommand = new ClickCallBack() {    
    @Override    
    public void clickEvent() {        
    Log.e("tag", "click");    
    }
};

只需要在布局文件中绑定这个回调即可生效。

但是你发现回调对象为空,因为还需要设置

binding.setVariable(BR.viewmodel, mViewModel);

这样才是完整的BindingAdapter过程。

值得注意的是binding.setVariable(BR.viewmodel, mViewModel);必须在创建ViewModel之后,否则你绑定回调参数为空,因为ViewModel没有创建为空,可以看自动生成的代码,自动生成的代码也可调试。

若是你想统一对ImageView都用Glide进行加载图片,你就可以通过这个中方式实现

@BindingAdapter({"imageUrl"})
    public static void loadImage(ImageView view, String u) {
        RequestOptions options = new RequestOptions()
                .centerCrop()
                .placeholder(R.mipmap.ic_launcher_round)
                .error(R.mipmap.ic_launcher)
                .priority(Priority.HIGH)
                .diskCacheStrategy(DiskCacheStrategy.NONE);
        Glide.with(view.getContext()).applyDefaultRequestOptions(options).load(u).transition(new DrawableTransitionOptions().crossFade(1000)).into(view);
    }

代码量会很少,效率会很高。

你可能感兴趣的:(Android,MVVM)