When it comes to actually touching and using the MVVM architecture, the whole person is no good. Because personally, COMPARED with MVC and MVP, MVVM is more difficult to learn, and the knowledge of design is not the slightest bit. So I want to slowly record my growth. Please correct any mistakes.


Building an MVVM Architecture from Scratch series of articles (continuing updates) : Android builds MVVM from scratch (1) ————DataBinding Android builds MVVM from scratch (2) ————ViewModel Android builds MVVM from scratch (3) ————LiveData Android builds MVVM architecture from scratch (4) ————Room (from beginner to advanced) Android builds MVVM architecture from scratch (5) ————Lifecycles Android builds MVVM architecture from scratch (6) ———— Uses Android to play ———— Use the Android API to take you to build the MVVM framework (final)


Let’s start with a graph. (This is what some people call the “AAC framework.”)

AAC(Android Architecture Components) : Is actually a set of Components officially provided by Android to implement the MVVM Architecture. Lifecycles: LifecycleOwner is implemented on AppCompatActivity and SupportActivity in Support libraries after version 26, and the LIFECYCLE of the UI is already handled internally. We can just click on the code, as follows

Okay, back to DataBinding. This is the first step in the MVVM framework. DataBinding comes with Studio. Just add the following to the Android TAB of our app build.gradle:

dataBinding {
        enabled = true
    }
Copy the code


Initial DataBinding (Studio3.5 is recommended, easy to use)

The best feature of DataBinding is the ability to bind our data to a View. DataBinding can bind data to XML. It also supports bidirectional binding: meaning if you change the data in the bean, it will automatically change the data displayed in the view. If you change data in XML, such as in editText, it automatically changes data in bean. After the Android tag is added, go to our XML layout, press Alt + Enter on the first line of the XML and select “Convert to DataBinding Layout” to generate the DataBinding layout rules

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

    <data>

    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </RelativeLayout>
</layout>
Copy the code

The framework automatically generates a DataBinding class with the XML name +Binding.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);
        // The code above can be commented out
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    }
Copy the code


You can also customize the name of the generated class, as I did here with MyBinding

    <data class="MyBinding">

    </data>
Copy the code


1.1. Set data and click events

Add the data to the <data> tag in the XML. Here I have a String and an OnClickListener:

  • The name in the

    tag is equivalent to a data reference
  • Type is the data type, also known as the package name. The name of the class. Common data type, direct write type
  • When setting a value, use @{value}. Value is a reference to the data in
<?xml version="1.0" encoding="utf-8"? >
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="textStr"
            type="String" />
            
        <variable
            name="onClickListener"
            type="android.view.View.OnClickListener" />

    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >

        <TextView
            android:id="@+id/txt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{textStr}"
            android:onClick="@{onClickListener}"
            />

    </RelativeLayout>
</layout>

Copy the code

DataBinding automatically generates get and set methods after the Activity sets the data and sets the click event. After Studio3.5, as long as the XML is written, the IDE will automatically generate, lower versions may need to Make Project). The following implements a setup data and setup click events.

As you can see from the following code, as long as the Binding is under the layout, the Binding object can do anything. Never use findViewById or butterKnife again.

public class BaseUseActivity extends AppCompatActivity implements View.OnClickListener {
    private ActivityBaseuseBinding baseuseBinding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_baseuse);
        baseuseBinding = DataBindingUtil.setContentView(this, R.layout.activity_baseuse);
        baseuseBinding.setTextStr("You can set the data here.");
        baseuseBinding.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        baseuseBinding.txt.setText("Click on set data"); }}Copy the code


You can also call a method from the class, and it’s important to note that when you use DataBinding, the package name has to be lowercase, otherwise you can’t find the package name, so let’s say we define a class here and call a method from that class.

public class OnClickUtil {

    public void onClickWithMe(View view) {
        Toast.makeText(view.getContext(), "Call a method in a class.", Toast.LENGTH_SHORT).show(); }}Copy the code

All other steps are the same, except that the method in the call class is written differently. Let’s say button clicks to call. Calls are represented by ::, followed by the method name.

            <Button
            .
            android:onClick="@{onClickUtil::onClickWithMe}"
            />
Copy the code


1.2. Use of <import> and aliases

Here we define two classes of the same name, User. Put them in different bags.

public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age; }}Copy the code

Previously, our <data> tag could have used <import>.

    <data>
        <import type="com.lihang.databindinglover.bean.User"/>

        <variable
            name="use_first"
            type="User" />
    </data>
Copy the code

The use of

is that when the User needs to be used more than once in the same XML, the type type can be represented only by the class name of

, instead of always writing the package name. Type. However, two classes with the same name and different packages need to use the alias alias, otherwise the class name will be repeated.

<data>
        <import type="com.lihang.databindinglover.bean.User"/>

        <import
            alias="loverUser"
            type="com.lihang.databindinglover.User"/>

        <variable
            name="user_first"
            type="User" />

        <variable
            name="user_second"
            type="loverUser" />
    </data>
