zoukankan      html  css  js  c++  java
  • Android Jetpack基本架构之ViewModel+LiveData+DataBinding入门

    前提:导入所有依赖,开启DataBinding

    app的build.gradle

    android {
        defaultConfig {
            。。。
            dataBinding {
                enabled true
            }
        }
    }
    dependencies {
        def lifecycle_version = "2.1.0"
    
        // ViewModel and LiveData
        implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
        // alternatively - just ViewModel
        implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" // For Kotlin use lifecycle-viewmodel-ktx
        // alternatively - just LiveData
        implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
        // alternatively - Lifecycles only (no ViewModel or LiveData). Some UI
        //     AndroidX libraries use this lightweight import for Lifecycle
        implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
    
        annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" // For Kotlin use kapt instead of annotationProcessor
        // alternately - if using Java8, use the following instead of lifecycle-compiler
        implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
    }
    

    ViewModel

    1.定义Model类集成androidx.lifecycle.ViewModel,并编写数据操作逻辑

    class MyViewModel : ViewModel() {
        private var mNumber = MutableLiveData<Int>(0)
    
        fun getNumber() = mNumber
        fun setNumber(number: Int) {
            mNumber.value = number
        }
    }
    //   使用MutableLiveData类包装数据,支持livedata
    //为了在ViewModel中使用Context,可以继承AndroidViewModel
    

    2.Activity类中直接调用Model类的方法来操作数据

    ViewModel的另一个用途:在Fragment之间共享数据

    以下示例了来自jetpack官方文档:

    /*
    在 Fragment 之间共享数据
    	Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的情况。想象一下主从 Fragment 的常见情况,假设您有一个 Fragment,在该 Fragment 中,用户从列表中选择一项,还有另一个 Fragment,用于显示选定项的内容。这种情况不太容易处理,因为这两个 Fragment 都需要定义某种接口描述,并且所有者 Activity 必须将两者绑定在一起。此外,这两个 Fragment 都必须处理另一个 Fragment 尚未创建或不可见的情况。
    	可以使用 ViewModel 对象解决这一常见的难点。这两个 Fragment 可以使用其 Activity 范围共享 ViewModel 来处理此类通信,如以下示例代码所示:
    */
    class SharedViewModel : ViewModel() {
        val selected = MutableLiveData<Item>()
        fun select(item: Item) {
            selected.value = item
        }
    }
    
    class MasterFragment : Fragment() {
        private lateinit var itemSelector: Selector
        private lateinit var model: SharedViewModel
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            model = activity?.run {
                ViewModelProviders.of(this)[SharedViewModel::class.java]
            } ?: throw Exception("Invalid Activity")
            itemSelector.setOnClickListener { item ->
                                             // Update the UI
                                            }
        }
    }
    
    class DetailFragment : Fragment() {
        private lateinit var model: SharedViewModel
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            model = activity?.run {
                ViewModelProviders.of(this)[SharedViewModel::class.java]
            } ?: throw Exception("Invalid Activity")
            model.selected.observe(this, Observer<Item> { item ->
                                                         // Update the UI
                                                        })
        }
    }
    /*
    	请注意,这两个 Fragment 都会检索包含它们的 Activity。这样,当这两个 Fragment 各自获取 ViewModelProvider 时,它们会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)。
    	此方法具有以下优势:
    	1)Activity 不需要执行任何操作,也不需要对此通信有任何了解。
    	2)除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。
    	3)每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。
    */
    

    LiveData

    1.注册观察器

    mViewModel.getNumber().observe(
        this, 
        Observer { it->
    		mText1.text = it.toString()
    		mText2.text = it.toString()
    	}
    )
    //    Model类中必须使用包装器才可以进行监听
    //    it参数类型为int,即原本数据类型而非包装器
    

    DataBinding

    1.将Activity与xml进行连接

    onCreate中,使用
    val binding = DataBindingUtil.setContentView(this, R.layout.activity_main) as ActivityMainBinding
    代替
    setContentView(R.layout.activity_main)
    

    2.xml文件中插入数据类
    使用包裹根布局,并插入标签:

    <layout>
        <data>
            <variable                                   //该变量在本xml文件中使用
                name="data"                             //变量名
                type="com.example.test.MyViewModel" />  //变量类型
        </data>
        <ConstraintLayout>
            。。。
            UI
            。。。
            <TextView
                android:text="@{String.valueOf(data.mNumber)}">
            <Button
                android:onClick="@{()->data.setNumber(data.mNumber + 1)}">
        <ConstraintLayout>
    </layout>
    

    3.将数据绑定至xml中

    binding.data = mViewModel        //绑定数据
    binding.lifecycleOwner = this    //绑定生命周期
    

    改变配置、内存回收等情况下的数据保持

    1.使用saveInstanceState
    在onSaveInstanceState(Bundle)和onRestoreInstanceState(Bundle)中进行操作
    
    2.使用SharePreference
    3.使用ViewModel持有一个SavedStateHandle对象,利用该对象保存数据
    class MyViewModel(var handle : SavedStateHandle) : ViewModel() {
        init {
            if (!handle.contains("NUMBER"))
                handle.set("NUMBER", 0)
        }
        fun getNumber() : LiveData<Int> = handle.getLiveData("NUMBER")
        fun add() {
            val number : Int = handle.get("NUMBER")!!
            handle.set("NUMBER", number + 1)
        }
    }
    

    经过改造后的Activity

    //Activity
    package com.example.test
    
    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import androidx.databinding.DataBindingUtil
    import androidx.lifecycle.ViewModelProviders
    import com.example.test.databinding.ActivityMainBinding
    
    class MainActivity : AppCompatActivity() {
       override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            val mViewModel = ViewModelProviders.of(this)[MyViewModel::class.java]
            /*
            	最新版本用以下方法创建ViewModel:
            	val mViewModel = ViewModelProvider(this).get(MyViewModel::class)
            */
            val binding = DataBindingUtil.setContentView(this, R.layout.activity_main) as ActivityMainBinding
    
            binding.data = mViewModel
            binding.lifecycleOwner = this
           
           
           //使用ViewModel操作数据
        }
    }
    
    //Model类
    package com.example.test
    
    import androidx.lifecycle.MutableLiveData
    import androidx.lifecycle.ViewModel
    
    class MyViewModel : ViewModel() {
        private var mNumber = MutableLiveData<Int>(0)
    
        fun getNumber() = mNumber
    
        fun setNumber(number: Int) {
            mNumber.value = number
        }
    }
    
    //xml布局
    <?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">
    
        <data>
    
            <variable
                name="data"
                type="com.example.test.MyViewModel" />
        </data>
    
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">
    
            <TextView
                android:id="@+id/textView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="80dp"
                android:text="@{String.valueOf(data.number)}"
                android:freezesText="true"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
    
            <Button
                android:id="@+id/button1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="80dp"
                android:text="+1"
                android:onClick="@{()->data.setNumber(data.number + 1)}"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/textView" />
    
            <Button
                android:id="@+id/button2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="80dp"
                android:text="+2"
                android:onClick="@{()->data.setNumber(data.number + 2)}"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/button1" />
    
            <TextView
                android:id="@+id/textView2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="80dp"
                android:text="@{String.valueOf(data.number)}"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/button2" />
    
        </androidx.constraintlayout.widget.ConstraintLayout>
    </layout>
    
  • 相关阅读:
    HDU 2122 HDU Today(dijkstra+map)
    DEDECMS后台传附件图片出现Upload filetype not allow解决办法
    织梦dedecms如何显示所有文章列表
    dedeCMS标签调用技巧和大全
    dedecms二次开发总结 变量参数
    dedecms上传图片相对路径改成绝对路径方法
    织梦Dedecms网站后台无法上传图片的步骤详解
    dedecms列表页调用当前文档的tags并带链接
    dedecms 自动获取缩略图
    dedecms网站转移(更换空间转移数据方法)
  • 原文地址:https://www.cnblogs.com/lizhenxin/p/12433736.html
Copyright © 2011-2022 走看看