Android Data Binding 高级用法

承接上一篇博客,Android Data Binding入门,这篇博客来看看Data Binding的高级用法。

1. 列表绑定

在Android中,列表是展示内容的最好方式,比如ListView、GridView、RecyclerView。前面我也写了一篇博客,介绍了RecyclerView的用法,请参考Android RecyclerViews实现下拉列表功能。这里用DataBinding绑定RecyclerView来实现一个列表,具体细节不在赘述,方法如下:

ListActivity.java

package com.jackie.sample.databinding;

import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.view.View;
import android.widget.Toast;

import com.jackie.sample.databinding.databinding.ActivityListBinding;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Administrator on 2016/10/29.
 */

public class ListActivity extends AppCompatActivity {
    private ActivityListBinding mBinding;
    private EmployeeAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_list);

        mBinding.setPresenter(new Presenter());

        mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
        mAdapter = new EmployeeAdapter(this);
        mBinding.recyclerView.setAdapter(mAdapter);
        mAdapter.setListener(new EmployeeAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(Employee employee) {
                Toast.makeText(ListActivity.this, employee.getFirstName(), Toast.LENGTH_SHORT).show();
            }
        });

        List<Employee> employeeList = new ArrayList<>();
        employeeList.add(new Employee("Cheng1", "Jackie1", false));
        employeeList.add(new Employee("Cheng2", "Jackie2", false));
        employeeList.add(new Employee("Cheng3", "Jackie3", true));
        employeeList.add(new Employee("Cheng4", "Jackie4", false));
        mAdapter.addAll(employeeList);
    }

    public class Presenter {
        public void onClickAddItem(View view) {
            mAdapter.add(new Employee("Huang", "Ashia", false));
        }

        public void onClickRemoveItem(View view) {
            mAdapter.remove();
        }
    }
}
EmployeeAdapter.java
package com.jackie.sample.databinding;

import android.content.Context;
import android.databinding.DataBindingUtil;
import android.databinding.ViewDataBinding;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Created by Administrator on 2016/10/29.
 */

public class EmployeeAdapter extends RecyclerView.Adapter<BindingViewHolder> {
    private LayoutInflater mInflater;
    private OnItemClickListener mListener;
    private List<Employee> mEmployeeList;

    public static final int ITEM_VIEW_TYPE_ON = 1;
    public static final int ITEM_VIEW_TYPE_OFF = 2;

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

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

    public EmployeeAdapter(Context context) {
        mInflater = LayoutInflater.from(context);
        mEmployeeList = new ArrayList<>();
    }

    @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;
        }
    }

    @Override
    public BindingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ViewDataBinding binding;
        if (viewType == ITEM_VIEW_TYPE_ON) {
            binding = DataBindingUtil.inflate(mInflater, R.layout.item_employee_on, parent, false);
        } else {
            binding = DataBindingUtil.inflate(mInflater, R.layout.item_employee_off, parent, false);
        }
        return new BindingViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(BindingViewHolder holder, int position) {
        final Employee employee = mEmployeeList.get(position);
        holder.getBinding().setVariable(com.jackie.sample.databinding.BR.item_employee, employee);
        holder.getBinding().executePendingBindings();
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mListener != null) {
                    mListener.onItemClick(employee);
                }
            }
        });
    }

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

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

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

    public void remove() {
        if (mEmployeeList.size() == 0) {
            return;
        }

        int position = new Random().nextInt(mEmployeeList.size());
        mEmployeeList.remove(position);
        notifyItemRemoved(position);
    }
}
BindingViewHolder.java
package com.jackie.sample.databinding;

import android.databinding.ViewDataBinding;
import android.support.v7.widget.RecyclerView;

/**
 * Created by Administrator on 2016/10/29.
 */

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;
    }
}

activity_list.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <!-- 绑定 -->
    <data>
        <variable
            name="presenter"
            type="com.jackie.sample.databinding.ListActivity.Presenter">
        </variable>
    </data>

    <LinearLayout
        android:id="@+id/activity_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:context="com.jackie.sample.databinding.ListActivity"
        android:orientation="vertical">

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{ presenter.onClickAddItem }"
            android:text="ADD"/>

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{ presenter.onClickRemoveItem }"
            android:text="REMOVE"/>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</layout>

效果如下:


2.自定义属性

默认的android命名空间下,我们会发现并不是所有的属性都能直接通过data binding进行设置,比如margin,padding,还有自定义View的各种属性。遇到这些属性,我们就需要自己去定义它们的绑定方法。
Setter
就像Data Binding会自动去查找get方法一下,在遇到属性绑定的时候,它也会去自动寻找对应的set方法。拿DrawerLayout举一个例子:
</android.support.v4.widget.drawerlayout>
如此,通过使用app命名空间,data binding就会去根据属性名字找对应的set方法,scrimColor -> setScrimColor:

public void setScrimColor(@ColorInt int color) {
    mScrimColor = color;
    invalidate();
}

如果找不到的话,就会在编译期报错。
利用这种特性,对一些第三方的自定义View,我们就可以继承它,来加上我们的set函数,以对其使用data binding。

比如Fresco的SimpleDraweeView,我们想要直接在xml指定url,就可以加上:

public void setUrl(String url) {
    view.setImageURI(TextUtils.isEmpty(url) ? null : Uri.parse(url));
}