Copy the code

Using the Activity is very simple. If you don’t understand it, post the link later.

There are also special functions, such as rearranging the preview page. You will usually use Tools :text=” middle “to preview the layout, which can be passed

            <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text=@{user.name,default = preview text}
            />
Copy the code

When using default, even in Studio3.5, it does not prompt, but it does not affect. Also, null is already handled in DataBinding. So at this point you set user to NULL in your Activity. It doesn’t collapse


2. Use DataBinding in Fragment and RecyclerView

Fragment is used in the same way as Activity. Obtain the root directory as follows.

// Note that getting the root layout is
View view = activityAlisBinding.getRoot();
Copy the code


Here focuses on the use of recyclerView. Did we get tired of writing ViewHolder before? With DataBinding, it tells you that one ViewHolder can handle all the Viewholders you need

Let’s take a look at our unique ViewHolder. First of all, the parent classes of automatically generated Bindings are ViewDataBinding. I pulled the ViewHolder out separately. So everyone can use:

public class NewViewHolder extends RecyclerView.ViewHolder {
    public ViewDataBinding binding;

    public NewViewHolder(ViewDataBinding binding) {
        super(binding.getRoot());
        this.binding = binding; }}Copy the code

Only classes are required in Adapter

 @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        ItemNewOrderBinding binding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.item_new_order, viewGroup, false);
        return new NewViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        NewViewHolder newViewHolder = (NewViewHolder) viewHolder;
        // If there are multiple layouts, then an instansof judgment on binding is fine. So we only need one ViewHolder forever
        ItemNewOrderBinding binding = (ItemNewOrderBinding) newViewHolder.binding;
        binding.txtName.setText("So it works!!");
    }
Copy the code


One-way data binding

One-way binding can be understood as changing the data in the bean object automatically changing the display of our XML. There are three classes involved: BaseObservable, ObservableField, and ObservableCollection. It’s a bit of an observer model, given the name

3.1, BaseObservable

First let’s define a Dog class

public class Dog extends BaseObservable {

    // If it is public, use @bindable
    @Bindable
    public String name;
    // If it is private, use @bindable in get
    private String color;


    public void setDataOnlyName(String name, String color) {
        this.name = name;
        this.color = color;
        // just brush the name field
        notifyPropertyChanged(com.lihang.databindinglover.BR.name);
    }

    public void setDataAll(String name, String color) {
        this.name = name;
        this.color = color;
        // Refresh all fieldsnotifyChange(); }...// Omit some code
}
Copy the code

Here my colleague changed the color of name and color, indicating that

  • The bean object needs to inherit from BaseObservable
  • The @bindable annotation is used to indicate which fields need one-way binding. Public can be bound directly to @bindable. The private modifier needs to be tagged with @bindable on the get() method
  • notifyChange(); Refresh all of the fields, notifyPropertyChanged (com) lihang. Databindinglover. BR. Name); Refreshes a single field. Note that the refresh is all bound by @bindable. If br.name doesn’t come out. It is recommended to build the project
  • You can link to the demo at the end to see something else: one-way data binding — BaseObservable.

Inherits from the BaseObservable bean object, and can listen for refreshes

dog.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(Observable sender, int propertyId) {
                if (propertyId == com.lihang.databindinglover.BR.name) {
                    Log.e("Look where it's refreshed."."Refreshed name");
                } else if (propertyId == com.lihang.databindinglover.BR._all) {
                    Log.e("Look where it's refreshed."."All of them.");
                } else {
                    Log.e("Look where it's refreshed."."Unknown error ~"); }}});Copy the code


3.2, ObservableField

In fact, the ObservableField is a simplification of the BaseObservable, without the need to inherit or actively refresh code. We have a Human class at this point

public class Human {
    ObservableField< Parameter type >
    // BaseObservable, set, get, @bindable, refresh all wrapped. Go straight to the constructor
    public final ObservableField<String> name = new ObservableField<>();
    ObservableInt, ObservableInt, etc
    public final ObservableInt age = new ObservableInt();

    public Human(String name,int age){
        this.name.set(name);
        this.age.set(age); }}Copy the code

The operations in the Activity and XML are the same as before. To change the data, automatically change the XML simply:

// How convenient
human.name.set("Breguet");
human.age.set(15);
Copy the code


3.3, ObservableCollection

It looks like a set, just like a List Map. But the ObservableList, ObservableMap, is wrapped. When we change the data in the set. XML also changes. The only thing to notice is that when you reference these collections in XML, < type >, these symbols, they affect the XML format so you want to escape them. Use & lt; On behalf of <; > (these escape characters also support Mark Down); To learn more, you can search for DataBinding escape characters.

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="list"
            type="androidx.databinding.ObservableList< String>" />

        <variable
            name="map"
            type="androidx.databinding.ObservableMap< String,String>" />

        <variable
            name="index"
            type="int" />

        <variable
            name="key"
            type="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="@{list[index],default = haha}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{map[key],default = ha ha}" />

    </LinearLayout>
