DataBinding的用法小记

732 阅读7分钟

databinding不仅可以进行事件的绑定即通过添加与原有方法相同的方法名来进行定义,也可以通过拉姆达表达式的形式来命名任何形式的绑定。 比如:在xml中绑定android的click事件,我们必须按照如下形式

public void clickTest(View view){//必须与onClick定义的返回值与入参一样
//do something
}

在xml中,比如在一个btn下,需要这样写android:onClick="@{presenter::clickTest}" 但是如果是利用拉姆达表达式,则无需那么严格的遵循某一规则,这时候可以随意的传参及使用,比如我要在click的单击事件中传递一个employee的对象则可以如下形式

android:onClick="@{()->presenter.onClickLBinding(employee)}"

在presenter中的定义应该是这样:

public void onClickLBinding(Employee e){
// do something
}

如果include的标签内部需要某一个变量的值的话可以按照下面这样过写(比如需要employee)

<include 
layout = ""layout/xxx 
bind:employee="@{employee}"/>

如果在定义ViewModel也就是bean的时候,继承来BaseObserverbal并且在get方法上添加@Bindable注解,那么在setter方法中利用notifyPropertyChanged(BR.属性名)来实时更新UI,或者在setter方法中直接notifyChange也可以(此时get方法不要@bindable注解)

动态绑定:recyclerView 在onBindViewHolder中可以通过如下代码来确定加载的布局

final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item,item);
holder.getBinding().executePendingBindings();

比如在recyclerView中应用,最关键的莫过于adapter的编写,而新建的adapter需要继承recyclerView的adapter,并且都需要指定继承RecyclerView的ViewHolder。这里我们建一个通用的ViewHolder。

public class BindingViewHolder<T extends ViewDataBinding> extends RecyclerView.ViewHolder {

    private T mBinding;

    public BindingViewHolder(T binding) {
        super(binding.getRoot());

        mBinding = binding;
    }

    public T getBinding() {
        return mBinding;
    }
}

然后是adapter的编写,已经注释了。

public class EmployeeAdapter extends RecyclerView.Adapter<BindingViewHolder> {

    /**
     * 首先定义不同类型的区分
     */
    private static final int ITEM_VIEW_TYPE_ON = 1;
    private static final int ITEM_VIEW_TYPE_OFF = 2;

    /**
     * 其次定义点击时的接口回调,当然也可以不用该接口,直接在XML中绑定需要响应的事件及参数
     * 这样做的另一个好处是可以对不同类型的布局响应不同的事件
     */
    private OnItemClickListener mListener;
    /**
     * 这个就是一般的数据源
     */
    private List<Employee> mEmployeeList;
    /**
     * 可能需要这个context来做一些事情
     */
    private Context mContext;
    /**
     * 加载布局时候使用
     */
    private final LayoutInflater mLayoutInflater;

    public void setListener(OnItemClickListener listener) {
        mListener = listener;
    }


    public interface OnItemClickListener {
        void onEmployeeClick(Employee employee);
    }

    public EmployeeAdapter(Context context) {
        mContext = context;
        mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mEmployeeList = new ArrayList<>();
    }

    /**
     * 这个方法是自己复写的,用于确定不同的类型
     *
     * @param position
     * @return
     */
    @Override
    public int getItemViewType(int position) {
        Employee employee = mEmployeeList.get(position);
        if (employee.isFired()) {
            return ITEM_VIEW_TYPE_OFF;
        } else {
            return ITEM_VIEW_TYPE_ON;
        }
    }

