• After the National Day holiday, I went to work today and felt that I had nothing to play during the holiday

The next vacation won’t be until next year

  • Continue DataBinding, part 3 of this series

The first paper ViewModel

The second LiveData

The final chapter MVVM

Let’s put down the jetpak demo that I wrote during the learning processjetpackDemo
  • Follow the old rules with an overview of the DataBinding website
  • The DataBinding function is to eliminate the need to write view assignment, state change code, and bind the data directly to XML for automatic assignment.
  • I’m going to go straight to the round

  • Use the build.gradle file in the app directory to insert the Android paragraph
android {
        ...
        dataBinding {
            enabled = true
        }
    }
Copy the code
  • Create a new activity_main.xml layout and wrap the layout around it, then insert data
<layout> <data> <variable name="mode" type=" Package name. The name of the class "/ > < / data > < androidx. Constraintlayout. Widget. Constraintlayout 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" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".dataBinding.DataBindActivity"> <TextView android:id="@+id/text" app:layout_constraintTop_toTopOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" android:layout_width="wrap_content" android:layout_marginTop="10dp" android:textSize="18sp" android:text='@{mode.xxxx}' android:textColor="@android:color/holo_red_dark" android:layout_height="wrap_content"/> </androidx.constraintlayout.widget.ConstraintLayout> </layout>Copy the code
  • The name attribute is a name you can use. The type attribute is the binding of the full path class. Note that this class is bound toCapital beginningXXX error “COULDN’t make a guess for com.xxx. XXX
  • @{mode. XXXX} is the only way to do this. @{} supports ternary operators, String, StringBuffer, and Integer
  • All that’s left is to use the XML in your code
/ / the activity here don't have to write the setContentView (R.l ayout. XXX) because DataBindingUtil. / / the setContentView already help us to finish the setContentView (R.l ayout. XXX); This step ActivityMainBinding binding = DataBindingUtil. The setContentView (this, R.l ayout. Activity_main); binding.setViewModel(mainViewModel);Copy the code

  • The ActivityMainBinding is automatically generated by DataBinding. Note that if you write the class name in the DATA tag in the XML, the default generated xxxxxBinding class name becomes the name specified by classs. By default, the Binding class generated by XML is the name of the XML, as in activity_main above, which generates an ActivityMainBinding.
  • SetViewModel is the type class that you put in the XML tag, setViewModel is the name that you define, if your name is user then the assignment method is setUser, and by the way tags support multiple tags, So you can bind many data sources.
  • In addition to the DataBinding operations described above, we can also manipulate the control using DataBinding’s control id binding, thus saving findViewById code
binding.text.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                
            }
        });
Copy the code
  • Finally, we unbind DataBinding in the onDestroy method
@Override
    protected void onDestroy() {
        super.onDestroy();
        if(binding!=null){
            binding.unbind();
        }
    }
Copy the code

  • All of this is just the simplest and most basic use of DataBinding
