Jatpack之ViewModel

目录

简单介绍

基本使用

创建活动的ViewModel

    两个碎片间的通信     

向ViewModel传递参数


简单介绍

        ViewModel应该可以算是Jetpack中最重要的组件之一了。其实Android平台上之所以会出现诸如MVP、MVVM之类的项目架构,就是因为在传统的开发模式下,Activity的任务实在是太重了,既要负责逻辑处理,又要控制UI展示,甚至还得处理网络回调,等等。在一个小型项目中这样写或许没有什么问题,但是如果在大型项目中仍然使用这种写法的话,那么这个项目将会变得非常臃肿并且难以维护,因为没有任何架构上的划分。
        而ViewModel的一个重要作用就是可以帮助Activity分担一部分工作,它是专门用于存放与界面相关的数据的。也就是说,只要是界面上能看得到的数据,它的相关变量都应该存放在 ViewModel中,而不是Activity中,这样可以在一定程度上减少Activity中的逻辑。
        另外,ViewModel还有一个非常重要的特性。我们都知道,当手机发生横竖屏旋转的时候, Activity会被重新创建,同时存放在Activity中的数据也会丢失。而ViewModel的生命周期和 Activity不同,它可以保证在手机屏幕发生旋转的时候不会被重新创建,只有当Activity退出的时候才会跟着Activity一起销毁。因此,将与界面相关的变量存放在ViewModel当中,这样即使旋转手机屏幕,界面上显示的数据也不会丢失。ViewModel的生命周期如图所示。

基本使用

创建活动的ViewModel

        先加入如下的依赖:

 implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"

         通常来说,比较好的编程规范是给每一个Activity和Fragment都创建一个对应的ViewModel,因此这里我们就为MainActivity创建一个MainViewModel类,并且让他继承ViewModel,如下:

class MainViewModel : ViewModel {
    // 可以添加成员变量和方法
}

         所有与界面有关的数据都应该放在ViewModel中,那么我们要实现一个计数器的功能,就可以在ViewModel中加入一个counter变量用于计数,如下:

class MainViewModel : ViewModel() {
    var counter = 0
}

         现在需要在界面上添加一个按钮,每点击一次按钮就让计数器加一,并且把最新的计数器显示在界面上,改代码省略

        现在开始实现计数器的逻辑,修改MainActivity中的代码:

class MainActivity : AppCompatActivity() {
    lateinit var viewModel: MainViewModel
    override fun onCreate(savedInstanceState: Bundle?{
        super.onCreate(savedInstanceState)
        setContentView(R.ayout .activity main)
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        plusOneBtn.setOnClickListener {
            viewModel.counter++refreshCounter()
            refreshCounter()
        }

    private fun refreshCounter() {
        infoText.text = viewModel.counter.toString()
    }
}

         代码不长,我来解释一下。这里最需要注意的是,我们绝对不可以直接去创建ViewModel的实例,而是一定要通过ViewModelProvider来获取ViewModel的实例,具体语法规则如下:

ViewModelProvider(<你的Activity或Fragment实例>).get(<你的ViewModel>::class.java)

        之所以要这么写,是因为ViewModel有其独立的生命周期,并且其生命周期要长于Activity。如果我们在onCreate()方法中创建ViewModel的实例,那么每次onCreate()方法执行的时候,ViewModel都会创建一个新的实例,这样当手机屏幕发生旋转的时候,就无法保留其中的数据了。
除此之外的其他代码应该都是非常好理解的,我们提供了一个refreshCounter()方法用来显示当前的计数,然后每次点击按钮的时候对计数器加1,并调用refreshCounter()方法刷新计数。

    两个碎片间的通信     

        但是,对于需要完成活动与碎片间通信或者碎片与碎片间通信时,创建时需要传入相同的context,接下来展示两个碎片间的通信:

class TopFragment : Fragment() {

    private lateinit var  mViewModel : MainViewModel

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fr_top,container,false)
    }
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        mViewModel = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)
        mViewModel.count.observe(viewLifecycleOwner){ count->
            tv.text = count.toString()
        }
    }
}
class BottomFragment : Fragment() {

    private lateinit var  mViewModel : MainViewModel

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fr_bottom,container,false)
    }
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        mViewModel = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)
        btn.setOnClickListener {
            mViewModel.count.value = mViewModel.count.value?.plus(1)
        }
    }

}

         需要注意的是:

mViewModel = ViewModelProvider(requireActivity()).get(MainViewModel::class.java

        ViewModelProvider传入的是activity而不是当前的fragment,道理很简单,我们要实现Fragemnt之间的数据共享或者通讯,必须要拿到相同的ViewModel实例,如果传入this,那就不是同一个ViewModel对象了。

        以下代码是LifeDate的内容,之后我会更新,简单来说它的功能是当count值发生变化时,它会通知TopFragment。

mViewModel.count.observe(viewLifecycleOwner){ count->
            tv.text = count.toString()

 接下来是主活动:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

}

向ViewModel传递参数

        由于所有ViewModel的实例都是通过ViewModelProvider来获取的,因此我们没有任何地方可以向VIewModel的构造函数中传递参数.

        当然,这个问题也不难解决,只需要借助ViewModelProvider.Factory就可以实现了.

        现在的计数器虽然在屏幕旋转的时候不会丢失数据,但是如果退出程序之后再重新打开,那么之前的计数就会被清零了.现在就要保证即使在退出程序后又重新打开的情况下,数据仍然不会丢失.

        实现这个功能就需要在 退出程序的时候对当前的计数进行保存,然后在重新打开程序的时候读取之前保存的计数,并传递给MainViewModel,因此,这里修改MainViewModel中的代码,如下
 

class MainViewModel(countReserved: Int) : ViewModel() {
    var counter = countReserved
}

        接下来的问题是如何向MainViewModel的构造函数传递数据了,就需要使用ViewModelProvider.Factory,下面看看如何实现.

        新建一个MainViewModelFactory类,并让他实现ViewModelProvider.Factory接口

class MainViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory{
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return MainViewModel(countReserved) as T
    }
}
    

 在布局中添加一个清零按钮,此处省略

然后修改MainActivity中的代码:

class MainActivity : AppCompatActivity(){
    lateinit var viewModel: MainViewModel
    lateinit var sp: SharedPreferences
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)setContentView(R.layout .activity main)
        sp = getPreferences(Context.MODE PRIVATE)
        val countReserved = sp.getInt("count reserved",0)
        viewModel = ViewModelProvider(this,MainViewModelFactory(countReserved))
                    .get(MainViewModel: : class .java)
        ...
        clearBtn.setOnClickListener {
            viewModel.counter = 0
            refreshCounter()
        }
        refreshCounter()

        override fun onPause() {
            super.onPause()
            sp.edit {
                putInt ("count reserved", viewModel.counter)
        }
    }
}

         在onCreate()方法中,我们首先获取了SharedPreferences的实例,然后读取之前保存的计数值,如果没有读到的话,就使用0作为默认值。接下来在ViewModelProvider中,额外传入了一个MainViewModelFactory参数,这里将读取到的计数值传给了MainViewModelFactory的构造函数。注意,这一步是非常重要的,只有用这种写法才能将计数值最终传递给MainViewModel的构造函数。
        剩下的代码就比较简单了,我们在“Clear”按钮的点击事件中对计数器进行清零,并且在
onPause()方法中对当前的计数进行保存,这样可以保证不管程序是退出还是进入后台,计数都不会丢失。

本文大部分内容皆来自郭霖的《Android第一行代码》第三版

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值