Jetpack - ViewModel、LiveData、DataBinding(数据绑定、双向数据绑定)

一、ViewModel

1、基本介绍
  • ViewModel 属于 Android Jetpack 架构组件的一部分,ViewModel 被设计用来存储和管理与 UI 相关的数据,这些数据在配置更改(例如,屏幕旋转)时能够幸存下来,ViewModel 的生命周期与 Activity 或 Fragment 的生命周期紧密相关,但比它们更长,这意味着即使 Activity 或 Fragment 被重新创建,ViewModel 中的数据也会保留下来,它有如下特点
  1. 配置更改时数据保留:当屏幕旋转或发生其他配置更改时,ViewModel 中的数据不会丢失

  2. 生命周期管理:ViewModel 在关联的 Activity 或 Fragment 被销毁时也会被清理,但它在配置更改时会保留下来

  3. 数据共享:可以在多个 Fragment 或 Activity 之间共享同一个 ViewModel 实例,以共享数据

  4. 与 LiveData 配合使用:ViewModel 通常与 LiveData 一起使用,以便在数据发生变化时通知 UI 更新

2、不使用 ViewModel
(1)Activity Layout
  • activity_view_model_no_use.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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=".ViewModelNoUseActivity">

    <Button
        android:id="@+id/btn_add"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="add"
        android:text="add"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_num"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.35000002" />

</androidx.constraintlayout.widget.ConstraintLayout>
(2)Activity Code
  • ViewModelNoUseActivity.java
java 复制代码
package com.my.viewmodel;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class ViewModelNoUseActivity extends AppCompatActivity {

    private TextView tvNum;
    private int num;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view_model_no_use);

        tvNum = findViewById(R.id.tv_num);
    }

    public void add(View view) {
        tvNum.setText(String.valueOf(++num));
    }
}
3、使用 ViewModel
(1)ViewModel
  • MyViewModel.java
java 复制代码
package com.my.jetpackdemo.viewmodel;

import androidx.lifecycle.ViewModel;

public class MyViewModel extends ViewModel {
    public int num;
}
(2)Activity Layout
  • activity_view_model_no_use.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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=".ViewModelNoUseActivity">

    <Button
        android:id="@+id/btn_add"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="add"
        android:text="add"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_num"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.35000002" />

</androidx.constraintlayout.widget.ConstraintLayout>
(3)Activity Code
  • ViewModelNoUseActivity.java
java 复制代码
package com.my.viewmodel;

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;

import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

import com.my.viewmodel.viewmodel.MyViewModel;

public class ViewModelActivity extends AppCompatActivity {

    private TextView tvNum;
    private MyViewModel myViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view_model);

        tvNum = findViewById(R.id.tv_num);
        myViewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);
        tvNum.setText(String.valueOf(myViewModel.num));
    }

    public void add(View view) {
        tvNum.setText(String.valueOf(++myViewModel.num));
    }
}

二、LiveData

1、基本介绍
  • LiveData 是 Android Jetpack 架构组件中的一个类,它用于在数据发生变化时通知观察者(通常是 UI 组件),LiveData 是一个可观察的数据持有者,它与生命周期感知组件(例如,Activity、Fragment)紧密集成,这意味着它只会在这些组件处于活跃状态时更新观察者,它有如下特点
  1. 生命周期感知:LiveData 只在有活跃的观察者时才会分发更新,这有助于避免在组件(例如,Activity、Fragment)不再可见时发生不必要的更新

  2. 粘性事件:LiveData 可以选择性地保留最后一个值,并在新的观察者开始观察时立即发送该值(粘性事件的行为),这对于如登录状态、配置变化等需要即时知道当前状态的情况很有用

  3. 线程安全:可以在任何线程上修改 LiveData 的值,而观察者会在主线程上接收到这些更新,从而避免了直接操作 UI 组件的线程安全问题

  4. 数据一致性:LiveData 保证观察者会收到最新的数据,即使它们是在数据变化之后开始观察的

2、LiveData 方法
方法 说明
setValue 用于设置 LiveData 对象的新值,当调用此方法时,所有活动的观察者都会收到这个新值 如果 LiveData 对象当前没有活动的观察者,setValue 方法调用不会做任何事情 但是,一旦有观察者,它将立即收到最近设置的值 注:setValue 方法应该在主线程上调用,因为观察者会在主线程上接收更新
getValue 用于获取 LiveData 对象当前持有的值,如果没有设置值,则返回 null
postValue 另一个用于设置 LiveData 值的方法,但它可以在任何线程上调用 从非主线程更新 LiveData 时,应该使用 postValue 方法而不是 setValue 方法 postValue 方法会将更新操作安排到主线程上执行,从而确保 UI 的更新是在主线程上进行的
3、演示
(1)ViewModel
  • MyLiveData.java
