《Jetpack应用指南》笔记

ViewModel

持有UI数据

Activity/Fragment仅展示数据和处理用户交互。

ViewModel持有UI数据。

生命周期

Activity的重建不会影响ViewModel的生命周期。

ViewModel的生命周期函数只有一个onCleared(),当且仅当Activity代表的页面被销毁时才会调用该函数。
在这里插入图片描述

ViewModel引用Context

ViewModel的生命周期长于Activity,所以不应该在ViewModel中持有Activity的引用,否则将引起内存泄漏。

ViewModel不建议引入Activity,但如果ViewModel内需要Context时怎么办?可以采用以下方法之一:
1、使用Context.getApplicationContext();
2、使用AndroidViewModel,它是ViewModel的子类,其内部接收Application作为Context;

ViewModel的实例化

XXXViewModel mXXXViewModel = new ViewModelProvider(this).get(XXXViewModel.class);

ViewModel与onSaveInstanceState()的区别

  1. 数据差异
    onSaveInstanceState()只能保存少量,可序列化的UI数据,不能保存Bitmap这样的大数据。
    ViewModel没有这样的限制。

  2. 数据持久化
    onSaveInstanceState()可以在以下两种情况下保存少量 的UI 数据:
    ① 应用的进程在后台的时候由于内存限制而被终止。
    ② 配置更改。

ViewModel 只能在配置更改的销毁情况下保留数据,而不能在被终止的进程中保留。

LiveData

用途

LiveData可以理解为一个数据的容器。它将数据包装起来,使数据变成一个被观察者,当数据发生变化时,观察者能够获得通知。

ViewModel持有UI数据,Activity/Fragment负责展示数据,如果UI数据发生变化,就由LiveData通知Activity/Fragment刷新数据。所以LiveData通常放在ViewModel中使用。

基本使用

LiveData是一个抽象类,不能直接使用。通常我们使用的是它的子类MutableLiveData。

通过LiveData.observe()方法对LiveData所包装的数据进行观察。反过来,当我们希望修改LiveData所包装的数据的时候,可以通过LiveData.postValue()/LiveData.setValue()方法来完成。postValue()在非UI线程中调用,setValue()在UI线程中调用。

通知更新

LiveData可以感知页面的生命周期,只有当页面处于活跃状态(Lifecycle.State.STARTED或Lifecycle.State.RESUMED)才会收到LiveData的通知,若页面被销毁(Lifecycle.State.DESTROYED),那么LiveData会自动清除与页面的关联,从而避免内存泄漏。

通常,LiveData 仅在数据发生更改时才发送更新,并且仅发送给活跃观察者。此行为的一种例外情况是,观察者从非活跃状态更改为活跃状态时也会收到更新。此外,如果观察者第二次从非活跃状态更改为活跃状态,则只有在自上次变为活跃状态以来值发生了更改时,它才会收到更新。

LiveData.observeForever()使用方式与observe差不多,区别是当数据发生变化时无论页面处于什么状态下都能收到通知。因此用完后一定要调用removeObserver(),移除观察者,避免内存泄漏。

ViewModel+LiveData实现Fragment之间的通信

public class OneFragment extends Fragment {
    
    

	public void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    
    
		/*
		关键在于ViewModelProvider的构造函数传入的是getActivity()而不是Fragment.this,
		这样才能保证每个Fragment得到的是同一个ViewModel,从而共享LiveData
		*/
		XXXViewModel mXXXViewModel = new ViewModelProvider(getActivity()).get(XXXViewModel.class);
	}
}

//TwoFragment与OneFragment类似

小结

LiveData的本质就是观察者模式+感知生命周期。

DataBinding

简单使用

  1. 启动DataBinding
android {
    
    
	……
	dataBinding {
    
    
		enabled = true;
	}
}
  1. 标记布局文件
    在布局文件的根目录外增加<layout>标签。这样做的目的是告诉DataBinding库生成该布局文件对应的Binding类。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
	/*
	以下是实际布局
	……
	*/
</layout>
  1. 定义布局变量
<layout xmlns:android="http://schemas.android.com/apk/res/android">
	<data>
		<variable
			name = "变量名"
			type = "类全名"/>
		/*
		或者使用<import>标签引入类
		<import type = "类全名"/>
		<variable
			name = "变量名"
			type = "类名称"/>
		*/
		
	</data>
	/*
	以下是实际布局
	……
	*/
</layout>
  1. 获取Binding类
//该方法给Activity设置布局文件的同时,返回Binding类。
XXXBinding mXXXBinding = DataBindingUtil.setContentView(this, R.layout.xxx);
  1. 布局变量的赋值
    Binding提供了2种给布局变量赋值的方法:
    ①通用方法:XXXBinding.setVariable(BR.变量名, 变量);
    ②针对特定布局变量的赋值方法:XXXBinding.set变量名(变量)

  2. 布局表达式
    布局表达式的格式:@{}
    比如:@{布局变量.字段}@{方法调用的表达式}

<data>
	<import type = "xxx.xxx.TestUtil"/>
	<variable
		name = "book"
		type = "xxx.xxx.Book"/>
</data>

<!-- 在布局中引用静态类-->
<TextView
	android:text="@{TestUtil.getText()}"/>

<TextView
	android:text="@{book.name}"/>

布局表达式远不止这些用法,详见:Data Binding 详解(二)-布局和绑定表达式

  1. Activity最终样子
public class TestActivity extends Activity {
    
    
	protected void onCreate(Bundle savedInstanceState) {
    
    
		super.onCreate(savedInstanceState);
		XXXBinding mXXXBinding = DataBindingUtil.setContentView(this, R.layout.xxx);
		Book book = new Book();
		book.name = "Jetpack应用指南";
		mXXXBinding.setBook(book);
	}
}