Act 1 DataBinding binding XML and binding data are used in different scenarios
The fragments using
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @ Nullable Bundle savedInstanceState) {/ / a choice FragmentDataBindingBinding dataBindFragment = DataBindingUtil.inflate(inflater, R.layout.fragment_data_binding, container, false); FragmentDataBindingBinding dataBindFragment = FragmentDataBindingBinding.inflate(inflater, container, false); return dataBindFragment.getRoot(); }Copy the code
Instantiating DataBinding is the same as fragment for custom composite controls and RecycleView but be aware of thisexecutePendingBindingsmethods
// Select ViewDataBindingBinding viewDataBinding = DataBindingUtil.inflate(LayoutInflater.from(context),R.layout.view_data_binding,this,true); ViewDataBindingBinding viewDataBinding = ViewDataBindingBinding.inflate(LayoutInflater.from(context),this,true);Copy the code
  • The RecycleView adapter and the ListView, GridView and other adapters can be used similarly
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { private List<User> users; @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, Int viewType) {// Select ItemAdapterBinding ItemAdapterBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_adapter,parent,false); ItemAdapterBining itemAdapterBinding = ItemAdapterBinding.inflate(LayoutInflater.from(parent.getContext()),parent,false); return new ViewHolder(itemAdapterBinding); } public MyAdapter(List<User> users) { this.users = users; } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { holder.itemAdapterBinding.setUser(users.get(position)); // When a mutable or observable changes, the binding is scheduled to change before the next frame. But sometimes the binding must be performed immediately. To enforce // use the executePendingBindings() method. / / this sentence is very critical Do not add data is likely to be confusion holder. ItemAdapterBinding. ExecutePendingBindings (); } @Override public int getItemCount() { return users.size(); } class ViewHolder extends RecyclerView.ViewHolder{ private ItemAdapterBinding itemAdapterBinding; public ViewHolder(@NonNull ItemAdapterBinding itemAdapterBinding) { super(itemAdapterBinding.getRoot()); this.itemAdapterBinding = itemAdapterBinding; }}}Copy the code
  • item_adapter.xml
<? The XML version = "1.0" encoding = "utf-8"? > <layout> <data > <variable name="user" type="com.sanyue.jetpakcdemonew.bean.User"/> </data> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto"> <TextView android:id="@+id/tv_name" Android :layout_width="match_parent" Android :gravity='center' Android :hint='@{@string/app_name + "even "}' android:background="@{user.age%2==0 ? @android:color/holo_blue_light:@android:color/holo_orange_dark }" Android :text='@{user.age%2==0? User. age%2==0? Integer.toString(user.age)}' app:layout_constraintTop_toTopOf="parent" android:textColor='@{user.age%2==0 ? @android:color/holo_red_dark:@color/colorAccent }' android:layout_height="30dp"/> </androidx.constraintlayout.widget.ConstraintLayout> </layout>Copy the code
  • The two methods of instantiation described above are DataBindingUtil and the XML-generated Binding class.

The second actTwo-way data bindingSo let’s talk about how do we use DataBinding for actual projects

  • In the above examples, the UI is changed passively only after setXXX. If you want the UI to follow the data changes, you need to use ViewModel and LiveData

  • Define a data class and a ViewModel class

public class User { private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } public User() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } public String getUser(){ return name==null&&age==0?" ":name+" year "+age; }}Copy the code
public class DemoViewModel extends ViewModel { public ObservableParcelable<Ab> ab= new ObservableParcelable<>(); public ObservableBoolean flag= new ObservableBoolean(false); public ObservableArrayList<String> list= new ObservableArrayList(); public ObservableInt count =new ObservableInt(0); private MediatorLiveData<User> userMediatorLiveData = new MediatorLiveData<>(); public LiveData<User> userLiveData = userMediatorLiveData; public MediatorLiveData<String> stringMediatorLiveData =new MediatorLiveData<>(); public ObservableField<User> userObservableField = new ObservableField<>(); public void addUser(User user){ userMediatorLiveData.setValue(user); StringMediatorLiveData. SetValue (user + "hello"); addList(stringMediatorLiveData.getValue()); userObservableField.set(user); } public String getUser(){ return userMediatorLiveData.getValue()==null?" MAO did not oh ": userMediatorLiveData getValue (). The getName () +" "this year. + userMediatorLiveData getValue (). GetAge (); } public void add(){ count.set(count.get()+1); } public void addList(String str){ list.add(str); } public DemoViewModel() {ab.set(new ab (" student ")); stringMediatorLiveData.setValue("1234"); } public static class Ab implements Parcelable { public String name; public Ab(String name) { this.name = name; } protected Ab(Parcel in) { name = in.readString(); } public static final Creator<Ab> CREATOR = new Creator<Ab>() { @Override public Ab createFromParcel(Parcel in) { return  new Ab(in); } @Override public Ab[] newArray(int size) { return new Ab[size]; }}; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeString(name); }}}Copy the code
  • In the previous article, LiveData was always used to observe the field. Observe the field with the observe method, and the addXXXXXXChangedCallback method is used to listen in the Observable series

  • ObservableBoolean

  • ObservableByte

  • ObservableChar

  • ObservableShort

  • ObservableInt

  • ObservableLong

  • ObservableFloat

  • ObservableDouble

  • ObservableParcelable

  • All ObservableParcelable classes use the same method to assign values to the get method. The ObservableParcelable assignment paradigm requires the Implementation of the Parcelable interface

  • Next, look at the XML and Activity pages