java 复制代码
package com.my.livedata.viewmodel;

import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

public class MyLiveData extends ViewModel {
    private MutableLiveData<Integer> currentSecond;

    public MutableLiveData<Integer> getCurrentSecond() {
        if (currentSecond == null) {
            currentSecond = new MutableLiveData<>();
            currentSecond.setValue(0);
        }
        return currentSecond;
    }
}
  • 上述代码使用了 MutableLiveData 来存储和分发一个整数值,它允许修改存储的值并通知观察者
(2)Activity Layout
  • activity_live_data.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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=".LiveDataActivity">

    <TextView
        android:id="@+id/tv_current_second"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
(3)Activity Code
  • LiveDataActivity.java
java 复制代码
package com.my.livedata;

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;

import android.os.Bundle;
import android.widget.TextView;

import com.my.livedata.viewmodel.MyLiveData;

import java.util.Timer;
import java.util.TimerTask;

public class LiveDataActivity extends AppCompatActivity {

    private TextView tvCurrentSecond;
    private MyLiveData myLiveData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_live_data);

        tvCurrentSecond = findViewById(R.id.tv_current_second);
        myLiveData = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyLiveData.class);
        myLiveData.getCurrentSecond().observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                tvCurrentSecond.setText(String.valueOf(integer));
            }
        });
        startTimer();
    }

    private void startTimer() {
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                myLiveData.getCurrentSecond().postValue(myLiveData.getCurrentSecond().getValue() + 1);
            }
        }, 1000, 1000);
    }
}
  1. observe 方法用来观察 LiveDataViewModel 中的 currentSecond,当 currentSecond 的值改变时,onChanged 方法会被调用

  2. postValue 方法用来更新 LiveDataViewModel 中的 currentSecond 的值

4、LiveData 优化
(1)ViewModel
  • MyAnotherLiveData.java
java 复制代码
package com.my.livedata.viewmodel;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

public class MyAnotherLiveData extends ViewModel {
    private MutableLiveData<Integer> currentSecond = new MutableLiveData<>(0);

    public LiveData<Integer> getCurrentSecond() {
        return currentSecond;
    }

    public Integer getValue() {
        return currentSecond.getValue();
    }

    public void setValue(Integer integer) {
        currentSecond.setValue(integer);
    }

    public void postValue(Integer integer) {
        currentSecond.postValue(integer);
    }
}
(2)Activity Layout
  • activity_another_live_data.xml
java 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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=".AnotherLiveDataActivity">

    <TextView
        android:id="@+id/tv_current_second"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
(3)Activity Code
  • AnotherLiveDataActivity.java
java 复制代码
package com.my.livedata;

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;

import android.os.Bundle;
import android.widget.TextView;

import com.my.livedata.viewmodel.MyAnotherLiveData;

import java.util.Timer;
import java.util.TimerTask;

public class AnotherLiveDataActivity extends AppCompatActivity {

    private TextView tvCurrentSecond;
    private MyAnotherLiveData myAnotherLiveData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_another_live_data);

        tvCurrentSecond = findViewById(R.id.tv_current_second);
        myAnotherLiveData = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyAnotherLiveData.class);
        myAnotherLiveData.getCurrentSecond().observe(this, (integer) -> {
            tvCurrentSecond.setText(String.valueOf(integer));
        });
        startTimer();
    }

    private void startTimer() {
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                myAnotherLiveData.postValue(myAnotherLiveData.getValue() + 1);
            }
        }, 1000, 1000);
    }
}
5、Fragment 通信
(1)ViewModel
  • SeekBarViewModel.java
java 复制代码
package com.my.jetpackdemo.viewmodel;

import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

public class SeekBarViewModel extends ViewModel {
    private MutableLiveData<Integer> process;

    public MutableLiveData<Integer> getProcess() {
        if (process == null) {
            process = new MutableLiveData<>();
            process.setValue(0);
        }
        return process;
    }
}
(2)Fragment
  • fragment_first.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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=".fragment.FirstFragment">

    <SeekBar
        android:id="@+id/seek_bar_first"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:max="100"
        android:min="0"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
  • FirstFragment.java
