高级:jetpack组件

一、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
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT老卢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值