<? The XML version = "1.0" encoding = "utf-8"? > <layout> <data> <variable name="mode" type="com.sanyue.jetpakcdemonew.liveDataBinding.two.DemoViewModel" /> <variable name="onclick" type="android.view.View.OnClickListener" /> <variable name="adapter" type="com.sanyue.jetpakcdemonew.liveDataBinding.ListAdapter" /> </data> <LinearLayout 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" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent" tools:context=".liveDataBinding.two.DemoActivity"> <Button android:id="@+id/addUser" android:layout_width="wrap_content" android:onClick="@{onclick}" Android :text='@{" add a "+mode.ab.name}' Android :layout_height="wrap_content"/> <TextView Android :id="@+id/text" android:layout_width="wrap_content" android:text='@{mode.userLiveData.user}' android:layout_height="50dp"/> <Button android:id="@+id/countAdd" android:layout_width="wrap_content" android:onClick="@{onclick}" android:text='@{mode.count+"+1"}' android:layout_height="wrap_content"/> <CheckBox android:id="@+id/checkbox" android:layout_width="wrap_content" android:button="@drawable/check" android:checked="@={mode.flag}" android:text="@{String.valueOf(mode.flag)}" android:paddingLeft="10dp" android:layout_height="50dp"/> <TextView android:layout_width="wrap_content" android:text="@{mode.stringMediatorLiveData}" android:layout_height="wrap_content"/>  <TextView android:layout_width="wrap_content" android:text="@{mode.userObservableField.user}" android:layout_height="wrap_content"/> <TextView android:layout_width="wrap_content" Format (" add % D students ",mode.list.size())}' Android :layout_height="wrap_content"/> <ListView android:id="@+id/listView" android:adapter="@{adapter}" android:layout_width="match_parent" android:selectedItemPosition="@{mode.list.size()-1}" app:layout_constraintTop_toBottomOf="@+id/checkbox" android:layout_height="match_parent"/> </LinearLayout> </layout>Copy the code
  • Simply encapsulates a BaseActivity
public abstract class BaseActivity extends AppCompatActivity { private ViewDataBinding dataBinding; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); / / here is the activity of using dataBinding I here is simply to write an abstract class save things dataBinding = DataBindingUtil. The setContentView (this, getLayout ()); init(); } public abstract void init(); public abstract int getLayout(); protected <T extends ViewDataBinding> T getViewDataBinding() { return (T) dataBinding; } @Override protected void onDestroy() { super.onDestroy(); if(dataBinding! =null){ dataBinding.unbind(); }}}Copy the code
public class DemoActivity extends BaseActivity implements View.OnClickListener { private DemoViewModel viewModel; private ActivityDemoBinding demoBinding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(DemoViewModel.class); demoBinding = getViewDataBinding(); demoBinding.setMode(viewModel); demoBinding.setOnclick(this); demoBinding.setLifecycleOwner(this); ListAdapter adapter = new ListAdapter(viewModel.list); demoBinding.setAdapter(adapter); } @Override public void init() { } @Override public int getLayout() { return R.layout.activity_demo; } @Override public void onClick(View view) { switch (view.getId()){ case R.id.addUser: Viewmodel.adduser (new User(" 三",18)); break; case R.id.countAdd: viewModel.add(); break; }}}Copy the code
  • ListView adapter and item.xml
public class ListAdapter extends BaseAdapter { private ObservableArrayList<String> list; public ListAdapter(ObservableArrayList<String> list) { this.list = list; } @Override public int getCount() { return list.size(); } @Override public Object getItem(int i) { return list.get(i); } @Override public long getItemId(int i) { return i; } @Override public View getView(int i, View view, ViewGroup viewGroup) { ItemListViewBinding binding = null; if(binding==null){ binding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()),R.layout.item_list_view,viewGroup,false); } binding.setStr(list.get(i)); binding.executePendingBindings(); return binding.getRoot(); }}Copy the code
<? The XML version = "1.0" encoding = "utf-8"? > <layout> <data> <variable name="str" type="String" /> </data> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:gravity="center" android:text='@{str}' android:textColor="#ffffff" android:background="@drawable/b1" android:layout_height="30dp"> </TextView> </layout>Copy the code
  • SetLifecycleOwner (this) method, if you do not bind the lifecycle then DataBinding will not be aware of changes to LiveData, which will cause the UI to not change with the data.
  • This ability to bind XML and data is called bidirectional
  • Currently, the following controls support bidirectional features