事件绑定

DataBinding支持用布局表达式来处理View的事件响应。
具体做法:在布局文件中给View的事件属性赋值布局表达式。
这样相当于用布局表达式来实现对应监听器的回调。这种做法被称为事件绑定

事件属性与监听器的对应关系

事件属性名字取决于监听器方法名字。例如 View.OnClickListener 有 onClick() 方法,View.OnLongClickListener 有 onLongClick() 的方法,因此事件的属性是 android:onClickandroid:onLongClick
对于 click 事件,为了避免多种 click 事件的冲突,Google也定义了一些专门的事件处理,比如:

Class 设置监听器的方法 绑定时的属性
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

事件绑定的布局表达式有2种:引用方法绑定监听器

引用方法

使用布局变量的方法来响应事件。该方法要求参数和返回值必须与监听器的参数和返回值相匹配。如果参数或者返回值不匹配则会在编译时报错。

public class EventHandler {
    
    

	public void onClickHandle(View view) {
    
    
		System.out.println("按钮被点击了");
	}
}


<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="eventHandler"
            type="xxx.xxx.EventHandler" />
    </data>
    ...
    <Button
           ...
            android:onClick="@{eventHandler::onClickHandle}"
           ... />
    ...
</layout>

使用“引用方法”时,生成的监听器会封装布局变量的方法调用。该监听器对象是在布局变量被设置的时候创建并赋值的。如果布局变量为null,则不会创建该监听器。

/*
“引用方法”的监听器创建原理如下伪代码所示
伪代码是在运行时运行的。
*/
xxxBinding.setEventHandler(EventHandler eventHandler) {
    
    
	if(eventHandler != null) {
    
    
		button.setOnClickListener(new OnClickListener() {
    
    
				public void onClick(View view) {
    
    
					eventHandler.onClickHandle(view);
				}
		});
	}
}

绑定监听器

在布局文件中使用lambda来响应事件。该方法只要求返回值与监听器的预期返回值匹配即可

public class Tester {
    
    
	
	public boolean testLongClick() {
    
    
		return false;
	}
}

<Button
   ...
   android:onClick="@{()->tester.testLongClick()}"
   ... />

绑定监听器允许携带自定义的参数。

public class Tester {
    
    

	public boolean testLongClick(View v, String info) {
    
    
		Toast.makeText(v.getContext(), info, Toast.LENGTH_LONG).show();
	}
}

<Button
   ...
   android:onClick="@{(view)->tester.testLongClick(view, '你好')}"
   ... />

“绑定监听器”在编译时会自动创建必要的监听器并为它注册事件(监听器是一开始就创建好了,等到触发时才会判断布局变量是否为空,为空则不执行任何操作)

/*
“绑定监听器”的监听器创建原理如下伪代码所示
伪代码是在编译时运行的。
*/
button.setOnClickListener(new OnClickListener() {
    
    
	public void onClick(View view) {
    
    
		if(tester != null) {
    
    
			tester.testLongClick(view, "你好");
		}});

三元表达式

如果需要使用带有谓词的表达式(例如,三元表达式) ,可以使用监听器相匹配的返回值类型作为表达式,比如 onCLick 属性使用 void,onLongClick 属性使用 Boolean。

    android:onClick="@{(view)->view.isEnabled()?activity.showSign(view, user):void}"
    android:onLongClick="@{(v)->v.isEnabled()?activity.showSign(user):false}"

二级页面的绑定

我们将Activity/Fragment直接引用的页面称为一级页面,在一级页面中通过标签引用的页面称为二级页面。

如何将布局变量从一级页面传递给二级页面呢?

在一级布局中定义了布局变量book后,不仅可以再一级布局中接收并使用该变量,而且该变量也成为了命名空间xmlns:app的一个属性。
该属性的用途就是将布局变量book传给二级布局。

//一级页面
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
	<data>
		<variable
			name = "book"
			type = "xxx.xxx.Book"/>
	</data>

	<LinearLayout
		android:orientation="vertical"
		android:layout_width="match_parent"
		android:layout_height="match_parent">

		<include
			layout="@layout/layout_content"
			app:book="@{book}">
	</LinearLayout>
</layout>

//二级页面
/**
在二级页面layout_content中,需要定义一个与一级页面相同的布局变量,
用来接收传递过来的数据。收到book变量后即可使用该变量了。
*/
<layout xmlns:android="http://schemas.android.com/apk/res/android">
	<data>
		<variable
			name = "book"
			type = "xxx.xxx.Book"/>
	</data>

	<TextView
		……
		android:text="@{book.name}"/>
</layout>

BindingAdapter

绑定适配器(BindingAdapter)就是把布局中的属性表达式转换成对应的方法调用设置值
所谓的设置值分为2种:
①设置属性值,比如调用 setText() 方法
②设置事件侦听器,比如调用 setOnClickListener() 方法。
还允许你自定义设置值的调用方法,提供你自己的绑定逻辑。

DataBinding库中的BindingAdapter

DataBinding库中提供了许多XXXBindingAdapter类,这些类使得android原生控件支持属性表达式。

//DataBinding库下ViewBindingAdapter的部分源码
public class ViewBindingAdapter {
    
    

	@BindingAdapter({
    
    "android:padding"})
	public static void setPadding(View view, float paddingFloat) {
    
    
		final int padding = pixelsToDimensionPixelSize(paddingFloat);
		view.setPadding(padding, padding, padding, padding);
	}
}

猜你喜欢

转载自blog.csdn.net/jiejingguo/article/details/118567220
今日推荐