Android DataBinding扫盲篇(二)

856 阅读3分钟

这是我参与8月更文挑战的第26天,活动详情查看:8月更文挑战

Android DataBinding扫盲篇(二)

上篇文件简单的讲解了数据绑定的基本操作,但是上篇文章是单向的绑定,这篇文章讲如何进行双向绑定,以及如何使用复杂点的业务场景。

一、双向绑定

什么被称为双向绑定?

数据改变,同步更新到视图上;视图改变,也同步修改数据。

数据改变同步视图很好理解,我们拿到服务器数据后就会封装成对象,对象映射给view直接显示就可以了; 视图改变同步数据,我们的数据都呈现在视图上了,为什么还要同步一遍数据,这是因为安卓中有内容会变化的组件,比如EditTextCheckBox

按照MVVM的思想,它是这样的流程。

graph LR
A[model] --> B[viewModel]
B --> A

B --> C[view]
C --> B

那么在代码中如何实现双向绑定呢?

CheckBox 单向绑定数据

<CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="@{person.isSelect}"/>

CheckBox 双向绑定数据

<CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="@={person.isSelect}"/>

仅仅从@{person.isSelect} -> @={person.isSelect} 即可。

EditText 双向绑定也是一样,当EditText改变的过程中,对象属性也跟着变化了。

eg

 <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@={person.name}" />

二、进阶用法

2.1 加载图片

布局文件

<ImageView    android:layout_width="wrap_content"    android:layout_height="wrap_content"    app:url="@{person.photo}" />

Bean文件

@BindingAdapter("url")
    public static void setURL(ImageView imageView, String photo) {
        Glide.with(imageView.getContext()).load(photo).into(imageView);
    }

注意布局文件中的app:url 是自定义属性,需要导入xmlns 且和Bean文件中的@BindingAdapter("url") 的 url 一定要对应,这样才能识别到。

我看到有些是这么人是这么写的 @BindingAdapter("bind:url"), 这样写也可以。

Bean文件中的 setURL 可以随意命名,因为它是靠上方的注解识别的。

2.2 ListView加载列表

1. 准备界面

像往常一样,在布局中写一个ListView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

        <ListView
            android:id="@+id/list_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
</LinearLayout>

2. 准备viewmodel类

与平常封装对象不同的是,这个对象类继承了BaseObservable 实现了数据的绑定,并增加了一些方法,比如显示图片、设置点击事件等。

public class Person extends BaseObservable {

    private int id;
    private String name;
    private String photo;

    public Person() {
    }

    public Person(int id, String name, String photo) {
        this.id = id;
        this.name = name;
        this.photo = photo;
    }

    @Bindable
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
        notifyPropertyChanged(BR.id);
    }

    @Bindable
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }

    @Bindable
    public String getPhoto() {
        return photo;
    }

    public void setPhoto(String photo) {
        this.photo = photo;
        notifyPropertyChanged(BR.photo);
    }


    @BindingAdapter("url")
    public static void setURL(ImageView imageView, String photo) {
        Glide.with(imageView.getContext()).load(photo).into(imageView);
    }

    public void itemClick(View view) {
        Log.d("Person", "item click");
    }
}

3. 准备item的布局

item的布局中@{} 和数据产生了联系,使它们能够绑定在一块。 如果想要执行点击事件,就需要添加 android:onClick="@{person.itemClick}"

<?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="person"
            type="com.demo.mvvmdemo.Person" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal"
        android:onClick="@{person.itemClick}">

        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            app:url="@{person.photo}" />

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

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{String.valueOf(person.id)}" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{person.name}" />

        </LinearLayout>

    </LinearLayout>
</layout>

4. 准备适配器

这里我模仿着别人写的通用的适配器,普通的效果都能满足了。 来看看适配器怎么实现的。

public class CommonAdapter<T> extends BaseAdapter {

    private final String TAG = "CommonAdapter";

    LayoutInflater mLayoutInflater;
    int mLayoutId;
    int mVariableId;
    List<T> mList;

    public CommonAdapter(LayoutInflater layoutInflater, int layoutId, int variableId, List<T> list) {
        this.mLayoutInflater = layoutInflater;
        this.mLayoutId = layoutId;
        this.mVariableId = variableId;
        this.mList = list;
    }

    @Override
    public int getCount() {
        return mList.size();
    }

    @Override
    public Object getItem(int position) {
        return mList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewDataBinding binding;
        // 复用convertView
        if (convertView == null) {
            binding = DataBindingUtil.inflate(mLayoutInflater, mLayoutId, parent, false);
            convertView = binding.getRoot();
            convertView.setTag(binding);
        } else {
            binding = (ViewDataBinding) convertView.getTag();
        }
        binding.setVariable(mVariableId, mList.get(position));
        return binding.getRoot();
    }
}

5. ListView和视图绑定

模拟了一些数据,然后ListView绑定上适配器就可以了。

public class MainActivity extends AppCompatActivity {

    List<Person> mPersonList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();

        ListView listView = findViewById(R.id.list_view);

        CommonAdapter<Person> personCommonAdapter = new CommonAdapter<>(getLayoutInflater(), R.layout.item, BR.person, mPersonList);

        listView.setAdapter(personCommonAdapter);

    }

    private void initData() {
        for (int i = 0; i < 10; i++) {
            mPersonList.add(new Person(i, "姓名" + i, "https://c-ssl.duitang.com/uploads/blog/202107/12/20210712132638_bd95f.thumb.1000_0.jpg"));
        }
    }
}

2.3 RecyclerView加载列表

除了适配器的不同和绑定方式不同之外,其余的还是按照ListView的方式,没有变动,下面代码是RecyclerView的适配器。

public class CommonAdapter<T> extends RecyclerView.Adapter<CommonAdapter.CommonViewHolder> {

    private final String TAG = "CommonAdapter";

    LayoutInflater mLayoutInflater;
    int mLayoutId;
    int mVariableId;
    List<T> mList;

    public CommonAdapter(LayoutInflater layoutInflater, int layoutId, int variableId, List<T> list) {
        this.mLayoutInflater = layoutInflater;
        this.mLayoutId = layoutId;
        this.mVariableId = variableId;
        this.mList = list;
    }


    @NonNull
    @Override
    public CommonViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int i) {
        ViewDataBinding viewDataBinding = DataBindingUtil.inflate(mLayoutInflater,mLayoutId, parent, false);
        CommonViewHolder commonViewHolder = new CommonViewHolder(viewDataBinding.getRoot().getRootView());
        commonViewHolder.setViewDataBinding(viewDataBinding);
        return commonViewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull CommonAdapter.CommonViewHolder commonViewHolder, int position) {
        //更新视图
        commonViewHolder.setContent(mList.get(position));
    }


    @Override
    public long getItemId(int position) {
        return position;
    }

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

    public class CommonViewHolder extends RecyclerView.ViewHolder{

        ViewDataBinding viewDataBinding;

        public CommonViewHolder(@NonNull View itemView) {
            super(itemView);
        }

        public ViewDataBinding getViewDataBinding() {
            return viewDataBinding;
        }

        public void setViewDataBinding(ViewDataBinding viewDataBinding) {
            this.viewDataBinding = viewDataBinding;
        }

        public void setContent(T t) {
            viewDataBinding.setVariable(mVariableId, t);
            viewDataBinding.executePendingBindings();
        }
    }
}