Jetpack
jetpack
是Google推出的新的架构组件库,分为基础,架构,行为,界面4个部分。
ViewModel
onPause(),onStop(),onDestroy(),onCreate(),onStart(),onResume()
的过程,这中途我们创建的数据就会一同丢失,而
先在build.gradle
里面添加:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.7"
假设我们要实现一个计数器,按一下加一。activity_main.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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_main_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textSize="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_main_plus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Plus One"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_main_info" />
</androidx.constraintlayout.widget.ConstraintLayout>
然后创建一个MainViewModel
类继承ViewModel
,里面用于存储我们计数的值:
class MainViewModel : ViewModel() {
var counter = 0
}
然后MainActivity.kt
:
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MainViewModel
lateinit var mBtnPlus: Button
lateinit var mTvText: TextView
lateinit var mBtnClear: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycle.addObserver(MyObserve())
mTvText = findViewById(R.id.tv_main_info)
mBtnPlus = findViewById(R.id.btn_main_plus)
ViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
mBtnPlus.setOnClickListener {
viewModel.counter++
update()
}
update()
}
private fun update() {
mTvText.text = viewModel.counter.toString()
}
}
我们在创建ViewModel时,为什么不直接写成viewmodel = MainViewModel()
呢?因为如果这样,每次旋转屏幕时都会调用onCreate()
方法,则ViewModel就跟着被重新创建了,不能达到预期效果,所以我们需要通过ViewModelProvider
来创建实例:
ViewModelProvider(Activity/Fragment实例).get(...ViewModel::class.java)
这样我们就可以实现基本的计数,按一次加一,同时当我们旋转屏幕时数据也不会丢失。
向ViewModel传递参数
假如我们想对这个计数器实现保存或者自定义初始数功能,又该如何向ViewModel传递参数呢?我们需要借助ViewModelProvider.Factory
接口,重写create接口:
MainViewModel.kt
:
class MainViewModel(countReserved: Int) : ViewModel() {
var counter = countReserved
}
新建一个MainViewModelFactory
类:
class MainViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MainViewModel(countReserved) as T
}
}
然后我们加上一个clear按钮:
<?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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_main_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textSize="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_main_plus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Plus One"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_main_info" />
<Button
android:id="@+id/btn_main_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="clear"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_main_plus" />
</androidx.constraintlayout.widget.ConstraintLayout>
然后修改MainActivity.kt
:
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MainViewModel
lateinit var mBtnPlus: Button
lateinit var mTvText: TextView
lateinit var mBtnClear: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycle.addObserver(MyObserve())
mTvText = findViewById(R.id.tv_main_info)
mBtnPlus = findViewById(R.id.btn_main_plus)
mBtnClear = findViewById(R.id.btn_main_clear)
ViewModel = ViewModelProvider(this, MainViewModelFactory(1)).get(MainViewModel::class.java)
mBtnPlus.setOnClickListener {
viewModel.counter++
update()
}
mBtnClear.setOnClickListener {
viewModel.counter = 0
update()
}
update()
}
private fun update() {
mTvText.text = viewModel.counter.toString()
}
}
Lifecycles
我们在一个activity页面中能很好的感知他的生命周期,假如我们想要在其他类中同样去感知生命周期呢,当然可以构建一个方法,在activity的生命周期中去调用这个方法去告诉他我们现在是什么生命周期。而为了减少activity的逻辑,我们就引入了lifecycle。
新建一个MyObserver
类:
class MyObserver : DefaultLifecycleObserver{
override fun onCreate(owner: LifecycleOwner) {
Log.d("zzx","(OnCreate:)-->>");
}
override fun onStart(owner: LifecycleOwner) {
Log.d("zzx","(OnStart:)-->>");
}
override fun onResume(owner: LifecycleOwner) {
Log.d("zzx","(OnResume:)-->>");
}
override fun onPause(owner: LifecycleOwner) {
Log.d("zzx","(OnPause:)-->>");
}
override fun onStop(owner: LifecycleOwner) {
Log.d("zzx","(onStop:)-->>");
}
override fun onDestroy(owner: LifecycleOwner) {
Log.d("zzx","(onDestroy:)-->>");
}
}
通过继承DefaultLifecycleObserver
来实现对Activity生命周期的感知。
然后在MainActivity.kt
添加这行代码:
lifecycle.addObserver(MyObserver())
就实现了对activity的生命周期的监听。其中lifecycle
是通过getLifecycle()
得到的一个Lifecycle对象。
LiveData
LiveData能在数据改变时响应并能主动提供给观察者,能和ViewModel搭配使用。我们之前的加一方法在单线程时肯定能用,但是如果我们在MainViewModel里面去开启了一些新的线程,此时我们在MainActivity里面调用肯定是行不通的,所以我们就可以用LiveData去让数据主动通知观察者。
MainViewModel.kt
:
class MainViewModel(countReserved: Int) : ViewModel() {
val counter = MutableLiveData<Int>()
init {
counter.value = countReserved
}
fun plusOne() {
val count = counter.value ?: 0
counter.value = count + 1
}
fun clear() {
counter.value = 0
}
}
我们将counter
变量修改成了MutableLiveData
对象,并将泛型指定成getValue(),setValue(),postValue()
三种方法进行读写数据。getValue()
是获取LiveData中的数据,setValue()
是给LiveData设置数据,但是只能在主线程调用。如果我们开启了新线程,则需要用postValue()
设置数据。上面代码则是用的getValue()
和setValue()
的语法糖。然后修改MainActivity.kt
:
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MainViewModel
lateinit var mBtnPlus: Button
lateinit var mTvText: TextView
lateinit var mBtnClear: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycle.addObserver(MyObserver())
viewModel = ViewModelProvider(this, MainViewModelFactory(100)).get(MainViewModel::class.java)
mTvText = findViewById(R.id.tv_main_info)
mBtnPlus = findViewById(R.id.btn_main_plus)
mBtnClear = findViewById(R.id.btn_main_clear)
mBtnPlus.setOnClickListener {
viewModel.plusOne()
}
mBtnClear.setOnClickListener {
viewModel.clear()
}
viewModel.counter.observe(this) {count ->
mTvText.text = count.toString()
}
}
}
这里我们通过调用counter
这个对象的observe
方法来观察数据变化,第一个参数是LifecycleOwner对象,由于Activity和fragment本身继承了lifecycleowner,所以可以直接传this进去,第二个参数就是Observer接口,当counter
包含的数据变化时,会直接回调到这里。注意,这里其实并不能写成函数API的形式,因为this本质上是LifecycleOwner也是个单抽象方法接口,所以这里要么两种都写成api函数形式,但这里已经用了this了所以不行。但implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.7"
这个库加入了对observe()的语法扩展,我们就可以改成上面这种格式了。
然后现在的counter我们是暴露在外面的,破坏了封装性,我们可以设置一个永不可变的变量暴露给外面,但我们调用他时拿到的确实内部可变的counter,这样外部只能拿而不能修改了。MainViewModel.kt
代码如下:
class MainViewModel(countReserved: Int) : ViewModel() {
val counter: LiveData<Int>
get() = _counter
private val _counter = MutableLiveData<Int>()
init {
_counter.value = countReserved
}
fun plusOne() {
val count = _counter.value ?: 0
_counter.value = count + 1
}
fun clear() {
_counter.value = 0
}
}
map和switchMap
map
map()方法是将实际包含数据的liveData()
对象与被观察的liveData()
对象间的转换。比如我们定义一个数据类:
data class User(var firstName: String, var lastName: String, var age: Int)
然后再在MaiViewModel.kt
中创建一个
val userLiveData = MutableLiveData<User>()
但假如我们只想关注用户的名字而不想让年龄暴露出去,就需要将这个User的
在google开发者说明中,lifecycle2.5
是用的Transformations.map(liveData) {...}
这种形式,而在2.6往后就改成了liveData.map {...}
。然后我们进行转换:
private val userLiveData = MutableLiveData<User>()
val username: LiveData<String> = userLiveData.map { user ->
"${user.firstName} ${user.lastName}"
}
switchMap
switchMap()
使用方法就比较固定了,适用于对不在MainViewModel
类里面创建的
object Repository {
fun getUser(userId: String) : LiveData<User> {
val liveData = MutableLiveData<User>()
liveData.value = User(userId, userId, 0)
return liveData
}
}
我们接受一个userId
的参数来返回一个MainViewModel.kt
中接收:
fun getUser(userId: String) {
userIdLiveData.value = userId
}
但是在MainActivity.kt
中直接用viewModel.getUser(userId).observe(this) {user -> }
是肯定不行的,因为这样每次的返回一个新的switchMap
来观察:
val userIdLiveData = MutableLiveData<String>()
val users: LiveData<User> = userIdLiveData.switchMap { userId ->
Repository.getUser(userId)
}
fun getUser(userId: String) {
userIdLiveData.value = userId
}
我们创建了一个空的可变getUser()
时仅仅只会改变userIdLiveData
的值,而当这个值发生变化时,switchMap
便会进行观察,然后将函数返回的值转成一个可观察的users
这个对象就好了。
假如getUser()中没有参数怎么办呢?只需要改成:
fun getUser() {
userIdLiveData.value = userIdLiveData.value
}
这样就可以了,因为setValue()
或getValue()
方法,而不会判断是否与原数据相同。
ViewBinding
ViewBinding
可以用来代替重复写findViewById
,在每个视图生成时一次性加载全部控件。
首先在build.gradle
中启用ViewBinding
:
android {
...
viewBinding {
enabled = true
}
}
如果某一个xml
不需要生成绑定类,就添加:
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>
且XML文件生成的绑定类类名为xml文件名转换为Pascal大小写,并加上Binding。如:activity_main.xml
转为ActivityMainBinding
。
三个类绑定API:
// View已存在
fun <T> bind(view : View) : T
// View未存在
fun <T> inflate(inflater : LayoutInflater) : T
fun <T> inflate(inflater : LayoutInflater, parent : ViewGroup?, attachToParent : Boolean) : T
接下来是各种场景ViewBinding
的演示:
Activity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 1、实例化绑定实例
binding = ActivityMainBinding.inflate(layoutInflater)
// 2、获得对根视图的引用
val view = binding.root
// 3、让根视图称为屏幕上的活动视图
setContentView(view)
// 4、引用视图控件
binding.tvContent.text = "修改TextView文本"
}
}
Fragment
class ContentFragment: Fragment() {
private var _binding: FragmentContentBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentContentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.ivLogo.visibility = View.GONE
}
override fun onDestroyView() {
super.onDestroyView()
// Fragment的存活时间比View长,务必在此方法中清除对绑定类实例的所有引用
// 否则会引发内存泄露
_binding = null
}
}
RecyclerView
class TestAdapter(list: List<String>) : RecyclerView.Adapter<TestAdapter.ViewHolder>() {
private var mList: List<String> = list
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// 需在此初始化以获得父类容器,假设父类容器为item_test
val binding = ItemTestBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvItem.text = "Adapter"
}
override fun getItemCount() = mList.size
// 传递Binding对象
class ViewHolder(binding: ItemTestBinding) : RecyclerView.ViewHolder(binding.root) {
var tvItem: TextView = binding.tvItem
}
}
Dialog
如果是继承DialogFragment写法同Fragment,如果是继承Dialog写法示例如下(PopupWindow类似)
class TestDialog(context: Context) : Dialog(context) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DialogTestBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tvTitle.text = "对话框标题"
}
}
include
在使用include
导入xml布局时,也可以用viewbinding
:
val includeBinding = binding.includeLayout
includeBinding.etInput.setText("info")
封装
如果这样写,我们每次都需要写一遍很麻烦,我们可以用泛型去封装。
Activity的封装
以上面的计时器为例,先创建一个BaseActivity
:
abstract class BaseActivity<T: ViewBinding>: AppCompatActivity() {
val binding get() = _binding!!
var _binding: T? = null
abstract fun inflateBinding(): T
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = inflateBinding()
setContentView(binding.root)
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
MainActivity.kt
:
class MainActivity : BaseActivity<ActivityMainBinding>() {
lateinit var viewModel: MainViewModel
lateinit var mBtnPlus: Button
lateinit var mTvText: TextView
lateinit var mBtnClear: Button
override fun inflateBinding(): ActivityMainBinding {
return ActivityMainBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycle.addObserver(MyObserver())
viewModel = ViewModelProvider(this, MainViewModelFactory(100)).get(MainViewModel::class.java)
mTvText = findViewById(R.id.tv_main_info)
mBtnPlus = findViewById(R.id.btn_main_plus)
mBtnClear = findViewById(R.id.btn_main_clear)
binding.btnMainPlus.setOnClickListener {
viewModel.plusOne()
}
binding.btnMainClear.setOnClickListener {
viewModel.clear()
}
viewModel.counter.observe(this) {count ->
mTvText.text = count.toString()
}
}
}
Fragment的封装
BaseFragment.kt
:
abstract class BaseFragment<T: ViewBinding> : Fragment() {
var _binding: T? = null
val binding get() = _binding!!
abstract fun inflateBinding(inflater: LayoutInflater, container: ViewGroup?): T
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = inflateBinding(inflater, container)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null //一定要置空
}
}
BlankFragment.kt
:
class BlankFragment : BaseFragment<FragmentBlankBinding>() {
override fun inflateBinding(
inflater: LayoutInflater,
container: ViewGroup?
): FragmentBlankBinding {
return FragmentBlankBinding.inflate(inflater,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 在这里编写你的代码
binding.textView.text = "Hello, ViewBinding in Fragment!"
}
}
使用ViewBinding加载绑定视图
假设现在有个custom_view.xml
需要加载:
class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun inflateBinding(): ActivityMainBinding {
return ActivityMainBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 使用 LayoutInflater 加载包含布局
val inflater = LayoutInflater.from(this)
val customViewBinding = CustomViewBinding.inflate(inflater, binding.root, true)
// 操作包含布局中的视图
customViewBinding.textView.text = "Hello from Custom View!"
}
}
补:关于inflate()
最后一个参数,表示是否将这个视图添加到root
中,如果为true
则会立即添加,如果为false
,你可以决定什么时候添加,只需要添加如下代码:
// 使用 LayoutInflater 加载包含布局,但不自动添加到父视图中
val customViewBinding = CustomViewBinding.inflate(layoutInflater, binding.root, false)
// 手动将新视图添加到父视图中
binding.root.addView(customViewBinding.root)