    /**
     * 根据不同类型的加载不同的布局
     *
     * @param parent
     * @param viewType
     * @return
     */
    @Override
    public BindingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ViewDataBinding binding;
        if (viewType == ITEM_VIEW_TYPE_ON) {//在这个布局中,通过employee变量给textview赋值,通过presenter响应点击
            binding = DataBindingUtil.inflate(mLayoutInflater, R.layout.item_employee_on, parent, false);
        } else {//在这个布局中,仅有通过employee给textView赋值
            binding = DataBindingUtil.inflate(mLayoutInflater, R.layout.employee_off, parent, false);
        }
        return new BindingViewHolder(binding);
    }

    /**
     * 绑定数据及事件
     *
     * @param holder
     * @param position
     */

    @Override
    public void onBindViewHolder(BindingViewHolder holder, int position) {

        //动态绑定
        final Employee employee = mEmployeeList.get(position);
        //以前用的是setStu,但ViewDataBinding是之前用的那些类的父类,只有自动生成的类才有setXXX方法。
        // 在该类中设置变量只能通过setVariable方法
        //第一个参数是我们变量名的引用,第二个参数是设置的值
        holder.getBinding().setVariable(BR.employee, employee);
        holder.getBinding().setVariable(BR.presenter, new Presenter());
        holder.getBinding().executePendingBindings();
        //此处是通过接口响应点击事件
        /*holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mListener!=null){
                    mListener.onEmployeeClick(employee);
                }
            }
        });*/
    }

    @Override
    public int getItemCount() {
        return mEmployeeList.size();
    }

    public void addAll(List<Employee> list) {
        mEmployeeList.addAll(list);
    }

    /**
     * 模仿随机插入
     */
    Random mRandom = new Random(System.currentTimeMillis());

    public void add(Employee employee) {
        int postition = mRandom.nextInt(mEmployeeList.size() + 1);
        mEmployeeList.add(postition, employee);
        notifyItemInserted(postition);
    }

    /**
     * 模拟随机删除
     */
    public void remove() {
        if (mEmployeeList.size() == 0) {
            return;
        }
        int pos = mRandom.nextInt(mEmployeeList.size());
        mEmployeeList.remove(pos);
        notifyItemRemoved(pos);
    }

    /**
     * 点击事件的响应
     */
    public class Presenter {
        public void onItemClick(Employee employee) {
            Toast.makeText(mContext, employee.getFirstName(), Toast.LENGTH_SHORT).show();
        }
    }
}

这里在不同的item布局中分别使用了变量employee来对textView进行赋值,而且在on的布局中通过presenter来对点击事件进行处理,这个关键的是变量的设置,主要体现的是现在用setVariable(),而不是setXXX属性。 Activity中其实没什么

public class MainActivity extends AppCompatActivity {

    ActivityMainBinding mBinding;
    EmployeeAdapter mEmployeeAdapter;

    public class Presenter {
        public void onClickAddItem(View view) {

            mEmployeeAdapter.add(new Employee("haha", "nihao", false));
        }

        public void onClickRemoveItem(View view) {
            mEmployeeAdapter.remove();
        }
        
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        mBinding.setPresenter(new Presenter());
        mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
        mEmployeeAdapter = new EmployeeAdapter(this);
        mBinding.recyclerView.setAdapter(mEmployeeAdapter);
       /* mEmployeeAdapter.setListener(new EmployeeAdapter.OnItemClickListener() {

            @Override
            public void onEmployeeClick(Employee employee) {
                Toast.makeText(MainActivity.this, employee.getFirstName(), Toast.LENGTH_SHORT).show();
            }
        });*/

        List<Employee> mDataList = new ArrayList<>();
        mDataList.add(new Employee("zhangsan", "lisi", false));
        mDataList.add(new Employee("zhangsan1", "lisi1", false));
        mDataList.add(new Employee("zhangsan2", "lisi2", true));
        mDataList.add(new Employee("zhangsan3", "lisi3", true));
        mDataList.add(new Employee("zhangsan4", "lisi4", false));

        mEmployeeAdapter.addAll(mDataList);
    }
}

以上这些只是dataBinding的基本用方法,还有一些高级技巧如下

自定义属性: 也就是说这个属性没有现成的set方法,这时候需要定义一个set方法;或者对应的属性与set方法并不是一致的,这时候可以使用bindingMethods来绑定属性对应的set方法。 比如imageView有一个属性为tint,但这个属性并没有setTint方法,而是setImageTintList,这时候要在类的前面加上注释

这时候dataBinding找setTint的时候就会找到setImageTintList方法

Binding适配器: 没有对应的set方法时候就会用到BindingAdapter来创建相应的方法,只要在某个类的某个方法上利用该注解