java 复制代码
package com.my.livedata.fragment;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SeekBar;

import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;

import com.my.livedata.R;
import com.my.livedata.viewmodel.SeekBarViewModel;

public class FirstFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_first, null, false);
        SeekBar seekBarFirst = root.findViewById(R.id.seek_bar_first);
        SeekBarViewModel seekBarViewModel = new ViewModelProvider(getActivity(),
                new ViewModelProvider.AndroidViewModelFactory(getActivity().getApplication())).get(SeekBarViewModel.class);
        seekBarViewModel.getProcess().observe(getActivity(), new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                seekBarFirst.setProgress(integer);
            }
        });

        seekBarFirst.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                seekBarViewModel.setValue(progress);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
        return root;
    }
}
  • fragment_second.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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=".fragment.SecondFragment">

    <SeekBar
        android:id="@+id/seek_bar_second"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:max="100"
        android:min="0"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
  • SecondFragment.java
java 复制代码
package com.my.livedata.fragment;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SeekBar;

import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;

import com.my.livedata.R;
import com.my.livedata.viewmodel.SeekBarViewModel;

public class SecondFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_second, null, false);
        SeekBar seekBarSecond = root.findViewById(R.id.seek_bar_second);
        SeekBarViewModel seekBarViewModel = new ViewModelProvider(getActivity(),
                new ViewModelProvider.AndroidViewModelFactory(getActivity().getApplication())).get(SeekBarViewModel.class);
        seekBarViewModel.getProcess().observe(getActivity(), new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                seekBarSecond.setProgress(integer);
            }
        });

        seekBarSecond.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                seekBarViewModel.setValue(progress);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
        return root;
    }
}
(3)Activity Layout
  • activity_live_data_fragment.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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=".LiveDataFragmentActivity">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/gl"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_end="365dp" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fcv1"
        android:name="com.my.livedata.fragment.FirstFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/gl"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fcv2"
        android:name="com.my.livedata.fragment.SecondFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/gl" />
</androidx.constraintlayout.widget.ConstraintLayout>
(4)Activity Code
  • LiveDataFragmentActivity.java
java 复制代码
package com.my.livedata;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;

public class LiveDataFragmentActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_live_data_fragment);
    }
}

三、数据绑定

1、基本介绍
  • Android DataBinding 是一种布局书写方式,它允许将数据直接绑定到布局的 XML 中,使得数据的变化能够直接反映到 View 上,这是 Android团队实现 MVVM 架构的一种方法,旨在减少 Android 开发中的大量模板代码(例如,findViewById()),增加代码及逻辑清晰度,提高开发效率和维护效率
2、演示
(1)Setting
  • 模块级 build.gradle

    android {
    ...

    复制代码
      defaultConfig {
          ...
          
          dataBinding {
              enabled = true
          }
      }

    }

(2)Entity
  • Idol.java
java 复制代码
package com.my.jetpackdemo.entity;

public class Idol {
    public String name;
    public String star;

    public Idol(String name, String star) {
        this.name = name;
        this.star = star;
    }
}
(3)Activity Layout
  • activity_data_binding.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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="idol"
            type="com.my.databinding.entity.Idol" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".DataBindingActivity">

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/gl"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            app:layout_constraintGuide_percent="0.5" />

        <TextView
            android:id="@+id/tv_idol_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{ idol.name }"
            android:textSize="20sp"
            app:layout_constraintBottom_toTopOf="@+id/gl"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv_idol_star"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{ idol.star }"
            android:textSize="20sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="@+id/gl" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
(4)Activity Code
  • DataBindingActivity.java
java 复制代码
package com.my.databinding;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import com.my.databinding.databinding.ActivityDataBindingBinding;
import com.my.databinding.entity.Idol;

public class DataBindingActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityDataBindingBinding activityDataBindingBinding = DataBindingUtil.setContentView(this, R.layout.activity_data_binding);
        Idol idol = new Idol("张学友", "五星");
        activityDataBindingBinding.setIdol(idol);
    }
}
3、更多用法
(1)Entity
  • User.java
java 复制代码
package com.my.databinding.entity;

public class User {
    public String name;
    public int star;

    public User(String name, int star) {
        this.name = name;
        this.star = star;
    }
}
(2)Listener
  • EventHandleListener.java
java 复制代码
package com.my.databinding.listener;

