安卓 DataBingding详解

作者: 勤能不能补拙 | 来源:发表于2017-12-10 21:29 被阅读108次

    DataBingding这个东西是15年推出的一个框架,去年尝试使用了一下,感觉不是很好用,包括IDE的兼容也不是很好,所以最后放弃,一年左右没有动过也忘得差不多了,不过最近谷歌退出了一个新的架构模式AAC(Android Architecture Components),研究了一下发现这个搭配DataBingding一起使用简直很美味啊,包括官方的Demo也是这么干的,所以重新学习了一下该框架,在此做个总结加深印象,至于AAC就不多说,还没有完全摸透,官方链接:https://developer.android.google.cn/develop/index.html

    一,DataBingding好处

      1,moudle与ViewD的几乎完全解耦
    
      2,节省大量无脑力代码,包括findId,adapter,setOnClick()....等
      
      3,据说布局的性能优于findviewbyId
    
      4,结合AAC对app的生命周期的一系列操作更加便捷
    
      .....
    

    当然,有好处必然也会有一些坏处

    1,xml的赋值方法,对于不熟悉,或者不是很熟悉的人来说,代码阅读起来会很头疼,代码量大的时候会显得很混乱,除了databingding常用者,或者代码开发者来说,比较恶心
    
    2,个人感觉出现问题不是很好定位(比如说偶尔的xml忘记导包,导致编译错误等等)
    
    3,IDE偶尔会抽风
    

    不过综合起来,如果你已经熟练使用DataBingding,个人觉的马上行动起来,他的好处远远大于他的坏处.

    二,基本使用

    此类教程网上N多,多少无义,直接来到正题,
    你需要在你要使用的这个Acitivity或者fragment的布局最外层嵌套一层layout,然后在laout正配置你需要用到的一系列东西,如下代码,这个是布局是一个列表的item,关于列表稍后再说,xml中配置相关的说明在下面代码中已经做了详细说明,请注意注释的文字说明.

    <layout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
      >
      //将你N多配置写在data标签下
        <data>
            /**
            import即使字面意思导包的意思,这个很常用,具体有俩种理解吧:
            1,比如说如果你有一个实体类叫做User,但是那你在下面需要配置很多个User的变量user1,user2....
            你也可以每个变量都写上这个User的全路径,不过这么做很不好,建议直接导包.然后在创建变量的时候Type
            写上这个类名即可,当然如果类名有重复,我们是课已自己定义一个别名的,在你导入的包的后面加上
            alias="user"即可
            2,比方说如下的textView你要使用一个系统的东西Color,你如果不导包的话编译是会出错的,所以你使用系统的
            必须导包(这种情况有很多,Color,View等等...),当然有个包是例外的,就是java的lang包,如Srting类...
          */
            <import type="android.graphics.Color"/>
    
            /**
            这个就像他的字面意思,变量,variable是你为这个xml定义的一个个变量.如下,你需要给它起一个名字name,
            然后你需要为他指定一个type,这个type可以使你上面导包的类名,也可以是一个类的全类名,也可是是你导包
            时候设置的别名,variable支持后多类型,比如String,int.....
            */
            <variable
                name="user"
                type="luo.com.shiyu.bingdingdemo.User"/>
            <variable
                name="position"
                type="Integer"/>
            <variable  
                name="str"  
                type="String"/>  
             <variable  
                name="error"  
                type="boolean"/>  
             <variable  
                name="num"  
                type="int" />  
            <variable  
                name="list"  
                type="ArrayList<String>" />  
            <variable  
                name="map"  
                type="HashMap<String, String>" />  
            <variable  
                name="array"  
                type="String[]" />  
            /**
            下面这些是某博客说定义list,map的key,其实这写也只是一个变量,和上面的没有任何区别,你把它当作key
            它就是key,你把它当作一个int变量或者一个String变量都是可以的,也可以这么理解,list的索引是一个int,
            所以我们定义一个int,起个名字叫做index而已,没有别的.
            */
            <variable  
                name="listKey"  
                type="int" />  
            <variable  
                name="mapKey"  
                type="String" />  
            <variable  
                name="arrayKey"  
                type="int" />  
        </data>
        <android.support.constraint.ConstraintLayout
    
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="8dp"
                android:layout_marginEnd="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginTop="8dp"
                /**
                关于使用,就是这种固定的写法,很多文章都讲解的非常细致,包括一些三元表达式等...需要的可以自行查阅,
                基本java代码中的一些表达写法都可以使用,当然不可已new,或者你调用的方法的返回值必须和这个xml属性匹配
                */
                android:text="@{user.firstName}"
                android:textColor="@{position%2==0?Color.RED:Color.BLUE}"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
        </android.support.constraint.ConstraintLayout>
    </layout>
    
    

    三,关于xml中的说明基本就是上面这么多,下面是几点注意事项

     1,@{user.firstName} 像这种情况,其实是调用的user的getFirstName,所以,在你不实用ObservableField这些包装类的情况下你必须要重写getFirstName
    
     2,同理你如果要使用一个方法的话,这个方法也必须是public修饰的,使用方法有俩中写法,   android:onClick="@{user.show}",像这样的,这个方法必须没有参数,或者有参数View,有参数View的一般都是绑定事件,多用在点击事件中.android:onClick="@{user.show()}"这也是一种写法,就是把函数后面的俩括号加上,而且方法是可以传递参数的
    
     3,android:text='@{user.firstName+"liudehua"}'  如果你要在表达式中引入一个字符串的时候,请这样写,注意引号.
    

    在java代码中的用法
    系统会自动为我们生成一个根据你这个Activity或者fragment名字的ViewDataBinding的子类,也就是下面代码中的ActivityMainBinding,然后我们就可以获取数据(网络获取,数据库获取等等)然后调用setUser设置我们xml中定义的变量,系统会根据你xml中定义的variable 的name属性生成一堆set方法,你可以更具自己情况去设置.

       ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this,   R.layout.activity_main);
       user.setFirstName("1111111111");
       viewDataBinding.setUser(user);
    

    当我们调用set方法的时候,xml中相对应的值就会改变,然后ui也会发生变化,当然你也可以根据id来设置,只需要为我们的控件设置一个id,viewDataBinding 中就会保存一个这个id的变量,你可以做一些操作

    四,然后此时基本就可以使用databingding了,不过之前我们有一个概念叫做双向绑定,如何做到呢?现在的情况是我们把一个user设置给bingding,但是如果我们值设置一次,之后的user的值无论如何变化都是不会改变ui的,这个时候谷歌爸爸为我们提供了几个东西:

    BaseObservable
    我们可以通过Observable的方式去通知UI数据已经改变了,当然了,官方为我们提供了更加简便的方式BaseObservable,我们的实体类只需要继承该类,稍做几个操作,就能轻松实现数据变化的通知。如何使用呢? 首先我们的实体类要继承BaseObservale类,第二步在Getter上使用注解@Bindable,第三步,在Setter里调用方法notifyPropertyChanged,第四步,完成。就是这么简单,下面我们来实际操作一下。
    首先定义一个实体类,并继承BaseObservable

    public class User extends BaseObservable {  
        private String firstName;  
      
        public User () {  
        }  
      
        public Student(String name) {  
            this.name = name;  
        }  
      
        @Bindable  
        public String getFirstName() {  
            return name;  
        }  
      
        public void FirstName(String name) {  
            this.firstName= firstName;  
            //BR是类是java的R文件的一个东西,它保存有我们所有字段
            notifyPropertyChanged(BR.firstName);  
        }  
    }  
    

    ObservableFields家族
    上面使用BaseObservable已经非常容易了,但是google工程师还不满足,继续给我们封装了一系列的ObservableFields,这里有ObservableField,ObservableBoolean,ObservableByte,ObservableChar,ObservableShort,ObservableInt,ObservableLong,ObservableFloat,ObservableDouble,ObservableArrayMap,ObservableArrayList,ObservableParcelable
    ObservableFields的使用方法就更加简单了

    public class User {
        public   ObservableField<String> firstName=new ObservableField<>();
        public ObservableInt age = new ObservableInt();  
    }
    

    这里我们就不用重写get方法了,因为内部已经替我们实现了,而且也不用我们调用notifyPropertyChanged了,个人建议使用这中方式,第一中的代码入侵太大.

    五,recycleview的用法
    这个东西说实话在listview中实在很好用,我们直接来看怎们用,首先来一个布局,就是我们开局时候的布局代码,
    然后来看adapter怎们用:
    1,onCreateViewHolder方法中我们来搞一个ViewDataBinding 出来
    2,然后正常的写一个holder,
    3,让这个holder保存一个ViewDataBinding ,因为这个holder是列表item的holder,所以保存的也是item布局的ViewDataBinding ,布局不匹配是会出大问题的.
    4,为这个holder的ViewDataBinding 创建get,set方法方便获取设置
    5,在onBindViewHolder中设置布局中需要的变量
    6,最后在onBindViewHolder中调用 bingDing.executePendingBindings(),次方法是立即刷刷新界面,正常情况是在下一帧刷新.

    public class MainAdapter  extends RecyclerView.Adapter<MainAdapter.MainHolder>{
    
        private Context context;
        private ArrayList<User> list=new ArrayList<>();
    
        public MainAdapter(Context context) {
            this.context = context;
        }
    
        @Override
        public MainHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.activity_main_item, parent, false);
            MainHolder mainHolder = new MainHolder(binding.getRoot());
            mainHolder.setBingDing(binding);
            return mainHolder;
        }
    
        @Override
        public void onBindViewHolder(MainHolder holder, int position) {
            ViewDataBinding bingDing = holder.getBingDing();
            bingDing.setVariable(BR.user,list.get(position));
            bingDing.setVariable(BR.position,position);
            bingDing.executePendingBindings();
        }
    
        @Override
        public int getItemCount() {
            return list.size();
        }
    
        public void setData(ArrayList<User> data) {
            list.clear();
            list.addAll(data);
        }
    
        public  static class MainHolder extends RecyclerView.ViewHolder{
    
            private ViewDataBinding bingDing;
    
            public MainHolder(View itemView) {
             super(itemView);
         }
    
            public ViewDataBinding getBingDing() {
                return bingDing;
            }
    
            public void setBingDing(ViewDataBinding bingDing) {
                this.bingDing = bingDing;
            }
        }
    }
    

    然后是activity的使用

    public class MainActivity extends AppCompatActivity {
    
        private ActivityMainBinding viewDataBinding;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
            MainAdapter mainAdapter = new MainAdapter(this);
            LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL,false);
            ArrayList<User> users = initData();
            mainAdapter.setData(users);
            viewDataBinding.rl.setLayoutManager(linearLayoutManager);
            viewDataBinding.rl.setAdapter(mainAdapter);
            User user = new User();
            users.add(user);
            mainAdapter.setData(users);
            users.get(10).setFirstName("hahahahahah");
        }
    
        private ArrayList<User> initData() {
            ArrayList<User> users = new ArrayList<>();
            for (int i = 0; i < 50; i++) {
                User user = new User();
                user.setFirstName("学生"+i+"号");
                users.add(user);
            }
            return  users;
        }
        
    }
    

    我们发现代码是不是简洁了很多啊.

    六,其他用法

    下面这中情况可能我们会遇到,当我们布局中有个imageview的时候貌似databingding就失灵了,因为我们图片都是从网络加载的啊,别担心dataBingding为我们准备了一个东西来解决这类问题

    比如布局如下:我们为ImageView 设置了这么一个属性 app:image="@{imageUrl}
    这是怎们回事,请往下看

    <layout xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:app="http://schemas.android.com/apk/res-auto">  
        <data class=".....User">  
            <variable  
                name="imageUrl"  
                type="String" />  
        </data>  
      
        <ImageView  
            android:layout_width="match_parent"  
            android:layout_height="wrap_content"  
            app:image="@{imageUrl}"/>  
    </layout> 
    

    我们需要定义这么一个方法,你可以专门搞个BingdingUtils 方此类的方法,
    在这个方法中我们用到一个注解BindingAdapter,这个就是来自定义一个get方法,方法中我们具体实现这个属性的内容(从网络上加载一个图片设置给imageView),xml中用到的在定义属性app:image就是我们 @BindingAdapter({"bind:image"}) 定义的这个,这是一个数组,所以呢可以定义很多个属性来用这个方法,然后即使在xml中使用 app:image="@{imageUrl}"这样就可以把这个网络地址的图片设置给我们的ImageView ,当然你需要在java代码中set这个值.

    public class BingdingUtils {  
        @BindingAdapter({"bind:image"})  
        public static void imageLoader(ImageView imageView, String url) {  
            ImageLoaderUtils.getInstance().displayImage(url, imageView);  
        }  
    } 
    

    还有一种情况比如我们要设置text属性,而拿到的值却不是一个string,或者说我们想在所有的text设置之前做一些操作,等等...这个属性是自带的我们没法去自定义一个BindingAdapter,这种情况要怎们做?
    谷歌还为我们提供了一个东西叫做Converter,就是字面的意思转换器

    就像下面的代码一样,我们创建一个转换器,用BindingConversion注解来标准,这个东西是统一性的,只要你用databingding设置的内容的类型是这个方法的参数类型,比如下面的这个方法就是String,呢设置的这个内容就会被转换,所以这个也要看好了再用,
    不过这个一般可以解决很多问题.

    
    public class MainConverter {
        @BindingConversion
        public static String convertDate(String string) {
    
            return string+"fule ";
        }
    }
    

    以上就是databing大大小小的一些用法,当然还需要在项目中去实操才能熟练使用.

    相关文章

      网友评论

      • o动感超人o:文章很多错误和错别字,比如
        <variable
        name="map"
        type="HashMap<String, String>" />
        这里的HashMap不能使用尖括号

      本文标题:安卓 DataBingding详解

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