If you want to implement MVVM architecture in Android, DataBinding is your best choice MVVM is also the mainstream development direction in all front-end / iOS/Android fields
- Less code
- More robust fault tolerance
- Faster iteration speed
- Higher readability
This article and 2019 are based on Kotlin re editing
preface
- Do not attempt to use LiveData instead of databinding. Databinding itself is compatible with LiveData properties
- No matter the project size, MVVM is better than M*3 This is the mainstream and the future
Enabling DataBinding will automatically generate classes in the build directory Because it is integrated into Android studio, you don't need to compile manually. It will compile in real time, and supports most code completion
apply plugin: "kotlin-kapt" // Kotlin must be added when using Databinding android{ /.../ dataBinding { enabled = true; } }
start
- Databinding is not a substitute for findById such as ButterKnife, but just a small auxiliary function. I recommend using Kotlin to solve this requirement;
- In most cases, the error prompt of Databinding is perfect, and individual XML writing errors are easy to check
- I want to emphasize that @{} in Xml only does assignment or simple ternary operation or space judgment, and do not do complex operation, otherwise it violates the decoupling principle
- Business logic should be in the Model as much as possible
- ViewModel belongs to the class automatically generated by DataBinding
Disadvantages of MVP over MVVM
- MVP is implemented through interface callback, resulting in poor code readability and incoherent reading order
- MVP cannot implement bidirectional data binding
- The implementation of MVP varies from person to person, and the difference leads to poor reading
- MVP has more code than MVC. It is decoupled by increasing the code, which is geometrically doubled than MVVM
- Any front-end platform starts to tend to MVVM, and MVVM in the web field is the most mature application
I open source a RecyclerView Library Based on Kotlin and Databinding features: BRV, which has unparalleled simplicity and MVVM features;
layout
Layout file
<layout> <data> <variable name="user" type="com.liangjingkanji.databinding.pojo.UserBean"/> </data> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.liangjingkanji.databinding.MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.userName}" /> </RelativeLayout> </layout>
layout
The layout root node must be < layout > At the same time, layout can only contain one View tag Cannot contain < merge > directly
data
The content of < data > tag is the data of DataBinding Only one data tag can exist
variable
The < variable > tag allows you to specify the class, which can then be used in the property value of the control
<data> <variable name="user" type="com.liangfeizc.databindingsamples.basic.User" /> </data>
You can set data for a Variable through the setxx() method of DataBinding name value cannot contain_ Underline
import
The second writing method (import) imports the class (String/Integer) under the java/lang package by default You can directly use the static methods of the imported class
<data> <!--Import class--> <import type="com.liangfeizc.databindingsamples.basic.User" /> <!--because User Already imported, So you can abbreviate the class name--> <variable name="user" type="User" /> </data>
Use class
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.userName}" /> <!--user Is in Variable In label name, You can customize it at will, Then it will be used type Classes in-->
Tip: user represents the UserBean class. You can use the methods and member variables in the UserBean If it is getxx(), it will be automatically recognized as xx Note that the string android cannot be used, otherwise an error will be reported and cannot be bound
class
The < data > tag has an attribute < class > to customize the class name and path generated by DataBinding
<!--Custom class name--> <data class="CustomDataBinding"></data> <!--Custom build path and type--> <data class=".CustomDataBinding"></data> <!--Automatically generate packages and classes under package names-->
Tip: note that there is no automatic code completion The user-defined path is in the Module/build/generated/source/apt/debug/databinding / directory. Basically, the user-defined path is not required
Default:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // The ActivityMainBinding class is generated based on the layout file name (id+Binding) ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); UserBean userBean = new UserBean(); userBean.setUserName("drake"); // The setUser method is automatically generated according to the name attribute of the Variable tag viewDataBinding.setUser(userBean); } }
alias
< variable > tag if you need to import two classes with the same name, you can use the alias attribute (alias attribute)
<import type="com.example.home.data.User" /> <import type="com.examle.detail.data.User" alias="DetailUser" /> <variable name="user" type="DetailUser" />
include
You may need to pass the value of a variable when including other layouts
<variable name="userName" type="String"/> .... <include layout="@layout/include_demo" bind:userName="@{userName}"/>
include\_demo
<data> <variable name="userName" type="String"/> </data> ... android:text="@{userName}"
The two layouts are passed through the bind: < variable name > value of include And both must have the same variable
DataBinding does not support passing variables with merge tags
Auto layout properties
DataBinding supports user-defined properties very well. As long as the setter method is included in the View, you can directly use this property in the layout (this is because many user-defined properties have been written for you in the DataBinding Library)
public void setCustomName(@NonNull final String customName) { mLastName.setText("Wu Yanzu"); }
Then use it directly (but the IDE has no code completion)
app:customName="@{@string/wuyanzu}"
However, the setter method only supports a single parameter app: this namespace is optional
Data bidirectional binding
Data refresh view
BaseObservable
If you want the data to change and the view to change, you need to use the following two methods
There are two ways:
Inherit BaseObservable
public class ObservableUser extends BaseObservable { private String firstName; private String lastName; @Bindable public String getFirstName() { return firstName; } // The annotation will automatically generate an entry in the BR class of the build directory. It is required that the method name must start with get @Bindable public String getLastName() { return lastName; } public void setFirstName(String firstName) { this.firstName = firstName; notifyPropertyChanged(BR.firstName); } public void setLastName(String lastName) { this.lastName = lastName; notifyPropertyChanged(BR.lastName); // Manual refresh required } }
- Simplified usage only requires data model to inherit BaseObservable, and then notify() function can refresh the view after each change of data. No comments are required observableUser.name observableUser.notifyChange()
- If you can't inherit, you can also implement the interface View the interface implemented by BaseObservable. You can implement it yourself or copy the code example
You can also listen for property change events
ObservableUser.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() { @Override public void onPropertyChanged(Observable observable, int i) { } });
The first time the property is changed, it will be called back twice, and then only once If you use notifyChange(), you will not get an ID (i.e. I equals 0) use
notifyPropertyChanged(i) can get the id in the callback
Difference between BaseObservable and Observable
- BaseObservable is a class that implements Observable and helps us realize the thread safety of listener
- BaseObservable uses PropertyChangeRegistry to execute OnPropertyChangedCallback
- So I don't recommend that you directly implement Observable
ObservableField
This is the second method. databinding implements a series of field types that implement the Observable interface by default
BaseObservable, ObservableBoolean, ObservableByte, ObservableChar, ObservableDouble, ObservableField<T>, ObservableFloat, ObservableInt, ObservableLong, ObservableParcelable<T extends Parcelable>, ObservableShort, ViewDataBinding
Example
public class PlainUser { public final ObservableField<String> firstName = new ObservableField<>(); public final ObservableField<String> lastName = new ObservableField<>(); public final ObservableInt age = new ObservableInt(); }
For collection data types, such as observablearraymap / observablearraylis / observaservablemap
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>(); user.put("firstName", "Google"); user.put("lastName", "Inc."); user.put("age", 17);
use
<data> <import type="android.databinding.ObservableMap"/> <variable name="user" type="ObservableMap<String, Object>"/> </data> ... <TextView android:text='@{user["lastName"]}' android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:text='@{String.valueOf(1 + (Integer)user["age"])}' android:layout_width="wrap_content" android:layout_height="wrap_content"/>
Tip:
- It also supports observableparcelable < Object > serialized data types
- The above two methods only update the view with the data, and the data will not refresh with the view
- ObservableField also supports addOnPropertyChangedCallback to listen for property changes
If the data is LiveData, it is also supported, and ViewDataBinding can set the life cycle
View refresh data
Using the @ = expression through the expression can automatically update the data when the view is refreshed, but the refresh can be triggered only when the data is modified in the following two ways
<EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textNoSuggestions" android:text="@={model.name}"/>
A big problem with this two - way binding is that it will loop The data change (callback listener) triggers the view change, and then the view will trigger the data change (callback listener again), and then keep cycling. Setting the same data is also regarded as data change
Therefore, we need to judge whether the current changed data is equivalent to the old data
public class CustomBindingAdapter { @BindingAdapter("android:text") public static void setText(TextView view, CharSequence text) { CharSequence oldText = view.getText(); if (!haveContentsChanged(text, oldText)) { return; // If the data does not change, the view will not be refreshed } view.setText(text); } // This tool class is intercepted from the official source code private static boolean haveContentsChanged(CharSequence str1, CharSequence str2) { if ((str1 == null) != (str2 == null)) { return true; } else if (str1 == null) { return false; } final int length = str1.length(); if (length != str2.length()) { return true; } for (int i = 0; i < length; i++) { if (str1.charAt(i) != str2.charAt(i)) { return true; } } return false; } }
Tip:
- According to what I said above, the listener calls back at least twice (data - > view, view - > data)
- The following is invalid because the variable passed by the String parameter belongs to the reference type and is not a constant. You need to use equals()
// This paragraph intercepts the official source code. I don't know why it sb says if (text = = oldtext | (text = = null & & oldtext. Length() = = 0)) {return;}/**/ correct if (text == null || text.equals(oldText) || oldText.length() == 0) { return; }
The summary is that if there is no default control property and two-way data binding is used, you need to implement the BindingAdapter annotation yourself
annotation
DataBinding controls the class generation of ViewModel through annotations
@Bindable
Used to refresh the view automatically when updating data The data binding mentioned later
@BindingAdapter
Create an XML attribute and function, and then set it in the attribute. The data operation will enter the function
The picture loading framework can facilitate the use of this method
@BindingAdapter(value = { "imageUrl", "error" }, requireAll = false) public static void loadImage(ImageView view, String url, Drawable error) { Glide.with(view.getContext()).load(url).into(view); }
- Modify the method. The method must be public static
- The first parameter must be a control or its parent class
- Method name optional
- Finally, the boolean type is an optional parameter You can ask whether all parameters need to be filled in The default is true
- If requireAll is false, the property value you did not fill in will be null Therefore, we need to make non empty judgment
use:
<ImageView android:layout_width="match_parent" android:layout_height="200dp" app:error="@{@drawable/error}" wuyanzu:imageUrl="@{imageUrl}" app:onClickListener="@{activity.avatarClickListener}" />
You can see that the namespace can be arbitrary, but if you define a namespace in the BindingAdapter array, you must fully comply with it
For example:
// An annotation parameter is omitted here @BindingAdapter({ "android:imageUrl", "error" }) public static void loadImage(ImageView view, String url, Drawable error) { if(url == null) return; Glide.with(view.getContext()).load(url).into(view); }
Tip: if your data initialization is asynchronous The method will be called back, but the data is null (member default) Therefore, we must first carry out air judgment
There are two ways to implement Kotlin
Singleton class + @ JvmStatic annotation
object ProgressAdapter { @JvmStatic @BindingAdapter("android:bindName") fun setBindName(view: View, name:String){ } }
Top level function
@BindingAdapter("android:bindName") fun setBindName(view: View, name:String){ } // Because too many top-level functions affect code completion, it is recommended to use top-level extension functions, which can also be easily used in the code later @BindingAdapter("android:bindName") fun View.setBindName( name:String){ }
@BindingMethods
If you want to create an XML attribute and associate it with a function in View (that is, the function will be called automatically with the attribute value as a parameter) You should annotate a class with @ BindingMethods (this class is unlimited and can even be an interface)
If @ BindingAdapter is to create a new function function for the control, then BindingMethod is to guide DataBinding to use the function of the control itself
The annotation belongs to a container The internal parameter is an array of @ BindingMethod, which can only be used to decorate classes;
Any class or interface does not need to override any function
Official example:
@BindingMethods({ @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:indeterminateTint", method = "setIndeterminateTintList"), @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:progressTint", method = "setProgressTintList"), @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:secondaryProgressTint", method = "setSecondaryProgressTintList"), }) public class ProgressBarBindingAdapter { }
@BindingMethod
Annotation parameter (required)
- type: bytecode is your control class
- attribute: XML attribute
- method: function name is the name of the function in the control
be careful
- If the attribute name is the same as the XML attribute defined by @ BindingAdapter, an error will be reported
- If a function associated with the property you defined already exists in the control class (for example, setName function and android:name property are associated), the function will be executed first
@BindingConversion
Property values are automatically type converted
- Only public static methods can be modified
- There is no limit to any method name anywhere
- DataBinding automatically matches the method and parameter types modified by the annotation
- The return value type must match the property setter method, and there can only be one parameter
- Property value is required to be a @{} DataBinding expression
Official example:
public class Converters { @BindingConversion public static ColorDrawable convertColorToDrawable(int color) { return new ColorDrawable(color); } @BindingConversion public static ColorStateList convertColorToColorStateList(int color) { return ColorStateList.valueOf(color); } }
Kotlin example I wrote
@BindingConversion fun int2string(integer:Int):String{ Log.d("journal", "(CusView.kt:92) int2string ___ integer = [$integer]") return integer.toString() }
XML
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="m" type="com.example.architecture.Model" /> </data> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <com.example.architecture.CusView android:bindName="@={m.age}" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </FrameLayout> </layout>
My code will actually report an error because it involves two-way data binding, and @ BindingConversion will only take effect when the data view is set However, if it is view setting data, other functions (get) will be used. If the type returned by this function does not match the type in the Model, an exception will be reported unless you change that function to type matching
Or remove the = symbol and do not use two-way data binding
android:text cannot convert int to string because it can normally receive int (as resourceID) Then I'll report
android.content.res.Resources$NotFoundException: String resource ID #0xa
@InverseMethod
This annotation belongs to the new annotation of the inverse series provided after Android studio 3. All of them are aimed at two-way data binding
This annotation @ InverseMethod can be used to solve the problem of data conversion when the data and view data are not unified
For example, the data model stores the user's id, but the view displays the user name instead of the id (the data and view types are inconsistent), so we need to convert between them
We need two functions: the function that sets the data to the view is called set / the function that sets the view to the data is called get
- Both set and get must have at least one parameter
- Its own parameters must correspond to the return value of another function (otherwise it is called conversion)
Simple example:
Convert between user id and user name The id is stored, but the user name is displayed when it is displayed
class Model { var name = "designer" @InverseMethod("ui2data") fun data2ui():String{ return "Designer Jin Chengwu" } fun ui2data():String{ return "Designer Wu Yanzu" } }
use
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="m" type="com.example.architecture.Model" /> </data> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <com.example.architecture.CusView android:text="@{m.data2ui(m.name)}" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </FrameLayout> </layout>
@InverseBindingAdapter
Parameters:
- String attribute attribute value (required)
- String event is not required. The default value is equal to < attribute > attrchanged
He cooperates with @ BindingAdapter to implement bidirectional data binding
Full bidirectional data binding requires three functions
- set (data to view)
- get (view to data)
- Notify (notify that the data binding view has been refreshed and the data model can be updated)
The set function has been written before
@BindingAdapter("android:bindName") fun TextView.setBindName(name:String?){ if (name.isNullOrEmpty() && name != text) { text = name } }
get function
@InverseBindingAdapter(attribute = "android:bindName", event = "cus_event") fun TextView.getBindName():String{ // Here you can process the data on the view and finally set it to the Model layer return text.toString() }
- No more parameters are allowed
- The return value type must be a bound data type
After the notify function view changes, you need to notify Databinding to start setting the Model layer. You also need to use @ BindingAdapter. The difference is that the parameter can only be InverseBindingListener
@BindingAdapter("cus_event") fun TextView.notifyBindName( inverseBindingListener: InverseBindingListener){ // This function monitors the official source code of TextWatch. Of course, different listeners have different needs doAfterTextChanged { inverseBindingListener.onChange() // This line of code is executed to notify the data refresh } }
InverseBindingListener is an interface with only one function. It is a necessary parameter of the notify function
public interface InverseBindingListener { /** * Notifies the data binding system that the attribute value has changed. */ void onChange(); }
@InverseBindingMethods
Similar to @ BindingMethods
However, @ InverseBindingMethods is the view change data (get function), while BindingMethods is the data to view (set function)
parameter
public @interface InverseBindingMethod { /** * Class bytecode of the control */ Class type(); /** * Custom properties */ String attribute(); /** * nitify The name of the function is the function used to notify data updates */ String event() default ""; /** * The function name of the control itself. If omitted, it will be automatically generated as {attribute} attribhange */ String method() default ""; }
If BindingMethods are associated setter methods and custom attributes, invertebindingmethods are associated getter methods and custom attributes;
The setter is used when updating the view, and the getter method is used when updating the data
There is one more function than @ BindingMethods, that is, the notify function, which is used to notify updates
@BindingAdapter("cus_event") fun TextView.notifyBindName( inverseBindingListener: InverseBindingListener){ doAfterTextChanged { inverseBindingListener.onChange() } }
Example:
@InverseBindingMethods( InverseBindingMethod( type = CusView::class, attribute = "android:bindName", method = "getName", event = "cus_event" ) ) object Adapter { }
- If the attribute value belongs to an attribute that does not exist, you need to create another BindingAdapter custom attribute to handle it
View the implementation source code of view update data in the generated class
private android.databinding.InverseBindingListener ivandroidTextAttr = new android.databinding.InverseBindingListener() { @Override public void onChange() { // Inverse of data.name // is data.setName((java.lang.String) callbackArg_0) java.lang.String callbackArg_0 = com.liangjingkanji.databinding.MyInverseBindingAdapter.getTextString(iv); // Get the attributes of change // localize variables for thread safety // data != null boolean dataJavaLangObjectNull = false; // data.name java.lang.String dataName = null; // data com.liangjingkanji.databinding.Bean data = mData; // Get the data dataJavaLangObjectNull = (data) != (null); if (dataJavaLangObjectNull) { data.setName(((java.lang.String) (callbackArg_0))); // Store to data } } };
Therefore, if you do not override the data change method of Inverse, you will not be able to let the view notify the data refresh
// This party * * * calls back when binding the layout @Override protected void executeBindings() { long dirtyFlags = 0; synchronized(this) { dirtyFlags = mDirtyFlags; mDirtyFlags = 0; } java.lang.String dataName = null; com.liangjingkanji.databinding.Bean data = mData; if ((dirtyFlags & 0x1aL) != 0) { if (data != null) { // read data.name dataName = data.getName(); } } // batch finished if ((dirtyFlags & 0x1aL) != 0) { // api target 1 com.liangjingkanji.databinding.MyInverseBindingAdapter.setText(this.iv, dataName); } if ((dirtyFlags & 0x10L) != 0) { // api target 1 // The focus is on this code. Pass the listener created above into the setTextWatcher method com.liangjingkanji.databinding.MyInverseBindingAdapter.setTextWatcher(this.iv, (com.liangjingkanji.databinding.MyInverseBindingAdapter.BeforeTextChanged)null, (com.liangjingkanji.databinding.MyInverseBindingAdapter.OnTextChanged)null, (com.liangjingkanji.databinding.MyInverseBindingAdapter.AfterTextChanged)null, ivandroidTextAttr); } }
summary
@The BindingBuildInfo and @ Untaggable annotations are used when DataBinding automatically generates Java classes
- Bindable Set data refresh view Automatically generated BR ID
- BindingAdapter Set custom properties You can overwrite the original system properties
- BindingMethod/BindingMethods Associate the custom property to the original setter method of the control
- BindingConversion If the attribute cannot match, the type parameter will be automatically converted according to the method that the type parameter matches to the annotation
- InverseMethod Responsible for the transformation between view and data
- InverseBindingAdapter View notifies of data refresh
- InverseBindingMethod/InverseBindingMethods The view notifies data refresh (if there are existing getter methods available)
- The BindingMethods family has a higher priority than the BindingAdapter family
- All annotation functions take effect only when the XML attribute value is a Databinding expression (i.e. @ {})
expression
This refers to the expressions used in XML files (used to assign variables), @{} which can write expressions in addition to executing methods, and support some unique expressions
- Arithmetic + - / *%
- String merge+
- Logical & &||
- Binary & |^
- One yuan + -~
- Shift > > > > ><<
- Compare = = > < > =<=
- Instanceof
- Grouping ()
- Text - character, String, numeric, null
- Cast
- Method call
- Field access
- Array access []
- Ternary?:
Avoid null pointers
The null pointer exception will not occur even if the value of variable is set to null or not set
This is because the official has rewritten many properties with the @ BindingAdapter annotation of DataBinding In addition, it was judged as empty
<variable name="userName" type="String"/> ..... android:text="@{userName}"
Null pointer exceptions do not occur
dataBinding.setUserName(null);
It also supports unique non empty multivariate expressions
android:text="@{user.displayName ?? user.lastName}"
Is equivalent to
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
You still need to pay attention to the fact that the array is out of bounds
aggregate
Collection does not belong to Java Lang *, the full path needs to be imported
<variable name="list" type="java.util.List<String>"/> <variable name="map" type="java.util.Map<String, String>"/>
The above writing * reports an error
Error:And element type "variable" Associated "type" Property value cannot contain '<' Character.
Because the < symbol needs to be escaped
Common escape characters
Space; ; < less than sign <; <; \>Greater than sign >; >; &And &; &; "Quotation marks"; "; 'apostrophe & apos;'; × multiplication sign ×; ×; ÷ division number ÷; ÷;
Correct writing
<variable name="list" type="java.util.List<String>"/> <variable name="map" type="java.util.Map<String, String>"/>
Both sets and arrays can use [] to get elements
android:text="@{map["firstName"]}"
character string
If you want to use strings in @{}, you can use three ways
First:
android:text='@{"Wu Yanzu"}'
Second:
android:text="@{`Wu Yanzu`}"
Third:
android:text="@{@string/user_name}"
It also supports @ color or @ drawable
format string
First define < string > in strings
<string name="string_format">name: %s Gender: %s</string>
You can then use the DataBinding expression
android:text="@{@string/string_format(`Wu Yanzu`, `male`)}"
Output content:
name: Wu Yanzu gender: male
Default value
If the Variable has not been copied, it will be displayed with the default value
android:text="@{user.integral, default=`30`}"
context
DataBinding itself provides a Variable named context It can be used directly Equivalent to getContext() of View
android:text="@{context.getApplicationInfo().toString()}"
Reference other controls
<TextView android:id="@+id/datingName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="8dp" android:layout_toRightOf="@id/iv_dating" android:text="activity" /> /... <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="8dp" android:layout_toRightOf="@id/iv_order" android:text="@{datingName.text}" />
Reference contains_ The control id of can be ignored directly For example, tv_name writes tvName directly
Thank you lambda for pointing out the error
It can be referenced regardless of order
Use Class
If you want to pass Class as a parameter, the Class cannot be used directly through static import It needs to be used as a field constant
Function callback
DataBinding also supports binding function parameter types in XML, and is also Lambda and higher-order function types, which is more advanced than Java
object
That is, objects are directly used in XML in the same way as attributes This requires you to manually create an object A little trouble
Higher order function
Create custom attributes
object EventDataBindingComponent { /** * The Model can be used to handle the UI when Binding views. It is not recommended to use the rules that break the view and logical decoupling * This can result in inconvenient unit testing of business logic * * @see OnBindViewListener This interface supports generic definitions and concrete views * * @receiver View * @param block OnBindViewListener<View> */ @JvmStatic @BindingAdapter("view") fun View.setView(listener: OnBindViewListener) { listener.onBind(this) } }
Interface used above
interface OnBindViewListener { fun onBind(v: View) }
Higher order function
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="v" type="com.liangjingkanji.databinding.MainActivity"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Designer Wu Yanzu" android:onClick="@{v::click}"/> </LinearLayout> </layout>
Using higher-order functions in XML requires matching the following rules
- The function parameter of BindingAdapter requires an interface, and Kotlin's function type parameter is not supported
- Interface allows only one function
- The method signature (return value | parameter) of the interface matches the passed higher-order function
Lambda
Higher order functions do not allow custom transfer parameters (otherwise the interface needs to be modified) Therefore, Lambda can be used for control
Create a multi parameter function
fun onBinding(v:View, name:String){ Log.d("journal", "(MainActivity.kt:45) this = [$v] name = [$name]") }
XML usage
view="@{(view) -> v.onBinding(view, `Wu Yanzu`)}"
If no parameters are used
view="@{() -> v.onBinding(`Wu Yanzu`)}
ViewDataBinding
Automatically generated DataBinding classes inherit from this class So they all have methods of this class
void addOnRebindCallback(OnRebindCallback listener) // Add a binding listener to call back when the Variable is set void removeOnRebindCallback(OnRebindCallback listener) // Delete bound listener View getRoot() // Returns the bound view object abstract void invalidateAll() // Invalidate all expressions and reset the expression immediately The OnRebindCallback callback will be triggered again (which can be regarded as a reset) abstract boolean setVariable(int variableId, Object value) // Variables can be set according to the field id void unbind() // Unbound, the ui will not change according to the data, but the listener will trigger
Here are three methods to focus on:
abstract boolean hasPendingBindings() // When the ui needs to change according to the current data, it will return true (there is a moment after the data changes) void executePendingBindings() // Force the ui to refresh the data immediately,
When you change the data (when you set the Observable viewer), the UI will be refreshed immediately, but the UI will not be refreshed until the next frame, with a certain delay During this time, hasPendingBindings() will return true If you want to refresh the UI synchronously (or immediately), you can call executePendingBindings()
OnRebindCallback
The listener can listen to the lifecycle of the layout binding
mDataBinding.addOnRebindCallback(new OnRebindCallback() { /** * Before binding * @param binding * @return If true is returned, the layout will be bound. If false is returned, the layout will be unbound */ @Override public boolean onPreBind(ViewDataBinding binding) { return false; } /** * If unbound, the method is called back (depending on the return value of onPreBind) * @param binding */ @Override public void onCanceled(ViewDataBinding binding) { super.onCanceled(binding); } /** * Binding complete * @param binding */ @Override public void onBound(ViewDataBinding binding) { super.onBound(binding); } });
OnPropertyChangedCallback
DataBinding also has a data change listener, which can listen to the setting events of variables
mDataBinding.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() { /** * It will call back when DataBinding sets data * @param sender DataBinding Generated class * @param propertyId Variable id of */ @Override public void onPropertyChanged(Observable sender, int propertyId) { ActivityMainBinding databinding = (ActivityMainBinding) sender; switch (propertyId) { case BR.data: Log.d("journal", "(MainActivity.java:54) ___ Result = " + databinding.getData().getName()); break; case BR.dataSecond: break; } } });
DataBindingUtil
DataBinding can bind not only activities but also view contents
// view static <T extends ViewDataBinding> T bind(View root) static <T extends ViewDataBinding> T bind(View root, DataBindingComponent bindingComponent) // layout static <T extends ViewDataBinding> T inflate(LayoutInflater inflater, int layoutId, ViewGroup parent, boolean attachToParent, DataBindingComponent bindingComponent) // assembly static <T extends ViewDataBinding> T inflate(LayoutInflater inflater, int layoutId, ViewGroup parent, boolean attachToParent) // activity static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId) static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId, DataBindingComponent bindingComponent)
There are also two less commonly used methods to retrieve whether the view is bound. If not, nul is returned
static <T extends ViewDataBinding> T getBinding(View view) // Different from getBinding, if the view is not bound, it will check whether the parent container is bound static <T extends ViewDataBinding> T findBinding(View view)
Other methods
// Return the string type according to the id of BR passed May be used for log output static String convertBrIdToString(int id)
For example, Br The name field corresponds to 4. You can use this method to convert 4 to "name"
DataBindingComponent
By default, the BindingAdapter annotation can be used for all XML attributes These custom properties can be switched by making different databindingcomponents
To create a DatabindingComponent:
- Create a custom class. The class contains functions that use @ BindingAdapter. No static functions are required At this time, Android studio will automatically generate the databindingcomposer interface
- When creating a derived class of DatabindingComponent, you will be prompted that a method requires override If you omit the first step, there will be no
- Use the DataBindingUtils tool to set your custom derived classes into Databinding, which includes global defaults and singletons
First step
class PinkComponent { @BindingAdapter("android:bindName") fun TextView.setBindName(name:String?){ if (!name.isNullOrEmpty() && name != text) { text = "Data body" } } @BindingAdapter("android:bindNameAttrChanged") fun TextView.notifyBindName(inverseBindingListener: InverseBindingListener){ doAfterTextChanged { inverseBindingListener.onChange() } } @InverseBindingAdapter(attribute = "android:bindName") fun TextView.getBindName():String{ return text.toString() } }
Step 2
class CusComponent : DataBindingComponent { override fun getPinkComponent(): PinkComponent { return PinkComponent() // null cannot be returned here } }
Step 3
The default components are set by DataBindingUtils, but the methods are different
static void setDefaultComponent(DataBindingComponent bindingComponent) static DataBindingComponent getDefaultComponent()
The above settings must be set before binding the view. They are global by default and only need to be set once
static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId, DataBindingComponent bindingComponent)
If you do not execute setDefaultComponent, you can choose to pass it in separately through the function. You must pass it in every time, otherwise an error will be reported
DatabindingComponent can only use @ BindingAdapter annotation
be careful
- You can use include, but not as the root layout merge cannot be used
- If the DataBinding class is not generated automatically, you can write a variable first (or under make module)
- Even if you do not bind the data (you may bind the data in the successful network request), the data will be bound as long as the view is created At this time, the data is an empty object The fields of empty objects will also have default values (the default value of String is null, and textview will display NULL); And if you use ternary expressions, the ternary expressions of empty objects are false; Therefore, it is recommended not to consider the case of empty objects;
- If you assign a function to a user-defined attribute (BindingAdapter) that requires a Boolean value, the null pointer will return false;
Recommended plug-ins
DataBindingSupport
Automatically create expressions and nodes in XML layout through shortcut keys (alt + enter), and AS4 is invalid
DataBindingConvert
Use the shortcut keys to quickly layout the package as layout, and AS4 is available
Related tutorials
Android Foundation Series tutorials:
Android foundation course U-summary_ Beep beep beep_ bilibili
Android foundation course UI layout_ Beep beep beep_ bilibili
Android basic course UI control_ Beep beep beep_ bilibili
Android foundation course UI animation_ Beep beep beep_ bilibili
Android basic course - use of activity_ Beep beep beep_ bilibili
Android basic course - Fragment usage_ Beep beep beep_ bilibili
Android basic course - Principles of hot repair / hot update technology_ Beep beep beep_ bilibili
This article is transferred from https://juejin.cn/post/6844903549223059463 , in case of infringement, please contact to delete.