这般,就能直接在xml中去绑定图片的url。这样是不是会比较麻烦呢,而且有一些系统的View,难道还要继承它们然后用自己实现的类?其实不然,我们还有其他方法可以做到自定义属性绑定。
BindingMethods
如果View本身就支持这种属性的set,只是xml中的属性名字和java代码中的方法名不相同呢?难道就为了这个,我们还得去继承View,使代码产生冗余?
当然没有这么笨,这时候我们可以使用BindingMethods注释。
android:tint是给ImageView加上着色的属性,可以在不换图的前提下改变图标的颜色。如果我们直接对android:tint使用data binding,由于会去查找setTint方法,而该方法不存在,则会编译出错。而实际对应的方法,应该是setImageTintList。

这时候我们就可以使用BindingMethod指定属性的绑定方法:

@BindingMethods({
@BindingMethod(type = “android.widget.ImageView”,
                     attribute = “android:tint”,
                      method = “setImageTintList”),
})

我们也可以称BindingMethod为Setter重命名。
BindingAdapter
如果没有对应的set方法,或者方法签名不同怎么办?BindingAdapter注释可以帮我们来做这个。
比如View的android:paddingLeft属性,是没有对应的直接进行设置的方法的,只有setPadding(left, top, right, bottom),而我们又不可能为了使用Data Binding去继承修改这种基础的View(即便修改了,还有一堆继承它的View呢)。又比如那些margin,需要修改必须拿到LayoutParams,这些都无法通过简单的set方法去做。

这时候我们可以使用BindingAdapter定义一个静态方法:

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
    view.setPadding(padding,
                    view.getPaddingTop(),
                    view.getPaddingRight(),
                    view.getPaddingBottom());
}

事实上这个Adapter已经由Data Binding实现好了,可以在android.databinding.adapters.ViewBindingAdapter看到有很多定义好的适配器,还有BindingMethod。如果需要自己再写点什么,仿照这些来写就好了。

我们还可以进行多属性绑定,比如:

@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
   Picasso.with(view.getContext()).load(url).error(error).into(view);
}

来使用Picasso读取图片到ImageView。
BindingConversion
有时候我们想在xml中绑定的属性,未必是最后的set方法需要的,比如我们想用color(int),但是view需要Drawable,比如我们想用String,而view需要的是Url。这时候我们就可以使用BindingConversion:

<view :="" android:background="“@{isError" android:layout_height="“wrap_content”/" android:layout_width="“wrap_content”" color="" red=""></view>
@BindingConversion
    public static ColorDrawable convertColorToDrawable(int color) {
        return new ColorDrawable(color);
3.双向绑定

自定义Listener过去,我们需要自己定义Listener来做双向绑定:

<edittext android:aftertextchanged="“@{callback.change}”/" android:text="“@{user.name}”"></edittext>

public void change(Editable s) {
    final String text = s.toString();
    if (!text.equals(name.get()) {
        name.set(text);
    }
}

需要自己绑定afterTextChanged方法,然后检测text是否有改变,有改变则去修改observable。
新方式 - @=

现在可以直接使用@=(而不是@)来进行双向绑定了,使用起来十分简单:

<pre name="code" class="html"><edittext android:inputtype="textNoSuggestions" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="@={model.name}"></edittext>
 

这样,我们对这个EditText的输入,就会自动set到对应model的name字段上。

实现如下:

activity_two_way.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="model"
            type="com.jackie.sample.databinding.FormModel"/>
    </data>

    <LinearLayout
        android:id="@+id/activity_two_way"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.jackie.sample.databinding.TwoWayActivity"
        android:orientation="vertical">

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

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

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{model.username}"/>
    </LinearLayout>
</layout>

TwoWayActivity.java

package com.jackie.sample.databinding;

import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

import com.jackie.sample.databinding.databinding.ActivityTwoWayBinding;

/**
 * Created by Administrator on 2016/10/29.
 */

public class TwoWayActivity extends AppCompatActivity {
    private ActivityTwoWayBinding mBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_two_way);
        mBinding.setModel(new FormModel("jackie.cheng", "123456"));
    }
}

FormModel.java

package com.jackie.sample.databinding;

import android.databinding.BaseObservable;
import android.databinding.Bindable;

/**
 * Created by Administrator on 2016/10/29.
 */

public class FormModel extends BaseObservable {
    private String username;
    private String password;

    @Bindable
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
        notifyPropertyChanged(com.jackie.sample.databinding.BR.username);
    }

    @Bindable
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
        notifyPropertyChanged(com.jackie.sample.databinding.BR.password);
    }

    public FormModel(String username, String password) {
        this.username = username;
        this.password = password;
    }
}

效果如下:


3.Lambda表达式

在入门篇中有提到,可以参考。

4.动画

activity_animation.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <import type="android.view.View"/>

        <variable
            name="presenter"
            type="com.jackie.sample.databinding.AnimationActivity.Presenter"/>

        <variable
            name="showImage"
            type="boolean"/>
    </data>

    <LinearLayout
        android:id="@+id/activity_animation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:context="com.jackie.sample.databinding.AnimationActivity"
        android:orientation="vertical">

        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:visibility="@{showImage ? View.VISIBLE : View.GONE}"
            android:src="@mipmap/ic_launcher"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onCheckedChanged="@{presenter.onCheckedChanged}"
            android:text="显示图片"/>
    </LinearLayout>
</layout>
添加动画:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值