美文网首页Android
Android Data Binding——高级

Android Data Binding——高级

作者: FlyDragonInSky | 来源:发表于2017-04-23 15:47 被阅读139次

    上一篇文章Android Data Binding——进阶
    介绍了Data Binding的语法等进阶功能。这一篇我们来介绍一下Data Binding的数据对象。

    文中的例子可前往DataBindingDemo查看。

    任何POJO对象都可以用在data binding中,但是对象改变时候,要如何通知UI更新呢?这是使用Data Binding最奥妙的地方。Ps:我们这边只是介绍如何使用,没有涉及到实现原理。

    有三种不同的数据变化通知机制:observable objects, observable fields, and observable collections.

    这些observable对象绑定到UI上,当对象的属性更改时就会自动通知UI更新。

    Observale Objects

    一个继承Observable接口的类,data binding会设置一个listener用于监听绑定的对象的属性变化。

    public interface Observable {
    
        /**
         * Adds a callback to listen for changes to the Observable.
         * @param callback The callback to start listening.
         */
        void addOnPropertyChangedCallback(OnPropertyChangedCallback callback);
    
        /**
         * Removes a callback from those listening for changes.
         * @param callback The callback that should stop listening.
         */
        void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback);
    
        /**
         * The callback that is called by Observable when an observable property has changed.
         */
        abstract class OnPropertyChangedCallback {
    
            /**
             * Called by an Observable whenever an observable property changes.
             * @param sender The Observable that is changing.
             * @param propertyId The BR identifier of the property that has changed. The getter
             *                   for this property should be annotated with {@link Bindable}.
             */
            public abstract void onPropertyChanged(Observable sender, int propertyId);
        }
    }
    

    Observable接口有注册/删除监听的方法,但是数据变化时是否通知取决于开发者。为了简化开发,data binding提供了一个BaseObservable的基类,帮我们实现了监听的注册和删除。这个类也实现了通知数据变化的方法,在getter中使用Bindable注解,在setter中调用notifyPropertyChanged通知数据变更。

    public class ObservableUser extends BaseObservable {
        public String firstName;
        public String lastName;
    
        public ObservableUser(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
    
        @Bindable
        public String getFirstName() {
            return firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
            notifyPropertyChanged(BR.firstName);
        }
    
        @Bindable
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
            notifyPropertyChanged(BR.lastName);
        }
    }
    

    Bindable注解会在编译时在BR中生成一个entry,当数据变化时调用notifyPropertyChanged通知这个entry数据发生了变化。

    ObservableFields

    创建Observable类还是比较麻烦的,data binding为我们提供了一个便捷的ObservableField类以及它的派生类:
    ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable.

    ObservableFields是包含了一个单一属性的observable objects,可以通过声明一个public final field来使用它:

    public class ObservableFieldUser extends BaseObservable {
        public final ObservableField<String> firstName = new ObservableField<>();
        public final ObservableField<String> lastName = new ObservableField<>();
    
        public ObservableFieldUser(String firstName, String lastName) {
            this.firstName.set(firstName);
            this.lastName.set(lastName);
        }
    }
    

    然后可以用set/get来存取数据:

    user.firstName.set("bai");
    String s = user.firstName.get();
    

    Observable Collections

    有些应用希望使用更加灵活的结构来管理数据,Observable集合类允许使用key来访问这些数据对象。

    • 如果key是String,ObservableArrayMap会非常有用:
    ObservableMap<String, String> userMap = new ObservableArrayMap<>();
    userMap.put("firstName", "bai");
    userMap.put("lastName", "li");
    binding.setUserMap(userMap);
    

    然后在布局文件中用String keys获取map中的数据:

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    
        <data>
            <import type="android.databinding.ObservableMap" />
            <variable
                name="userMap"
                type="ObservableMap<String,String>" />
        </data>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{`obserable map : ` + userMap[`firstName`] + ` ` + userMap[`lastName`]}" />
        </LinearLayout>
    </layout>
    
    • 如果key是integer,ObservableArrayList会非常有用:
    ObservableList<User> useList = new ObservableArrayList<>();
    useList.add(new User("bai", "li"));
    binding.setUserList(useList);
    

    然后在布局文件中使用下标获取list中的数据:

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    
        <data>
            <import type="com.dragonjiang.databindingdemo.model.User" />
            <import type="android.databinding.ObservableList" />
            <variable
                name="userList"
                type="ObservableList<User>" />
        </data>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{`obserable list : ` + userList[0].toString()}" />
        </LinearLayout>
    </layout>
    
    

    生成绑定

    自动生成的Binding类都继承了ViewDataBinding类,它们是连接layout的variables和Views的桥梁。

    Creating

    binding在View inflate之后创建。inflate方法会将Veiw绑定到binding上,对于不同的Veiw有不同的创建方法:

    MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
    MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
    

    如果布局使用不同的机制inflate,可以单独绑定:

    MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
    
    

    有时候绑定不能提前确定,例如ListView的Item layout,这时候可以使用DataBindingUtil类:

    ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
        parent, attachToParent);
    ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
    
    

    有ID的View

    我们在之前的例子里面都没有给View声明一个id,因为用不到。但是如果有些情况下,我要调用到布局里面的特定的View,还是需要一个id。data binding提供了一个比findViewById更快的机制:

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{user.firstName + ` ` + user.lastName}" />
    

    data binding会在binding类中自动生成对应的属性:

    public final TextView tvName;
    

    可以直接使用:

    binding.tvName.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Toast.makeText(LayoutDetailsActivity.this, binding.tvName.getText(), Toast.LENGTH_SHORT).show();
        }
    });
    

    ViewStubs

    ViewStub不同于正常的View,它一开始是不可见的,在需要时才加载出特定的布局。所以data binding提供了一个ViewStubProxy类来代替ViewStub,开发者可以通过这个类来操作ViewStub

    ViewStub需要在inflate时候创建一个binding,故需要设置监听ViewStub.OnInflateLister

    public class ViewStubActivity extends AppCompatActivity {
    
        private ActivityViewStubBinding mBinding;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mBinding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
            mBinding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
                @Override
                public void onInflate(ViewStub stub, View inflated) {
                    IncludeBinding binding = DataBindingUtil.bind(inflated);
                    binding.setUser(new User("bai", "li"));
                }
            });
        }
    
        public void onClick(View view) {
            if (!mBinding.viewStub.isInflated()) {
                mBinding.viewStub.getViewStub().inflate();
            }
        }
    }
    
    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    
        <data></data>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="onClick"
                android:text="inflate view_stub" />
    
            <ViewStub
                android:id="@+id/view_stub"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout="@layout/include" />
        </LinearLayout>
    </layout>
    
    

    高级绑定

    有些情况下,例如RecyclerView.Adapter中我们无法事先知道binding类。需要在onBindViewHodler(VH, int)中给binding赋值。
    在这种情况下,RecyclerView布局内都设置了一个item变量,可以通过getBinding方法返回一个ViewDataBinding类:

    public void onBindViewHolder(VH holder, int position) {
        holder.binding.setModel(mDataList.get(position));
        holder.binding.executePendingBindings();
    }
    

    注意到上面executePendingBindings()表示立即绑定。如果没有指定立即执行,在数据变化时,binding会在下一帧开始前触发。

    属性设置

    当绑定的数据变化时,自动生成的binding类会寻找对应属性的setter方法。data binding框架设置了几种自定义赋值的机制。

    自动Setter

    对于一个属性,data binding 尝试找到对应的setter方法,例如我们自定义了一个UserView类,实现一个setUser方法:

    public class UserView extends AppCompatTextView {
        public UserView(Context context) {
            super(context);
        }
    
        public UserView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        public UserView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        public void setUser(User user) {
            this.setText(user.toString());
        }
    }
    

    在布局文件中使用:

    <com.dragonjiang.databindingdemo.ui.UserView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:user="@{user}" />
    

    data binding自动为我们找到了setUser(User user)的方法。

    重命名Setter

    有的属性的名称与它的setter不匹配,对于这类属性,可以使用注解BindingMethods将属性与setter关联起来。例如下面这个例子将andorid:tintsetImageTintList关联起来:

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

    自定义Setter(Binding Adapter)

    有些属性需要自定义属性设置逻辑,例如没有android:paddingLeft属性对应的setter方法。但是有setPadding(left, top, right, bottom)。一个用BindingAdapter注解的静态方法允许开发者自定义setter:

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

    BindingAdapter的方法还可以获取旧的值。只需将旧的值放前面,新的值放后面:

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

    BindingAdapter很强大,尤其对自定义属性。比如可以用来异步加载图片:

    @android.databinding.BindingAdapter({"imageUrl", "error"})
        public static void loadImage(ImageView view, String url, Drawable error) {
            Glide.with(view.getContext()).load(url).error(error).into(view);
        }
    
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:error="@{@drawable/ic_launcher}"
        app:imageUrl="@{user.avatar}" />
    

    imageUrlerror属性被使用时,就会匹配调用BindindAdapter的loadImage方法。

    转换器

    对象转换

    如果binding表达式返回一个对象,data binding会寻找对应的setter(自动setter、重命名setter、自定义setter),然后将返回的对象强制转换成setter需要的类型。
    这是一个使用ObservableMap的例子:

    <TextView
       android:text='@{userMap["lastName"]}'
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
    

    userMap返回一个对象,这个对象会被自动转换为setText(CharSequence)需要的类型。如果类型转换有问题,开发者需要受到进行类型转换。

    自定义转换

    有时候需要对一些特定的类型直接做转换,例如设置背景:

    <com.dragonjiang.databindingdemo.ui.UserView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@{user.isAdult ? @color/colorAccent : @color/colorPrimary}"
        app:user="@{user}" />
    

    这里background需要Drawable类型,而color是int类型,此时需要一个BindingConversation将int转为ColorDrawable:

    @BindingConversion
    public static ColorDrawable convertColorToDrawable(int color) {
       return new ColorDrawable(color);
    }
    

    注意:转换只能在setter时生效,所以不允许混合类型

    <View
       <!--这是不允许的-->
       android:background="@{isError ? @drawable/error : @color/white}"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
    

    参考资料

    相关文章

      网友评论

        本文标题:Android Data Binding——高级

        本文链接:https://www.haomeiwen.com/subject/axryzttx.html