import android.content.Context;
import android.view.View;
import android.widget.Toast;

public class EventHandleListener {
    private Context context;

    public EventHandleListener(Context context) {
        this.context = context;
    }

    public void btnOnClick(View view) {
        Toast.makeText(context, "点赞", Toast.LENGTH_SHORT).show();
    }
}
(3)Util
  • UserUtil.java
java 复制代码
package com.my.jetpackdemo.util;

public class UserUtil {
    public static String getStar(int star) {
        switch (star) {
            case 1:
                return "一星";
            case 2:
                return "一星";
            case 3:
                return "三星";
            case 4:
                return "四星";
            case 5:
                return "五星";
        }
        return "零星";
    }
}
(4)Activity Layout
  • activity_user.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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="user"
            type="com.my.databinding.entity.User" />
        <variable
            name="eventHandleListener"
            type="com.my.databinding.listener.EventHandleListener" />
        <import type="com.my.databinding.util.UserUtil" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".UserActivity">

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guideline2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            app:layout_constraintGuide_end="365dp" />

        <TextView
            android:id="@+id/tv_user_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{ user.name }"
            android:textSize="20sp"
            app:layout_constraintBottom_toTopOf="@+id/guideline2"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv_user_star"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{ UserUtil.getStar(user.star) }"
            android:textSize="20sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="@+id/guideline2" />

        <Button
            android:id="@+id/btn_like"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="点赞"
            android:textSize="20sp"
            android:onClick="@{ eventHandleListener.btnOnClick }"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.498"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintVertical_bias="0.508" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
(5)Activity Code
  • UserActivity.java
java 复制代码
package com.my.databinding;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import com.my.databinding.databinding.ActivityUserBinding;
import com.my.databinding.listener.EventHandleListener;
import com.my.databinding.entity.User;

public class UserActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user);

        ActivityUserBinding activityUserBinding = DataBindingUtil.setContentView(this, R.layout.activity_user);
        User user = new User("张三", 3);
        activityUserBinding.setUser(user);
        EventHandleListener eventHandleListener = new EventHandleListener(this);
        activityUserBinding.setEventHandleListener(eventHandleListener);
    }
}
4、二级页面数据绑定
(1)Entity
java 复制代码
package com.my.databinding.entity;

public class User {
    public String name;
    public int star;

    public User(String name, int star) {
        this.name = name;
        this.star = star;
    }
}
(2)Util
  • UserUtil.java
java 复制代码
package com.my.jetpackdemo.util;

public class UserUtil {
    public static String getStar(int star) {
        switch (star) {
            case 1:
                return "一星";
            case 2:
                return "一星";
            case 3:
                return "三星";
            case 4:
                return "四星";
            case 5:
                return "五星";
        }
        return "零星";
    }
}
(3)Activity Layout
  1. user_son1.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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="user"
            type="com.my.databinding.entity.User" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/son_tv_user_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{ user.name }"
            android:textColor="#E91E63"
            android:textSize="24sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
  1. user_son2.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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="user"
            type="com.my.databinding.entity.User" />
        <import type="com.my.databinding.util.UserUtil" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/son_tv_user_star"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{ UserUtil.getStar(user.star) }"
            android:textColor="#E91E63"
            android:textSize="20sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
  1. activity_user_father.java
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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="user1"
            type="com.my.databinding.entity.User" />
        <variable
            name="user2"
            type="com.my.databinding.entity.User" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".UserFatherActivity">


        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/gl"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            app:layout_constraintGuide_percent="0.5" />

        <include
            layout="@layout/user_son1"
            app:user="@{ user1 }"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toTopOf="@+id/gl"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <include
            layout="@layout/user_son2"
            app:user="@{ user2 }"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="@+id/gl" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
(4)Activity Code
  • UserFatherActivity.java
java 复制代码
package com.my.databinding;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import android.os.Bundle;

import com.my.databinding.databinding.ActivityUserFatherBinding;
import com.my.databinding.entity.User;

public class UserFatherActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityUserFatherBinding activityUserFatherBinding = DataBindingUtil.setContentView(this, R.layout.activity_user_father);
        User user1 = new User("张三", 3);
        User user2 = new User("李四", 4);
        activityUserFatherBinding.setUser1(user1);
        activityUserFatherBinding.setUser2(user2);
    }
}
5、RecyclerView 数据绑定
(1)Entity
  • Student.java
java 复制代码
package com.my.databinding.entity;