</layout>
Copy the code

There is a pit where your default = “default” is better than a reference in data. Otherwise, you’ll get an error. So here we put index = 0 put key = name. Plug in, and then dynamically change, these two values in the set:

@Override
    public void onClick(View v) {
        int randowInt = new Random().nextInt(100);
        switch (v.getId()){
            case R.id.btn_index:
                // Change the first item of the list
                list.add(0."The list of values" + randowInt);
                break;
            case R.id.btn_key:
                map.put("name"."The value of the map" + randowInt);
                break; }}Copy the code


Two-way data binding

What that means is you change the value in the bean object, it will actively change the display of the XML, change the value in the XML, it will change the properties in the bean object. Here we have a TextView to display the data; Bind the bean object with an EditTextView and a Button to dynamically query the property values in the bean object

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="human"
            type="com.lihang.databindinglover.bean.Human" />
    </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="@{human.name}" />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:layout_marginTop="20dp"
            android:text="@={human.name}" />

        <Button
            android:id="@+id/btn_search"
            android:layout_marginTop="60dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Dynamic Query properties"
            />
    </LinearLayout>
</layout>
Copy the code

The bean object binding XML shows that the one-way binding is @{attribute value} and the two-way binding is @={attribute value}. The effect is as follows:

5. Use in include and viewStub

5.1 Used in Include.

Include looks like this:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="user"
            type="com.lihang.databindinglover.bean.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="@{user.name}" />

    </LinearLayout>
</layout>
Copy the code

An Activity references include like this:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="user"
            type="com.lihang.databindinglover.bean.User" />

    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <include
            layout="@layout/include_item"
            app:user="@{user}" />

    </RelativeLayout>
</layout>
Copy the code

Note: app:user=”@{user}”. The first user is a reference to name in the include. The second user is the current value passed in.


5.2. Use in viewStub

ViewStub: wrapped by the viewStub. The wrapped layout is not loaded even when the page is displayed, unless the inflate is called. This is an optimization of the layout. Include is a layout optimization in your code.

Put the Activity layout directly. The layout of the package is the same as the include above

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="user"
            type="com.lihang.databindinglover.bean.User" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ViewStub
            android:id="@+id/view_stub"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:layout="@layout/viewstub_layout"
            app:user="@{user}" />

    </RelativeLayout>
</layout>
Copy the code

In the activity:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_viewstub);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_viewstub);
        User user = new User("I love learning.".18);
        binding.viewStub.getViewStub().inflate();
        binding.setUser(user);
    }
Copy the code


Use @bindingAdapter.

The important usage here is that when our imageView needs to load a web URL, it needs to use @bindingAdapter if it is loaded using Glide. This requires a helper class: after you create a new helper class, you can use it in XML. A bit like Dagger2

public class DataBindingHelper {
    // The @bindingAdapter annotation is similar to the custom property, followed by the property name, and the method body is similar to what you do when you get the property value.
    // The first argument is the current control type, which can also be written as View, but the load is still determined by imageView
    // The second argument is the url to load the network.
    @BindingAdapter("imageWithGlide")
    public static void loadImage(ImageView imageView, String url) {
        Glide.with(imageView).load(url)
                .placeholder(R.mipmap.ic_launcher)
                .error(R.mipmap.ic_launcher)
                .transition(withCrossFade())
                .centerCrop()
                .into(imageView);
    }

    // @bindingAdapter can also modify the system properties of the textView. This is to modify the properties of the textView.
    // Add the following paragraph "- I add it by method"
    // I'll comment it out. Otherwise, the entire textView of the project will be added to the entire textView, so if you want to test it, you can open it
    //@BindingAdapter("android:text")
    //public static void setText(TextView textView, String testStr) {
    Textview.settext (testStr + "- I added it by method ");
    / /}
}
Copy the code

Our XML is professional:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="imageUrl"
            type="String" />

        <variable
            name="testStr"
            type="String" />

    </data>


    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/img"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="15dp"
            android:text="@{testStr}" />

        <ImageView
            android:id="@+id/img"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_centerInParent="true"
            app:imageWithGlide="@{imageUrl}" />

    </RelativeLayout>
</layout>
Copy the code

The activity sets the values of imageUrl and testStr. I won’t write it because it’s too easy.


Syntax supported in DataBinding layout

Supported syntax:

  • Arithmetic + – / * %
  • String merge +
  • Logic && | |
  • Binary & | ^
  • One yuan + -! ~
  • Shift >> >>> <<
  • Compare == > < >= <=
  • Instanceof
  • Grouping ()
  • character, String, numeric, null
  • Cast
  • The method call
  • Field visit
  • Array access []
  • Three yuan? :

Unsupported syntax:

  • this
  • super
  • new
  • Shows the generic call

Your likes are my biggest motivation

My official number, just started playing