一、ViewModel
1、第一个ViewModel用例
到目前为止 ,业务代码都是写在activity中,但是这样activiy代码里太大了。
ViewModel就是为了分担activity一部分代码。用于存储UI相关数据。也就是说UI看到的数据的相关变量应该写在ViewModel中。
ViewModel的生命周期横跨activity的创建、旋转、finish生命周期。所以生命周期更长。当activity横屏时,activity会重建,数据会被销毁,数据放到ViewModel就不会丢失。所以比较好的编程规范是为每个activity、fragment都创建配套的ViewModel.
使用前需要引入依赖,修改app/build.gradle:
dependencies{
implementation:"androidx.lifecycle:lifecycle-extensions:2.1.0"
}
下面演示如何和activity组合,本例演示一个计数器。
1、创建一个ViewMode,定义相关变量:
class MainViewModel:ViewModel(){
var counter=0 //计数
}
2、创建MainActivity
class MainActivity:AppCompatActivity(){
lateint var viewModel:MainViewModel
override fun Oncreate(aveInstanceSate:Bundle?){
//自动生成的代码
//实例化
viewModel=ViewModelProviders.of(this).get(MainViewModel::class.java)
//点击按钮修改ViewModel的counter成员
Button.setOnClickListener{
viewModel.counter++
refreshCounter()//更新到UI
}
}
private fun refreshCounter(){
TextView.text=viewModelcounter.toString()
}
}
viewModel的实例不能在OnCreate中创建,因为生命周期不同,当OnCreate每次执行都会被创建一个实例,无法长时间保留数据。所以需要通过ViewModelProviders来创建。
2、向ViewModel传参
因为ViewModel实例化是通过ViewModelProviders来创建,在初始化时,我们无法直接传参,但是可以通过ViewModelProviders.Factory来实现。
下面将上面的例子改成即使activity退出重新启动,也能根据上次的计数重新累计。
1、修改MainViewModel
修改构造函数,接受一个参数。
class MainViewModel(countReserved:Int):ViewModel(){
var counter=countReserved //计数
}
2、实现一个ViewModelProviders.Factory
class MainViewModelFactory(private val countReserved:Int):ViewModelProviders.Factory{
override fun<T:ViewModel>create(modelClass:Class<T>):T{
return MainViewModel(countReserved) as T
}
}
3、修改MainActivity
class MainActivity:AppCompatActivity(){
lateint var viewModel:MainViewModel
lateint var sp:SharePreferences //数据持久化
override fun Oncreate(aveInstanceSate:Bundle?){
//自动生成的代码
//数据持久化
sp=getPreferences(Context.MODE_PRIVATE)
val countReserved=sp.getInt("count_reserved",0)
//使用MainViewModelFactory实例化
viewModel=ViewModelProviders.of(this,MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
//点击按钮修改ViewModel的counter成员
Button.setOnClickListener{
viewModel.counter++
refreshCounter()//更新到UI
}
}
private fun refreshCounter(){
TextView.text=viewModelcounter.toString()
}
//退出保存最新数据
override fun onPause(){
super.onPause()
sp.edit{
putInt("count_reserved",viewModel.counter)
}
}
}
二、lifecycles控件
在实际业务中,需要实时监控activity的生命周期,比如正在网络请求,用户忽然退出了程序,这时网络响应代码不应该在继续执行了。
我们只要重写activity生命周期相关方法即可。但是在其他类中感知activity的生命周期,就必须手工写个类,在activity的生命周期方法进行调用,也能得知activity的生命周期。
class MyobServer{
fun start(){}
fun stop(){}
}
class MainActivity:AppcompateActivity{
lateint ver observer:MyobServer
override onCreate(){
observer=MyobServer()
}
onverride fun onStart(){
super.onStart()
observer.start()
}
onverride fun onStop(){
super.onStop()
observer.stop()
}
}
我们使用lifecycles控件能更简单的感知activity的生命周期。
class MyobServer:LifecycleObserver{
@OnlifecycelEvent(Lifecycle.Event.ON_START)
fun start(){}
@OnlifecycelEvent(Lifecycle.Event.ON_STOP)
fun stop(){}
}
在MainActivity添加一行代码,即可感知activity代码。
lifecycle.addObserver(MyObserver()
现在已经完了对activity的生命周期监控,但是还是无法主动获取activiy的状态,只能等activity通知。
我们只要把activiy中的lifecycle实例传进来即可,但是不要传递activity实例,可能会导致activity实例无法释放导致内存溢出。
class MyobServer(val lifecycle:Lifecycle):LifecycleObserver{
val lifecycle:Lifecycle=lifecycle
fun getStatus():Lifecycle{
return lifecycle.getcurrentState
//getcurrentState返回一个枚举,对应activity5种生命周期。
}
}
三、liveData
到目前为止,我们可以用viewModel保存UI数据,可以通过lifecycle监控activity生命周期,但是数据的更新都是在activity中更新,我们是否有办法主动通知activity更新数据?
不要尝试将activity实例传给veiwModel进行更新数据。
LiveData的作用是将数据的变化通知给activity。我们通修改ViewModel可以来完成。还是以上面的计数器为例。
1、修改MainViewModel
class MainViewModel(countReserved:Int):ViewModel(){
var counter= MutableLiveData<Int>()
init{
counter.value=countReserved
}
fun plusOne(){
val count=counter.value?:0
counter.value=count+1
}
fun clear(){
counter.value=0
}
}
MutableLiveData的数据类型泛型,只有三个getValue、setValue、postValue三个方法。
一、底部菜单栏(kotlin版本)
在jetpack中,提供了两个控件,可以方便的实现底部菜单:
控件 | 说明 |
---|---|
BottomNavigationView | 包含一个菜单XML文件 |
Fragment | 局部界面 |
具体思路是,在BottomNavigationView设置setOnNavigationItemSelectedListener监听,获取当前菜单ID,来编写哪些fragment显示,哪个fragment显示。
具体实现如下:
1、创建MainActivity
UI布局代码:
<?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=".MainActivity">
<include
layout="@layout/content_main"
android:id="@+id/container"
app:layout_constraintBottom_toTopOf="@+id/bottom_nav_menu"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:background="?android:attr/windowBackground"
app:menu="@menu/nav_menu"
android:id="@+id/bottom_nav_menu"/>
</androidx.constraintlayout.widget.ConstraintLayout>
布局分为两部分,上部分是各fragmenet内容展现区,是直接引入的一个布局文件,这里面就是一个容器,以后在代码里动态绑定fragmenet。
下部分是BottomNavigationView,是菜单区域,通过app:menu指定一个菜单。下面是两部分的布局:
1、内容区域布局:
通过include引入content_main.xml布局:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
</FrameLayout>
2、菜单布局xml文件:
菜单文件存储与res/menu/nav_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/index_button"
android:title="@string/title_index" />
<item android:id="@+id/sourse_button"
android:title="@string/title_sourse" />
<item android:id="@+id/home_button"
android:title="@string/title_home" />
</menu>
2、创建fragment
菜单总共有三个,首页,选课,我的。为了演示,我没有创建三个fragmenet,只创建了2两个fragment,只要完成切换即可。
创建HomeFragment、IndexFragment,界面简单显示一句话即可。因为不在里面做任何的操作,所以代码自动生成即可。
3、在MainActivity填写切换代码:
在onCreate里,主要完成如下操作:
1、将fragmenet加入FragmentManger,并且隐藏,只显示一个默认的fragment。
2、给菜单BottomNavigationView注册菜单选中监听事件:setOnNavigationItemSelectedListener
具体代码如下:
class MainActivity : AppCompatActivity() {
val homeFragment=HomeFragment()
val indexFragment=IndexFragment()
private var activeFragment: Fragment = indexFragment
lateinit var bottom_nav_menu:BottomNavigationView;
private val fragmentManager = supportFragmentManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//将fragmenet加入FragmentManger,并且隐藏,只显示一个默认的fragment。
fragmentManager.beginTransaction().apply {
add(R.id.container, indexFragment, "home").hide(indexFragment)
add(R.id.container, homeFragment,"index").hide(homeFragment).show(activeFragment)
}.commit()
//bottom_nav_menu.itemIconTintList = null
//给菜单BottomNavigationView注册菜单选中监听事
bottom_nav_menu=findViewById<BottomNavigationView>(R.id.bottom_nav_menu)
initListeners() //注册监听事件
}
private fun initListeners() {
bottom_nav_menu.setOnNavigationItemSelectedListener { menuItem ->
when (menuItem.itemId) {
R.id.index_button -> {
fragmentManager.beginTransaction().hide(activeFragment).show(indexFragment).commit()
activeFragment = indexFragment
true
}
R.id.home_button -> {
fragmentManager.beginTransaction().hide(activeFragment).show(homeFragment).commit()
activeFragment = homeFragment
true
}
else -> false
}
}
}
}