public class Student {
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getAgeStr() {
        return age + "";
    }
}
(2)Activity Layout
  1. recycler_view_binding_item.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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="student"
            type="com.my.databinding.entity.Student" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#03A9F4"
        android:paddingTop="25dp"
        android:paddingBottom="25dp">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{ student.name }"
            android:textSize="24sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv_age"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="25dp"
            android:text="@{ student.ageStr }"
            android:textSize="20sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_name" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
  1. activity_recycler_view_binding.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.my.databinding.RecyclerViewBindingActivity">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:ignore="MissingConstraints"
            tools:layout_editor_absoluteX="1dp"
            tools:layout_editor_absoluteY="1dp" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
(3)Adapter
  • RecyclerViewBindingAdapter.java
java 复制代码
package com.my.databinding.adapter;

import android.view.LayoutInflater;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.RecyclerView;

import com.my.databinding.R;
import com.my.databinding.databinding.RecyclerViewBindingItemBinding;
import com.my.databinding.entity.Student;

import java.util.List;

public class RecyclerViewBindingAdapter extends RecyclerView.Adapter<RecyclerViewBindingAdapter.MyViewHolder> {

    List<Student> studentList;

    public RecyclerViewBindingAdapter(List<Student> studentList) {
        this.studentList = studentList;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        RecyclerViewBindingItemBinding recyclerViewBindingItemBinding =
                DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),
                        R.layout.recycler_view_binding_item, parent, false);

        return new MyViewHolder(recyclerViewBindingItemBinding);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        Student student = studentList.get(position);
        holder.recyclerViewBindingItemBinding.setStudent(student);
    }

    @Override
    public int getItemCount() {
        return studentList.size();
    }

    static class MyViewHolder extends RecyclerView.ViewHolder {

        private RecyclerViewBindingItemBinding recyclerViewBindingItemBinding;

        public MyViewHolder(RecyclerViewBindingItemBinding recyclerViewBindingItemBinding) {
            super(recyclerViewBindingItemBinding.getRoot());
            this.recyclerViewBindingItemBinding = recyclerViewBindingItemBinding;
        }
    }
}
(4)Activity Code
  • RecyclerViewBindingActivity.java
java 复制代码
package com.my.databinding;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;

import com.my.databinding.adapter.RecyclerViewBindingAdapter;
import com.my.databinding.databinding.ActivityRecyclerViewBindingBinding;
import com.my.databinding.entity.Student;

import java.util.ArrayList;
import java.util.List;

public class RecyclerViewBindingActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityRecyclerViewBindingBinding activityRecyclerViewBindingBinding = DataBindingUtil.setContentView(this, R.layout.activity_recycler_view_binding);

        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        activityRecyclerViewBindingBinding.rv.setLayoutManager(linearLayoutManager);

        List<Student> studentList = new ArrayList<>();
        studentList.add(new Student("jack", 18));
        studentList.add(new Student("smith", 19));
        studentList.add(new Student("tom", 20));
        RecyclerViewBindingAdapter recyclerViewBindingAdapter = new RecyclerViewBindingAdapter(studentList);
        activityRecyclerViewBindingBinding.rv.setAdapter(recyclerViewBindingAdapter);
    }
}

四、双向数据绑定

2、演示
(1)Entity
  • Info.java
java 复制代码
package com.my.databinding.entity;

public class Info {
    public String username;
    public String password;

    public Info(String username, String password) {
        this.username = username;
        this.password = password;
    }
}
(2)viewModel
  • InfoViewModel.java
java 复制代码
package com.my.databinding.viewmodel;

import android.util.Log;

import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;

import com.my.databinding.BR;
import com.my.databinding.entity.Info;

public class InfoViewModel extends BaseObservable {
    private Info info;

    public InfoViewModel(Info info) {
        this.info = info;
    }

    @Bindable
    public String getUsername() {
        return info.username;
    }

    public void setUsername(String username) {
        if (username != null && !username.equals(info.username)) {
            info.username = username;
            Log.d("my ==========", "setUsername: " + info.username);
            notifyPropertyChanged(BR.username);
        }
    }

    @Bindable
    public String getPassword() {
        return info.password;
    }

