概述 DataBinding是一个绑定数据的支持库,使用该库,我们可以使用声明性格式而非编程方式将布局中的UI组件绑定到应用程序中的数据源。可在gradle中配置启用DataBinding:
1 2 3 4 5 6 android { dataBinding { enabled = true } }
基础使用 通过在layout根节点下添加data节点,可以使用@{expression}
引用相关变量,其中expression表示表达式,但是尽量不要使用太复杂的表达式,会使得代码难以维护。布局文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <?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 class =".MainBinding" > <import alias ="MyUser" type ="com.hearing.mvvmdemo.bean.User" /> <variable name ="title" type ="String" /> <variable name ="user" type ="MyUser" /> </data > <LinearLayout android:id ="@+id/container" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context =".MainActivity" > <TextView android:id ="@+id/name_tv" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:padding ="10dp" android:text ="@{title, default=test}" android:textSize ="21sp" /> <TextView android:id ="@+id/age_tv" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:padding ="10dp" android:text ="@{String.valueOf(user.age)}" android:textSize ="21sp" /> </LinearLayout > </layout >
假设上诉的xml文件名为activity_main.xml
,Gradle会根据文件名生成一个ActivityMainBinding类,如上可以通过在data节点添加class属性更改默认的文件名。使用方式如下:
1 2 3 4 MainBinding binding = DataBindingUtil.setContentView(this , R.layout.activity_main); binding.setLifecycleOwner(this ); binding.setUser(user); binding.setTitle("title-title" );
绑定可观察数据 Field 对于一些数据类,如果我们不想继承BaseObservable或者只需要其中几个字段支持可观察,那么可以使用这种方式来创建可观察数据:
1 2 ObservableField<String> name = new ObservableField<>(); ObservableInt age = new ObservableInt();
Collection 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?xml version="1.0" encoding="utf-8"?> <layout > <data > <import type ="androidx.databinding.ObservableMap" /> <import type ="androidx.databinding.ObservableList" /> <variable name ="map" type ="ObservableMap< String, Object>" /> <variable name ="list" type ="ObservableList< Object>" /> </data > <TextView android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="@{String.valueOf(map.count)}" /> <TextView android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="@{String.valueOf(list[0])}" /> </layout >
List可以直接使用[index]
运算符获取对应位置的元素
Map可以使用.
运算符直接获取key对应的value,如果不想定义一个数据实体,可以直接使用Map来替代
Object 使用@Bindable
注解的属性会在BR类中生成一条记录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class User extends BaseObservable { private String name; private int age; public User (String name, int age) { this .name = name; this .age = age; } @Bindable public String getName () { return name; } public void setName (String name) { this .name = name; notifyPropertyChanged(BR.name); } @Bindable public int getAge () { return age; } public void setAge (int age) { this .age = age; notifyPropertyChanged(BR.age); } }
LiveData LiveData是首选的一种方式,用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?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 class =".MainBinding" > <variable name ="title" type ="androidx.lifecycle.LiveData< String>" /> </data > <LinearLayout android:id ="@+id/container" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context =".MainActivity" > <TextView android:id ="@+id/name_tv" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:padding ="10dp" android:text ="@{title}" android:textSize ="21sp" /> </LinearLayout > </layout >
使用时:
1 2 3 4 5 6 7 8 9 10 11 final MainBinding binding = DataBindingUtil.setContentView(this , R.layout.activity_main);final MutableLiveData<String> title = new MutableLiveData<>("title-title" );binding.setLifecycleOwner(this ); binding.setTitle(title); new Thread(new Runnable() { @Override public void run () { sleep(2000 ); title.postValue("title" ); } }).start();
双向绑定 上述的单向绑定是指数据变化后更新UI,而双向绑定是指其中任意一个变化后都会同步更新到另一个。双向绑定使用@={}
表达式来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?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 class =".MainBinding" > <variable name ="text" type ="androidx.databinding.ObservableField< String>" /> </data > <LinearLayout android:id ="@+id/container" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context =".MainActivity" > <EditText android:layout_width ="match_parent" android:layout_height ="wrap_content" android:text ="@={text}" /> </LinearLayout > </layout >
使用如下:
1 2 3 4 5 6 7 8 9 final MainBinding binding = DataBindingUtil.setContentView(this , R.layout.activity_main);ObservableField<String> text = new ObservableField<>("text" ); binding.setText(text); text.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() { @Override public void onPropertyChanged (Observable sender, int propertyId) { Log.d(TAG, "s = " + binding.getText().get()); } });
事件绑定 定义事件处理方法:
1 2 3 4 5 6 7 8 9 10 11 public class EventHandler { private static final String TAG = "jetPack" ; public void onClick (View view) { Log.d(TAG, "onClick" ); } public void onHandler () { Log.d(TAG, "onHandler" ); } }
布局文件中引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <?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 class =".MainBinding" > <variable name ="handler" type ="com.hearing.mvvmdemo.bean.EventHandler" /> </data > <LinearLayout android:id ="@+id/container" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context =".MainActivity" > <TextView android:id ="@+id/name_tv" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:onClick ="@{handler::onClick}" android:padding ="10dp" android:text ="@string/app_name" android:textSize ="21sp" /> <TextView android:id ="@+id/age_tv" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:onClick ="@{()->handler.onHandler()}" android:padding ="10dp" android:text ="@string/app_name" android:textSize ="21sp" /> </LinearLayout > </layout >
在Java中使用:
1 2 3 final MainBinding binding = DataBindingUtil.setContentView(this , R.layout.activity_main);binding.setLifecycleOwner(this ); binding.setHandler(new EventHandler());
BindingAdapter Android中已经支持了许多可绑定的参数,如android:text
,android:onClick
等,也可以使用BindingAdapter来自定义参数,定义一个静态方法:
1 2 3 4 @BindingAdapter("imageUrl") public static void setUrl (ImageView view, String url) { Glide.with(view).load(url).into(view); }
注意第一个参数需要为View,在布局文件中可以使用这个自定义参数:
1 2 3 4 5 <ImageView imageUrl ="@{url}" android:layout_width ="match_parent" android:layout_height ="200dp" android:layout_gravity ="center_horizontal" />
在代码中可以这样设置:
1 2 final MainBinding binding = DataBindingUtil.setContentView(this , R.layout.activity_main);binding.setUrl(url);
如果使用的是Kotlin,可以直接将其定义为扩展函数:
1 2 @BindingAdapter("imageUrl" ) fun ImageView.loadImage (url: String ) = Glide.with(this .context).load(url).into(this )
还可以配置多参数的BindingAdapter:
1 2 3 4 @BindingAdapter(value = {"age", "title"}, requireAll = true) public static void setText (TextView view, int age, String title) { view.setText(title + ": " + age); }
对应的布局文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?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 class =".MainBinding" > <variable name ="age" type ="int" /> <variable name ="title" type ="String" /> </data > <LinearLayout android:id ="@+id/container" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context =".MainActivity" > <TextView android:id ="@+id/name_tv" age ="@{age}" title ="@{title}" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:padding ="10dp" android:textSize ="21sp" /> </LinearLayout > </layout >
DataBinding工作流程 先跳过编译期间生成代码的逻辑,直接看生成的代码之间是怎么配合工作的。首先给出一个layout文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?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 class =".MainBinding" > <variable name ="title" type ="String" /> </data > <LinearLayout android:id ="@+id/container" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context =".MainActivity" > <TextView android:id ="@+id/name_tv" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:padding ="10dp" android:text ="@{title}" android:textSize ="21sp" /> </LinearLayout > </layout >
接下来贴出生成的部分文件:
activity_main-layout.xml 该文件包含了布局文件的一些Binding信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?xml version="1.0" encoding="utf-8" standalone="yes"?> <Layout bindingClass =".MainBinding" directory ="layout" filePath ="D:\Workspace\Android\MvvmDemo\app\src\main\res\layout\activity_main.xml" isBindingData ="true" isMerge ="false" layout ="activity_main" modulePackage ="com.hearing.mvvmdemo" rootNodeType ="android.widget.LinearLayout" > <ClassNameLocation endLine ="4" endOffset ="28" startLine ="4" startOffset ="17" /> <Variables name ="title" declared ="true" type ="String" > <location endLine ="8" endOffset ="27" startLine ="6" startOffset ="8" /> </Variables > <Targets > <Target id ="@+id/container" tag ="layout/activity_main_0" view ="LinearLayout" > <Expressions /> <location endLine ="26" endOffset ="18" startLine ="11" startOffset ="4" /> </Target > <Target id ="@+id/name_tv" tag ="binding_1" view ="TextView" > <Expressions > <Expression attribute ="android:text" text ="title" > <Location endLine ="23" endOffset ="34" startLine ="23" startOffset ="12" /> <TwoWay > false</TwoWay > <ValueLocation endLine ="23" endOffset ="32" startLine ="23" startOffset ="28" /> </Expression > </Expressions > <location endLine ="24" endOffset ="37" startLine ="18" startOffset ="8" /> </Target > </Targets > </Layout >
activity_main.xml 该文件是编译器移除DataBinding相关的标签后生成的标准的layout文件,主要就是多了一个tag标签,用来在后面的代码里检索。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:orientation ="1" android:id ="@ref/0x7f070049" android:tag ="layout/activity_main_0" android:layout_width ="-1" android:layout_height ="-1" > <TextView android:textSize ="dimension(5378)" android:id ="@ref/0x7f07006e" android:tag ="binding_1" android:padding ="dimension(2561)" android:layout_width ="-1" android:layout_height ="-2" /> </LinearLayout >
BR 该类记录layout中所有的变量。
1 2 3 4 5 public class BR { public static final int _all = 0 ; public static final int title = 1 ; }
MainBinding 该类是一个abstract类,继承自ViewDataBinding类,它的实现类也是一个自动生成的类:MainBindingImpl。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public abstract class ViewDataBinding extends BaseObservable implements ViewBinding { } public abstract class MainBinding extends ViewDataBinding { @NonNull public final LinearLayout container; @NonNull public final TextView nameTv; @Bindable protected String mTitle; protected MainBinding (Object _bindingComponent, View _root, int _localFieldCount, LinearLayout container, TextView nameTv) { super (_bindingComponent, _root, _localFieldCount); this .container = container; this .nameTv = nameTv; } public abstract void setTitle (@Nullable String title) ; @Nullable public String getTitle () { return mTitle; } @NonNull public static MainBinding inflate (@NonNull LayoutInflater inflater, @Nullable ViewGroup root, boolean attachToRoot) { return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent()); } @NonNull @Deprecated public static MainBinding inflate (@NonNull LayoutInflater inflater, @Nullable ViewGroup root, boolean attachToRoot, @Nullable Object component) { return ViewDataBinding.<MainBinding>inflateInternal(inflater, R.layout.activity_main, root, attachToRoot, component); } @NonNull public static MainBinding inflate (@NonNull LayoutInflater inflater) { return inflate(inflater, DataBindingUtil.getDefaultComponent()); } @NonNull @Deprecated public static MainBinding inflate (@NonNull LayoutInflater inflater, @Nullable Object component) { return ViewDataBinding.<MainBinding>inflateInternal(inflater, R.layout.activity_main, null , false , component); } public static MainBinding bind (@NonNull View view) { return bind(view, DataBindingUtil.getDefaultComponent()); } @Deprecated public static MainBinding bind (@NonNull View view, @Nullable Object component) { return (MainBinding) bind(component, view, R.layout.activity_main); } }
MainBindingImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 public class MainBindingImpl extends MainBinding { @Nullable private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes; @Nullable private static final android.util.SparseIntArray sViewsWithIds; static { sIncludes = null ; sViewsWithIds = null ; } public MainBindingImpl (@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) { this (bindingComponent, root, mapBindings(bindingComponent, root, 2 , sIncludes, sViewsWithIds)); } private MainBindingImpl (androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) { super (bindingComponent, root, 0 , (android.widget.LinearLayout) bindings[0 ] , (android.widget.TextView) bindings[1 ] ); this .container.setTag(null ); this .nameTv.setTag(null ); setRootTag(root); invalidateAll(); } @Override public void invalidateAll () { synchronized (this ) { mDirtyFlags = 0x2L ; } requestRebind(); } @Override public boolean hasPendingBindings () { synchronized (this ) { if (mDirtyFlags != 0 ) { return true ; } } return false ; } @Override public boolean setVariable (int variableId, @Nullable Object variable) { boolean variableSet = true ; if (BR.title == variableId) { setTitle((java.lang.String) variable); } else { variableSet = false ; } return variableSet; } public void setTitle (@Nullable java.lang.String Title) { this .mTitle = Title; synchronized (this ) { mDirtyFlags |= 0x1L ; } notifyPropertyChanged(BR.title); super .requestRebind(); } @Override protected boolean onFieldChange (int localFieldId, Object object, int fieldId) { switch (localFieldId) { } return false ; } @Override protected void executeBindings () { long dirtyFlags = 0 ; synchronized (this ) { dirtyFlags = mDirtyFlags; mDirtyFlags = 0 ; } java.lang.String title = mTitle; if ((dirtyFlags & 0x3L ) != 0 ) { } if ((dirtyFlags & 0x3L ) != 0 ) { androidx.databinding.adapters.TextViewBindingAdapter.setText(this .nameTv, title); } } private long mDirtyFlags = 0xffffffffffffffffL ; }
setContentView 接下来看看这个方法是怎么实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 final MainBinding binding = DataBindingUtil.setContentView(this , R.layout.activity_main);public static <T extends ViewDataBinding> T setContentView (@NonNull Activity activity, int layoutId, @Nullable DataBindingComponent bindingComponent) { activity.setContentView(layoutId); View decorView = activity.getWindow().getDecorView(); ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content); return bindToAddedViews(bindingComponent, contentView, 0 , layoutId); } private static <T extends ViewDataBinding> T bindToAddedViews (DataBindingComponent component, ViewGroup parent, int startChildren, int layoutId) { final int endChildren = parent.getChildCount(); final int childrenAdded = endChildren - startChildren; if (childrenAdded == 1 ) { final View childView = parent.getChildAt(endChildren - 1 ); return bind(component, childView, layoutId); } else { final View[] children = new View[childrenAdded]; for (int i = 0 ; i < childrenAdded; i++) { children[i] = parent.getChildAt(i + startChildren); } return bind(component, children, layoutId); } } static <T extends ViewDataBinding> T bind (DataBindingComponent bindingComponent, View[] roots, int layoutId) { return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId); } static <T extends ViewDataBinding> T bind (DataBindingComponent bindingComponent, View root, int layoutId) { return (T) sMapper.getDataBinder(bindingComponent, root, layoutId); } private static DataBinderMapper sMapper = new DataBinderMapperImpl();
这里的sMapper是DataBinderMapperImpl实例,其实现如下:
1 2 3 4 5 public class DataBinderMapperImpl extends MergedDataBinderMapper { DataBinderMapperImpl() { addMapper(new com.hearing.mvvmdemo.DataBinderMapperImpl()); } }
可以看出sMapper.getDataBinder方法的实现在MergedDataBinderMapper类中(只看参数为View的,参数为View[]的类似):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override public ViewDataBinding getDataBinder (DataBindingComponent bindingComponent, View view, int layoutId) { for (DataBinderMapper mapper : mMappers) { ViewDataBinding result = mapper.getDataBinder(bindingComponent, view, layoutId); if (result != null ) { return result; } } if (loadFeatures()) { return getDataBinder(bindingComponent, view, layoutId); } return null ; }
这里的mMappers中的元素即是上面DataBinderMapperImpl中addMapper添加的com.hearing.mvvmdemo.DataBinderMapperImpl类(这俩类名字一样但是不同包)。因此看看com.hearing.mvvmdemo.DataBinderMapperImpl中的getDataBinder方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Override public ViewDataBinding getDataBinder (DataBindingComponent component, View view, int layoutId) { int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId); if (localizedLayoutId > 0 ) { final Object tag = view.getTag(); if (tag == null ) { throw new RuntimeException("view must have a tag" ); } switch (localizedLayoutId) { case LAYOUT_ACTIVITYMAIN: { if ("layout/activity_main_0" .equals(tag)) { return new MainBindingImpl(component, view); } throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag); } } } return null ; }
回到最开始的final MainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
,这里返回的binding即是MainBindingImpl类的实例。
setTitle 接下来看看这段代码的实现:
1 2 3 4 5 6 7 8 9 10 11 binding.setTitle("title" ); public void setTitle (@Nullable java.lang.String Title) { this .mTitle = Title; synchronized (this ) { mDirtyFlags |= 0x1L ; } notifyPropertyChanged(BR.title); super .requestRebind(); }
setTitle方法首先给mTitle赋值,然后调用notifyPropertyChanged
方法,requestRebind方法最后会调用到MainBindingImpl#executeBindings方法给目标TextView设置text:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Override protected void executeBindings () { long dirtyFlags = 0 ; synchronized (this ) { dirtyFlags = mDirtyFlags; mDirtyFlags = 0 ; } java.lang.String title = mTitle; if ((dirtyFlags & 0x3L ) != 0 ) { } if ((dirtyFlags & 0x3L ) != 0 ) { androidx.databinding.adapters.TextViewBindingAdapter.setText(this .nameTv, title); } } @BindingAdapter("android:text") public static void setText (TextView view, CharSequence text) { final CharSequence oldText = view.getText(); if (text == oldText || (text == null && oldText.length() == 0 )) { return ; } if (text instanceof Spanned) { if (text.equals(oldText)) { return ; } } else if (!haveContentsChanged(text, oldText)) { return ; } view.setText(text); }
requestRebind方法最后会重新给TextView赋值。当 Binding 中使用的数据是 LiveData 时,在调用 binding.setXXX 方法内部,会给 LiveData 添加观察者监听,所以才可对 LiveData 数据变化做出反应。其它可观测的数据类型也类似。
DataBinding生成代码 关于这部分的源码可以在data-binding 下载。入口可以从这个类开始:android.databinding.annotationprocessor.ProcessDataBinding
,这是一个注解处理器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 @SupportedAnnotationTypes({ "android.databinding.BindingAdapter", "android.databinding.Untaggable", "android.databinding.BindingMethods", "android.databinding.BindingConversion", "android.databinding.BindingBuildInfo"} ) public class ProcessDataBinding extends AbstractProcessor { private List<ProcessingStep> mProcessingSteps; @Override public boolean process (Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (mProcessingSteps == null ) { initProcessingSteps(); } final BindingBuildInfo buildInfo = BuildInfoUtil.load(roundEnv); if (buildInfo == null ) { return false ; } boolean done = true ; for (ProcessingStep step : mProcessingSteps) { try { done = step.runStep(roundEnv, processingEnv, buildInfo) && done; } catch (JAXBException e) { L.e(e, "Exception while handling step %s" , step); } } if (roundEnv.processingOver()) { for (ProcessingStep step : mProcessingSteps) { step.onProcessingOver(roundEnv, processingEnv, buildInfo); } } Scope.assertNoError(); return done; } @Override public SourceVersion getSupportedSourceVersion () { return SourceVersion.latest(); } private void initProcessingSteps () { final ProcessBindable processBindable = new ProcessBindable(); mProcessingSteps = Arrays.asList( new ProcessMethodAdapters(), new ProcessExpressions(), processBindable ); } }
上面主要处理三个ProcessingStep的子类:
ProcessMethodAdapters:检查哪些类或者方法有这些注解:@BindingAdapter, @Untaggable, @BindingMethods, @BindingConversion, @InverseBindingAdapter, @InverseBindingMethods,然后把它们保存在SetterStore中。在编译期间,注解处理器拿到的这些信息会被存放在pkgxxx-setter_store.bin
文件中。
ProcessExpressions:这个类用来处理表达式,在这一步中会搜索工程中所有xml文件并且会转换最外层为<layout></layout>
标签支持data-binding的xml文件,它把这个文件拆分为2个文件:activity_main.xml(正常的布局文件)和activity_main-layout.xml(包含绑定信息)。
ProcessBindable:生成BR类,绑定属性的id。
在上面的步骤中,DataBinderWriter和LayoutBinderWriter等类会生成我们需要的MainBinding等类。这部分代码比较多,所以只是大概过了一下。
ViewBinding 概述 在Android Studio 3.6 Canary 11及更高版本中可以使用ViewBinding,这是Google提供的一个避免使用过多findViewById的库。之前可以使用Jake Wharton大神的Butter Knife开源库,最近Butter Knife的readme上有一句话:Attention: This tool is now deprecated. Please switch to view binding ,即推荐使用ViewBinding。
配置 ViewBinding可以按模块启用,在要启用的module gradle中配置:
1 2 3 4 5 6 android { viewBinding { enabled = true } }
如果希望在生成绑定类时忽略某个布局文件,可以添加:
1 2 3 <LinearLayout tools:viewBindingIgnore ="true" > </LinearLayout >
假设layout文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:tools ="http://schemas.android.com/tools" android:id ="@+id/container" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context =".MainActivity" > <TextView android:id ="@+id/name_tv" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:padding ="10dp" android:text ="@{title}" android:textSize ="21sp" /> </LinearLayout >
ActivityMainBinding ActivityMainBinding是上述layout生成的类,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public final class ActivityMainBinding implements ViewBinding { @NonNull private final LinearLayout rootView; @NonNull public final LinearLayout container; @NonNull public final TextView nameTv; private ActivityMainBinding (@NonNull LinearLayout rootView, @NonNull LinearLayout container, @NonNull TextView nameTv) { this .rootView = rootView; this .container = container; this .nameTv = nameTv; } @Override @NonNull public LinearLayout getRoot () { return rootView; } @NonNull public static ActivityMainBinding inflate (@NonNull LayoutInflater inflater) { return inflate(inflater, null , false ); } @NonNull public static ActivityMainBinding inflate (@NonNull LayoutInflater inflater, @Nullable ViewGroup parent, boolean attachToParent) { View root = inflater.inflate(R.layout.activity_main, parent, false ); if (attachToParent) { parent.addView(root); } return bind(root); } @NonNull public static ActivityMainBinding bind (@NonNull View rootView) { String missingId; missingId: { LinearLayout container = rootView.findViewById(R.id.container); if (container == null ) { missingId = "container" ; break missingId; } TextView nameTv = rootView.findViewById(R.id.name_tv); if (nameTv == null ) { missingId = "nameTv" ; break missingId; } return new ActivityMainBinding((LinearLayout) rootView, container, nameTv); } throw new NullPointerException("Missing required view with ID: " .concat(missingId)); } }
其中ViewBinding是一个接口:
1 2 3 4 5 6 7 8 package androidx.viewbinding;public interface ViewBinding { @NonNull View getRoot () ; }
使用 在Activity中:
1 2 3 4 5 6 7 8 9 10 public class MainActivity extends AppCompatActivity { @Override protected void onCreate (@Nullable Bundle savedInstanceState) { super .onCreate(savedInstanceState); ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); binding.nameTv.setText("hearing" ); } }
在Fragment中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class MainFragment extends Fragment { private ActivityMainBinding mBinding; @Nullable @Override public View onCreateView (@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { mBinding = ActivityMainBinding.inflate(inflater, container, false ); mBinding.nameTv.setText("hearing" ); return mBinding.getRoot(); } @Override public void onDestroyView () { super .onDestroyView(); mBinding = null ; } }
总结 与findViewById的区别:
Null安全:由于ViewBinding会创建对View的直接引用,因此不存在因View ID无效而引发Null指针异常的风险。此外,如果视图仅出现在布局的某些配置中,则绑定类中包含其引用的字段会使用@Nullable标记。
类型安全:每个绑定类中的字段均具有与它们在XML文件中引用的View相匹配的类型,这意味着不存在发生类转换异常的风险。
这些差异意味着布局和代码之间的不兼容将会导致构建在编译时(而非运行时)失败。
与DataBinding的对比,二者均会生成可用于直接引用视图的绑定类。但是,ViewBinding旨在处理更简单的用例,与DataBinding相比,具有以下优势:
更快的编译速度:不需要处理注解,因此编译时间更短。
易于使用:不需要特别标记的XML布局文件,因此在应用中使用速度更快。在模块中启用ViewBinding后,它会自动应用于该模块的所有布局。
反过来,与DataBinding相比,ViewBinding也具有以下限制:
ViewBinding不支持布局变量或布局表达式,因此不能用于直接在XML布局文件中声明动态界面内容。
ViewBinding不支持双向数据绑定。
考虑到这些因素,在某些情况下,最好在项目中同时使用ViewBinding和DataBinding。可以在需要高级功能的布局中使用DataBinding,而在不需要高级功能的布局中使用ViewBinding。
总结 DataBinding经常结合ViewModel&LivaData一起使用,DataBinding的作用:
取代烦琐的findviewById,自动在binding中生成对应的view;
绑定VM层和V层的监听关系,使得VM和V层的交互更简单。一是VM通知V层作改变,比如setText等;二是V层通知VM作改变,比如click事件,edit的输入等。
DataBinding工作流程(双向绑定和观察者模式):
DataBindingUtil.setContentView方法将xml中的各个View赋值给ViewDataBinding,完成findviewById的任务;
ViewDataBinding的setVariable等方法建立了ViewDataBinding与VM之间的联系,也就搭建了一个可以互相通信的桥梁;
当VM层调用notifyPropertyChanged方法时,最终在ViewDataBinding实现类的executeBindings方法中处理逻辑。
ViewBinding和DataBinding代码生成:
DataBinding:通过注解处理器和Android Gradle插件,在编译期间生成需要的代码。
ViewBinding:通过Android Gradle插件,在编译期扫描开启了ViewBinding的模块下的layout文件,生成对应的binding类。