比如在XMl中的某个imageView中使用属性 app:imageUri="",就会调用该方法

比如针对上面个的例子,利用glide来加载图片我们定义两个属性,一个是图片的url,另一个是请求出错时默认显示的图片,这里就可以这样定义

public class DemoBindingAdapter {
    @BindingAdapter({"imageUrl", "placeHolder"})
    public static void loadImageFromUrl(ImageView imageView, String url, Drawable drawable) {
        Glide.with(imageView.getContext())
                .load(url)
                .placeholder(drawable)
                .into(imageView);
    }
}

在XML中直接使用即可:

<?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="url"
            type="String"/>
    </data>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:layout_width="150dp"
            android:layout_height="150dp"
            app:imageUrl="@{url}"
            app:placeHolder="@{@drawable/weixin}"/>
    </RelativeLayout>
</layout>

BindingConversion: 转换为setter需要的属性

bindingConversion会把用到dataBinding的属性值按照定义的函数进行转换。比如刚才的例子,在XML中添加如下内容

<layout ...>
    <data>
...
        <variable
            name="date"
            type="java.util.Date"/>
    </data>
    <LinearLayout
        ...>
...
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{date}"/>
    </LinearLayout>
</layout>

只需定义一个将date类型转换为string类型的函数即可,在此我们将其创建在adapter中如下:

public class DemoBindingAdapter {
...
    @BindingConversion
    public static String formatDate(Date date){
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd-hh-mm");
        return simpleDateFormat.format(date);
    }
}

使用时只需在activity中为date变量赋值即可,此处为 binding.setDate(new Date());

双向绑定:比如登录时获取用户信息。 老的即单向绑定时一般这样写

双向绑定:即赋值方式变成“@=”

双向绑定的内部实现: 通过

public interface InverseBindingListener{
void onChange();
}

该方法实际上是在生成的Binding文件中对相关view进行绑定的。

并不是所有的属性都支持双向绑定,只有具有额外事件的才可以,比如text,checked,progress等。 没有了textwatcher,监听属性变更可以通过model来实现,比如刚才的edittext,我们为这个model添加一个addOnPropertyChangedCallback监听器来实现

FormModel f = new FormModel("zhangsan","23");
binding.setModel(f);
f.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
    @Override
    public void onPropertyChanged(Observable observable, int i) {
        //i为model的BR中的int值
        Toast.makeText(MainActivity.this, String.valueOf(i), Toast.LENGTH_SHORT).show();
    }
});

这样只要输入框有变化就会弹出Toast

表达式链:包括简化表达式和隐式更新 重复的表达式:比如这种形式

可以看出,只需写一个,其余处用到时只需利用定义处的“id.属性”即可

我们可以利用这种形式来给某些特定的监听器传参

比如在XML中如下定义

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="presenter"
            type="com.example.lamadademo.MainActivity.Presenter"/>

        <variable
            name="employee"
            type="com.example.lamadademo.Employee"/>
    </data>

    <LinearLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{() -> presenter.onEmployeeClick(employee)}"
            android:text="@{employee.firstName}"/>

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onLongClick="@{(v)-> presenter.onEmployeeLongClick(employee,context)}"

            android:text="@{employee.lastName}"/>

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="111111"
            android:onFocusChange="@{(v,fcs) -> presenter.onFocusChange(employee)}"/>

    </LinearLayout>
</layout>

presenter 如下

public class Presenter {
    public void onEmployeeClick(Employee employee) {
        Toast.makeText(MainActivity.this, "onEmployeeClick", Toast.LENGTH_SHORT).show();
    }

    public boolean onEmployeeLongClick(Employee employee, Context context) {
        Toast.makeText(context, "onEmployeeLongClick", Toast.LENGTH_SHORT).show();
        return false;
    }

    public void onFocusChange(Employee employee) {
        Toast.makeText(MainActivity.this, "onFocusChange", Toast.LENGTH_SHORT).show();
    }
}

按这种写法刚进页面时就会调用onFocusChange,点击按钮会调用相应方法。