是什么
用来管理界面相关数据。
为什么要引入ViewModel
1、数据可以在配置发生变化后继续存在,横竖屏切换的时候,获取的Model是同一个对象。
2、避免内存泄露:比如进行一个网络请求,需要5秒,然后在1秒的时候关闭页面,此时网络请求回来要处理,否则就会出现内存泄露。
3、onCleared 方法的执行时机:经过测试得出,在横竖屏切换的时候,此时并不会执行onCleared方法,而是会在Activity销毁的时候执行。
如何使用
利用ViewModel 实现获取数据
第一步:写一个实体类
package com.example.myapplication
class User(public val name: String,public val age: String) {
}
第二步:创建ViewModel,内部主要封装了数据的容器和网络请求的操作。
package com.example.myapplication
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MyViewModel : ViewModel() {
// 数据管理
private val users: MutableLiveData<List<User>> by lazy {
// 获取数据
MutableLiveData<List<User>>().also {
loadUsers()
}
}
fun getUsers(): LiveData<List<User>> {
return users
}
private fun loadUsers() {
Thread {
// 模拟网络请求
val list = mutableListOf<User>()
list.add(User("张三", "20"))
// 将数据存储到容器中
users.postValue(list)
}.start()
}
}
第三步:然后在Activity中监听ViewModel,然后获取数据
package com.example.myapplication
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 获取到ViewModel,然后监听数据,然后展示数据
val model: MyViewModel = ViewModelProvider(this)[MyViewModel::class.java]
Log.e("cdx", "model:$model") model.getUsers().observe(this, Observer {
Log.e("cdx", "数据个数:" + it.size)
})
}
}
在Fragment中使用ViewModel
在Fragment中使用ViewModel 和在Activity中略有不同,传入的参数为 requireActivity()
val model: TestViewModel = ViewModelProvider(this)[TestViewModel::class.java]
model.getValue().observe(requireActivity(), Observer {
})
问题:Cannot invoke setValue on a background thread
原因:在线程中调用.value的属性的形式赋值,就会出现问题。
解决办法:在线程中调用postValue可以,但是代用.value属性的形式不行。
DataBinding 和 ViewModel 的配合使用
第一步:在app-build.gradle-android 下增加
dataBinding {
enabled true
}
第二步:数据实体类
package com.example.myapplication
class User(var name: String, var age: String) {
}
第三步:定义 Activity 的布局的页面逻辑
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<!--绑定数据-->
<variable
name="user"
type="com.example.myapplication.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.age}" />
</LinearLayout>
</layout>
第四步:定义ViewModel,内部模拟了设置多次数据的逻辑。
package com.example.myapplication
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MyViewModel : ViewModel() {
// 数据管理
private val users: MutableLiveData<String> by lazy {
// 获取数据
MutableLiveData<String>().also {
loadUsers()
}
}
fun getUsers(): LiveData<String> {
return users
}
private fun loadUsers() {
Thread {
users.postValue("第一次设置数据")
Thread.sleep(2000)
users.postValue("第二次设置数据")
}.start()
}
fun setValue(s: String) {
Thread {
users.postValue(s)
}.start()
}
}
第五步:定义MainActivity的逻辑:从ViewModel中获取数据,然后展示在页面上。
package com.example.myapplication
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.example.myapplication.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 构造DataBinding
val binding =
DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
// 利用ViewModel监听数据
val user = User("", "")
//获取到ViewModel,然后监听数据,然后展示数据
val model: MyViewModel = ViewModelProvider(this)[MyViewModel::class.java]
model.getUsers().observe(this, Observer {
user.name = it
user.age = it
binding.user = user
})
Thread {
Thread.sleep(4000)
model.setValue("第三次设置数据")
}.start()
}
}
左右两个Fragment之间通过ViewModel进行交互
效果图
第一步:定义 fragment_left
<?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">
<Button
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="点击"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
第二步:定义TestViewModel
package com.example.myapplication
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class TestViewModel : ViewModel() {
// 数据容器
private val value = MutableLiveData<String>()
fun getValue(): LiveData<String> {
return value
}
fun setValue(temp: String) {
value.value = temp
}
}
第三步:定义LeftFragment
package com.example.myapplication
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.example.myapplication.databinding.FragmentLeftBinding
class LeftFragment : Fragment() {
private lateinit var binding: FragmentLeftBinding;
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate<FragmentLeftBinding>(
inflater,
R.layout.fragment_left,
container,
false
)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val model: TestViewModel = ViewModelProvider(requireActivity())[TestViewModel::class.java]
Log.e("cdxcdx",model.toString())
// 更新数据
binding.btn.setOnClickListener {
model.setValue("1")
}
}
}
第四步:定义lefragment_right
<?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">
<data>
<!--绑定数据-->
<variable
name="data"
type="com.example.myapplication.TestActivityData" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{data.index}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
第五步:定义RightFragment
package com.example.myapplication
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.example.myapplication.databinding.FragmentLeftBinding
import com.example.myapplication.databinding.FragmentRightBinding
class RightFragment : Fragment() {
private lateinit var binding: FragmentRightBinding
val data = TestActivityData()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate<FragmentRightBinding>(
inflater,
R.layout.fragment_right,
container,
false
)
binding.data = data
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 观察数据变化,然后进行展示
val model: TestViewModel = ViewModelProvider(requireActivity())[TestViewModel::class.java]
Log.e("cdxcdx",model.toString())
model.getValue().observe(requireActivity(), Observer {
data.index = it
binding.data = data
})
}
}
第六步:定义Activity中的逻辑
package com.example.myapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.example.myapplication.databinding.ActivityTest1Binding
class Test1Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding =
DataBindingUtil.setContentView<ActivityTest1Binding>(this, R.layout.activity_test1)
supportFragmentManager
.beginTransaction()
.replace(R.id.contain1, LeftFragment())
.replace(R.id.contain2, RightFragment())
.commit()
}
}
注意事项:
LeftFragment 和 RightFragment 中无法通过ViewMoel进行通信,可能的原因是
LeftModel 中传入的参数为 requireActivity
val model: TestViewModel = ViewModelProvider(requireActivity())[TestViewModel::class.java]
但是在RightModel 中传入的参数为this
两个Fragment 中生成的ViewModel 不是同一个对象,所以才会导致无法通信。