    public void setPassword(String password) {
        if (password != null && !password.equals(info.password)) {
            info.password = password;
            Log.d("my ==========", "setPassword: " + info.password);
            notifyPropertyChanged(BR.password);
        }
    }
}
(3)Activity Layout
  • activity_data_binding_two.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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="infoViewModel"
            type="com.my.databinding.viewmodel.InfoViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".DataBindingTwoActivity">

        <EditText
            android:id="@+id/et_username"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ems="10"
            android:inputType="textPersonName"
            android:text="@={ infoViewModel.username }"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.1" />

        <EditText
            android:id="@+id/et_password"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ems="10"
            android:inputType="textPersonName"
            android:text="@={ infoViewModel.password }"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/et_username"
            app:layout_constraintVertical_bias="0.2" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
(4)Activity Code
  • DataBindingTwoActivity.java
java 复制代码
package com.my.databinding;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import com.my.databinding.databinding.ActivityDataBindingTwoBinding;
import com.my.databinding.viewmodel.InfoViewModel;
import com.my.databinding.entity.Info;

public class DataBindingTwoActivity extends AppCompatActivity {

    private InfoViewModel infoViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityDataBindingTwoBinding activityDataBindingTwoBinding =
                DataBindingUtil.setContentView(this, R.layout.activity_data_binding_two);
        Info info = new Info("123", "456");
        infoViewModel = new InfoViewModel(info);
        activityDataBindingTwoBinding.setInfoViewModel(infoViewModel);

        test();
    }

    public void test() {
        new Thread(() -> {
            try {
                Thread.sleep(5000);
                infoViewModel.setUsername("112233");
                infoViewModel.setPassword("445566");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
3、另一种实现演示
(1)Entity
  • Info.java
java 复制代码
package com.my.databinding.entity;

public class Info {
    public String username;
    public String password;

    public Info(String username, String password) {
        this.username = username;
        this.password = password;
    }
}
(2)viewModel
java 复制代码
package com.my.databinding.viewmodel;

import android.util.Log;

import androidx.databinding.ObservableField;

import com.my.databinding.entity.Info;

public class AnotherInfoViewModel {
    public ObservableField<String> username;
    public ObservableField<String> password;

    public AnotherInfoViewModel(Info info) {
        username = new ObservableField<>(info.username);
        password = new ObservableField<>(info.password);
    }

    public String getUsername_() {
        return username.get();
    }

    public void setUsername_(String username) {
        Log.d("my ==========", "setUsername: " + username);
        this.username.set(username);
    }

    public String getPassword_() {
        return password.get();
    }

    public void setPassword_(String password) {
        Log.d("my ==========", "setPassword: " + password);
        this.password.set(password);
    }
}
(3)Activity Layout
  • activity_data_binding_two_another.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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="anotherInfoViewModel"
            type="com.my.databinding.viewmodel.AnotherInfoViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.my.databinding.DataBindingTwoAnotherActivity">

        <EditText
            android:id="@+id/et_username"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ems="10"
            android:inputType="textPersonName"
            android:text="@={ anotherInfoViewModel.username }"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.1" />

        <EditText
            android:id="@+id/et_password"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ems="10"
            android:inputType="textPersonName"
            android:text="@={ anotherInfoViewModel.password }"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/et_username"
            app:layout_constraintVertical_bias="0.2" />

        <Button
            android:id="@+id/btn_change"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Change"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
(4)Activity Code
  • DataBindingTwoAnotherActivity.java
java 复制代码
package com.my.databinding;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import android.os.Bundle;

import com.my.databinding.databinding.ActivityDataBindingTwoAnotherBinding;
import com.my.databinding.entity.Info;
import com.my.databinding.viewmodel.AnotherInfoViewModel;

public class DataBindingTwoAnotherActivity extends AppCompatActivity {

    private AnotherInfoViewModel anotherInfoViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityDataBindingTwoAnotherBinding activityDataBindingTwoAnotherBinding = DataBindingUtil.setContentView(this, R.layout.activity_data_binding_two_another);
        Info info = new Info("123", "456");
        anotherInfoViewModel = new AnotherInfoViewModel(info);
        activityDataBindingTwoAnotherBinding.setAnotherInfoViewModel(anotherInfoViewModel);

        findViewById(R.id.btn_change).setOnClickListener((v) -> {
            anotherInfoViewModel.setUsername_("112233");
            anotherInfoViewModel.setPassword_("445566");
        });

        test();
    }

    public void test() {
        new Thread(() -> {
            try {
                Thread.sleep(5000);
                anotherInfoViewModel.setUsername_("112233");
                anotherInfoViewModel.setPassword_("445566");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}