The third actBinding adapter
  • Take a look at the code for the TextViewBindingAdapter provided by Google

  • TextView setText: android: TextView setText: Android: TextView setText: Android: TextView setText
  • The following RecycleView way to achieve the above ListView effect
  • Define an adapter class, BindingAdapters
Public class BindingAdapters {/** * @param view * @param Position is the key binding. The value of position must be viewable by the liveData type. If it is a normal listView, the method will not be executed when data changes @BindingAdapter({"android:scrollToPosition", "android:adapter"}) public static void setRecycleViewAdapter(RecyclerView view,int position,RecyclerView.Adapter adapter){ if (view.getAdapter() == null) { view.setAdapter(adapter); }else { view.getAdapter().notifyDataSetChanged(); } view.scrollToPosition(position); } @BindingAdapter("android:layoutManager") public static void setLayoutManager(RecyclerView recyclerView,RecyclerView.LayoutManager LayoutManager){ recyclerView.setLayoutManager(LayoutManager); }}Copy the code
public class DemoRecycleAdapter extends RecyclerView.Adapter<DemoRecycleAdapter.ViewHolder> { private List<String> users; @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new ViewHolder(ItemDemoRecyViewBinding.inflate(LayoutInflater.from(parent.getContext()),parent,false)); } public DemoRecycleAdapter(ObservableArrayList<String> users) { this.users = users; } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { holder.itemAdapterBinding.setUser(users.get(position)); / / this sentence is very critical Do not add data will disorder at https://developer.android.google.cn/topic/libraries/data-binding/generated-binding holder.itemAdapterBinding.executePendingBindings(); } @Override public int getItemCount() { return users.size(); } class ViewHolder extends RecyclerView.ViewHolder{ private ItemDemoRecyViewBinding itemAdapterBinding; public ViewHolder(@NonNull ItemDemoRecyViewBinding itemAdapterBinding) { super(itemAdapterBinding.getRoot()); this.itemAdapterBinding = itemAdapterBinding; }}}Copy the code
<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycleView"
        app:layout_constraintTop_toBottomOf="@+id/scoreText"
        android:layout_width="match_parent"
        android:adapter="@{recycleAdapter}"
        android:layoutManager="@{layout}"
        android:scrollToPosition="@{mode.list.size()-1}"
        android:layout_height="90dp"/>
Copy the code
demoBinding.setLayout(new LinearLayoutManager(this));
DemoRecycleAdapter recycleAdapter = new DemoRecycleAdapter(viewModel.list);
demoBinding.setRecycleAdapter(recycleAdapter);
Copy the code
  • Here is the main explanationpositionIf you want to refresh the Adapter in real time, binding an ObservableArrayList is the key. The Adapter is not an ObservableArrayList. If an observable is not bound, the custom adapter method setRecycleViewAdapter is executed only once, so you can try it out yourself.
Act 4 Inherits a BaseObservable that implements an observable itself
  • Post the official link using observable data objects

] (developer. The android. Google. Cn/topic/libra…).

public class Book extends BaseObservable { private String name; private int pages; public Book() { } public Book(String name, int pages) { this.name = name; this.pages = pages; } @Bindable public String getName() { return name; } public void setName(String name) { this.name = name; notifyPropertyChanged(BR.name); notifyPropertyChanged(BR.score); } @Bindable public int getPages() { return pages; } public void setPages(int pages) { this.pages = pages; notifyPropertyChanged(BR.pages); } @Bindable public String getScore(){ if(name==null){ return ""; } return name.startsWith("Android")? Name +" strongly recommend ":name+" this stupid book is not interesting "; }}Copy the code
  • The two key codes to implement a BaseObservable yourself are @bindable and notifyPropertyChanged
 <TextView
        android:layout_width="wrap_content"
        android:text="@{mode.book.score}"
        android:layout_height="wrap_content"/>
Copy the code
   public Book book= new Book();
    public void addBooK(String name,int page){
        book.setName(name);
        book.setPages(page);
    }
Copy the code
Final curtain

So that’s the end of the Jetpack trilogy and DataBinding is a much more complex source code than ViewModel and LiveData using annotation reflection and APT technology, The helper class ActivityMainBindingImpl is generated by annotation reflection if you are interested.

Write wrong bad place please give directions