Jetpack组件
1数据的存储与展示----ViewModel
加入依赖
//加入ViewModel的依赖
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
当Activity界面需要进行反转或者返回时,当前界面的数据需要保存,然后进行恢复
使用方法
1自定义界面的ViewModel类,继承ViewModel,将当前界面需要用到的数据变成此类的属性。
import androidx.lifecycle.ViewModel
//有参构造进行数据的修改
class MainViewModel(countVal:Int): ViewModel() {
var count=countVal
}
2如果需要手动的去对里面的数据进行赋值,则需要通过ViewModelProvider.Factory来实现
package com.example.viewmodletest
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
//重写方法,并且将返回值返回
class MainViewModelFactory(var countVal:Int):ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MainViewModel(countVal) as T
}
}
3在界面中获取到viewModel的实例,绑定factory进行数据的手动赋值
package com.example.viewmodletest
import android.content.Context
import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.ShareActionProvider
import android.widget.TextView
import androidx.core.content.edit
import androidx.lifecycle.ViewModelProvider
class MainActivity : AppCompatActivity() {
private lateinit var textView: TextView
private lateinit var btnadd:Button
private lateinit var btnless:Button
private lateinit var clearZero:Button
private lateinit var viewModel: MainViewModel
private lateinit var sp:SharedPreferences
private var countVal=0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initData()
//检测当前Activity的生命周期
//lifecycle.addObserver(MyObserver())
btnadd.setOnClickListener(){
viewModel.count++
refreshData()
}
btnless.setOnClickListener(){
viewModel.count--
refreshData()
}
//利用构造方法将数据清零
clearZero.setOnClickListener(){
viewModel.count=0
refreshData()
}
refreshData()
}
private fun initData(){
textView=findViewById(R.id.count)
btnadd=findViewById(R.id.countSum)
btnless=findViewById(R.id.countless)
clearZero=findViewById(R.id.clearZero)
//获取当前界面的ViewModle
//viewModel=ViewModelProvider(this).get(MainViewModel::class.java)
sp=getPreferences(Context.MODE_PRIVATE)
//从存储的里面取得数据,如果没有数就默认为0
countVal=sp.getInt("countVal",0)
viewModel=ViewModelProvider(this,MainViewModelFactory(countVal)).get(MainViewModel::class.java)
}
private fun refreshData(){
textView.text=viewModel.count.toString()
}
//在界面进行暂停时将数据保存
override fun onPause() {
super.onPause()
sp.edit{
putInt("countVal",viewModel.count)
}
}
}
2线程安全且更加优雅的ViewModel—LiveData
使用LiveData对属性进行包装,当数据进行变化时,可以通过回调函数进行调用最新的数据,从而保证了数据的线程 安全性
1MutableLiveData的用法
package com.example.viewmodletest
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
//有参构造进行数据的修改
class MainViewModel(countVal:Int): ViewModel() {
//使用LiveData来监听目标值的变动状态
var counter=MutableLiveData<Int>()
init{
counter.value=countVal
}
fun add(){
val count=counter.value?:0
counter.value=count+1
}
fun sub(){
val count=counter.value?:0
counter.value=count-1
}
fun zero(){
counter.value=0
//counter.postValue()在非主线程中给LiveData设置数据
}
}
2主程序中observe回调函数的使用
package com.example.viewmodletest
import android.content.Context
import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.ShareActionProvider
import android.widget.TextView
import androidx.core.content.edit
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
class MainActivity : AppCompatActivity() {
private lateinit var textView: TextView
private lateinit var btnadd:Button
private lateinit var btnless:Button
private lateinit var clearZero:Button
private lateinit var viewModel: MainViewModel
private lateinit var sp:SharedPreferences
private var countVal=0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initData()
btnadd.setOnClickListener(){
viewModel.add()
}
btnless.setOnClickListener(){
viewModel.sub()
}
//利用构造方法将数据清零
clearZero.setOnClickListener(){
viewModel.zero()
}
//使用这个方法来检测里面数据的变化,会调用回调函数进行显示,可以保证线程安全
viewModel.counter.observe(this, Observer {
count->textView.text=count.toString()
})
}
private fun initData(){
textView=findViewById(R.id.count)
btnadd=findViewById(R.id.countSum)
btnless=findViewById(R.id.countless)
clearZero=findViewById(R.id.clearZero)
//获取当前界面的ViewModle
//viewModel=ViewModelProvider(this).get(MainViewModel::class.java)
sp=getPreferences(Context.MODE_PRIVATE)
//从存储的里面取得数据,如果没有数就默认为0
countVal=sp.getInt("countVal",0)
viewModel=ViewModelProvider(this,MainViewModelFactory(countVal)).get(MainViewModel::class.java)
}
//在界面进行暂停时将数据保存
override fun onPause() {
super.onPause()
sp.edit{
putInt("countVal",viewModel.counter.value ?: 0)
}
}
}
3Lifecycles 检测界面的生命周期
在进行网络请求时,如果网络请求还没有结束,但是当前界面已经结束,此时就不需要在进行请求结果的处理,所以需要检测当前界面的进行的生命周期,需要使用到jetpack组件中的lifecycles
使用方法
1自定义观测类,实现DefaultLifecycleObserver接口
package com.example.viewmodletest
import android.util.Log
import androidx.lifecycle.*
class MyObserver:DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
Log.d("MyObserver","activityCreate")
super.onCreate(owner)
}
override fun onStart(owner: LifecycleOwner) {
Log.d("MyObserver","activityStart")
super.onStart(owner)
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
}
override fun onStop(owner: LifecycleOwner) {
Log.d("MyObserver","activityStop")
super.onStop(owner)
}
override fun onDestroy(owner: LifecycleOwner) {
Log.d("MyObserver","activityDestroy")
super.onDestroy(owner)
}
}
2在需要检测的界面里引入当前观测对象
lifecycle.addObserver(MyObserver())
4处理定时任务的WorkManager组件
WorkManager和Service并不相同,也没有直接的联系。
Service是Android系统的四大组件之一,它在没有被销毁的情况下是一直保持在后台运行的。而WorkManager只是一个处理定时任务的工具,它可以保证即使在应用退出甚至手机重启的情况下,之前注册的任务仍然将会得到执行,因此WorkManager很适合用于执行一些定期和服务器进行交互的任务,比如周期性地同步数据
第一步加入依赖
//加入WorkManager的依赖
implementation ‘androidx.work:work-runtime:2.8.1’
第二步编写任务类,完成需要处理的后台任务
import android.content.Context
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
class SimpleWorker(context:Context,params:WorkerParameters) :Worker(context,params){
override fun doWork(): Result {
Log.d("SimpleWorker","do work in SimpleWorker")
return Result.success()
}
}
第三步在程序中实现后台任务的请求绑定
doWorkBtn.setOnClickListener(){
//处理单次请求的结果
request=OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.setInitialDelay(5,TimeUnit.MINUTES)//设置多长时间后开始运行
.addTag("simple")
.build()
//周期性处理请求,但是周期时间要大于15分钟
// val request=PeriodicWorkRequest.Builder(SimpleWorker::class.java,15,TimeUnit.MINUTES).build()
WorkManager.getInstance(this).enqueue(request)
}
//需要后台任务
doWorkBtnCancel.setOnClickListener(){
WorkManager.getInstance(this).cancelAllWorkByTag("simple")
//通过id来结束后台任务
WorkManager.getInstance(this).cancelWorkById(request.id)
}
进阶,需要监听返回结果的任务处理
request=OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.setInitialDelay(5,TimeUnit.MINUTES)//设置多长时间后开始运行
.addTag("simple")
.build()
//周期性处理请求,但是周期时间要大于15分钟
// val request=PeriodicWorkRequest.Builder(SimpleWorker::class.java,15,TimeUnit.MINUTES).build()
WorkManager.getInstance(this)
.getWorkInfoByIdLiveData(request.id)
.observe(this){
workInfo->
if(workInfo.state==WorkInfo.State.SUCCEEDED){
Log.d("MainActivity","do work success")
}else if(workInfo.state==WorkInfo.State.FAILED){
Log.d("MainActivity","do work failed")
}
}
高级使用方法,当有多个后台任务需要进行配合使用时,可以使用链式的办公方法
val requestA=OneTimeWorkRequest.Builder(AAA::class.java)
.build()
val requestB=OneTimeWorkRequest.Builder(BBB::class.java)
.build()
//.enqueue(request)
WorkManager.getInstance(this)
.beginWith(requestA)
.then(requestB)
.enqueue()
5DataBinding 数据绑定简单实现
1在Activity中的数据绑定
databing 主要的目的是提供一种简单的方法将用户界面布局中的视图连接到应用程序代码中存储的数据
首先开启数据绑定功能
//数据绑定
android {
................
buildFeatures{
viewBinding true
dataBinding true
}
}
2准备数据实体
data class User(val name:String,val address:String) {
}
3改变展示界面的布局
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.example.viewmodletest.entity.User" />
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.name}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.address}"/>
</LinearLayout>
</layout>
4主程序中开启界面数据的绑定
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.example.viewmodletest.databinding.UserdatabingdingBinding
import com.example.viewmodletest.entity.User
class UserDataActivity:AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding:UserdatabingdingBinding= DataBindingUtil.setContentView(this,R.layout.userdatabingding)
binding.user= User("小明","中国")
}
}
2在Fragment中的DataBinding+ViewModel
实现一个点击即可将人民币转换成一个美元的小工具
1 先为Fragment绑定一个ViewModel,用来存储Fragment中的数据,来应对屏幕反转等数据问题
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class FragmentViewModel: ViewModel() {
private val rate=0.1453f
val yuan:MutableLiveData<String> = MutableLiveData()
val result:MutableLiveData<Float> = MutableLiveData()
fun convert(){
yuan.let {
if(!it.value.equals("")){
result.value=it.value?.toFloat()?.times(rate)
}else{
result.value=0.0f
}
}
}
}
2在主界面中新建fragmentlayout
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="myDataBinding"
type="com.example.viewmodletest.FragmentViewModel" />
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text='@{myDataBinding.result==0.0 ? "输入要兑换的人民币" : String.valueOf(myDataBinding.result)+"美元"}'
android:layout_gravity="center"/>
<!--双向表达式-->
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={myDataBinding.yuan}"
android:layout_gravity="center"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{()->myDataBinding.convert()}"
android:text="点击将人民币转换成美元" />
</LinearLayout>
</layout>
3为当前fragment写Fragment实现类
package com.example.viewmodletest
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.example.viewmodletest.databinding.MainFragmentBinding
import com.example.viewmodletest.BR.myDataBinding
class ConvertFragment:Fragment() {
companion object{
fun newInstance()=ConvertFragment()
}
private lateinit var viewModel: FragmentViewModel
lateinit var binding:MainFragmentBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding=DataBindingUtil.inflate(inflater,R.layout.main_fragment,container,false)
binding.lifecycleOwner=this
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel=ViewModelProvider(this).get(FragmentViewModel::class.java)
binding.setVariable(myDataBinding,viewModel)
}
}
4在主Activity的xml中加载Fragment
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="user"
type="com.example.viewmodletest.entity.User" />
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.name}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.address}"/>
<!--在里面实现数据的展示和处理-->
<fragment
android:id="@+id/fragment1"
android:name="com.example.viewmodletest.ConvertFragment"
tools:layout="@layout/main_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent">
</fragment>
</LinearLayout>
</layout>
5在主类中启动
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.example.viewmodletest.databinding.UserdatabingdingBinding
import com.example.viewmodletest.entity.User
//希望实现一个人民币兑换美元的程序
class UserDataActivity:AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding:UserdatabingdingBinding= DataBindingUtil.setContentView(this,R.layout.userdatabingding)
//使用此布局去加载fragment
binding.user= User("小明","中国")
}
}
6结果展示
当 屏幕进行反转时,数据不丢失
6Navigation解决Fragment之间的跳转问题
实现Navigation需要三个部分,分别是
Navigation graph:一个包含所有导航相关信息的 XML 资源
NavHostFragment:一种特殊的Fragment,用于承载导航内容的容器
NavController:管理应用导航的对象,实现Fragment之间的跳转等操作
1加入依赖
//加入navigation的依赖
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
2新建navigation资源目录,并在目录里面创建Navigation graph.xml文件
创建出一个navigation目录,然后在此目录中新建一个
新建nav_graph.xml文件后,在Design里进行跳转的Framelayout的切换
命名之后就进行设定,以上同步几次之后就可以生成多个FrameLayout进行操作,创建多个之后可以进行链阶
在左侧就可出现很多个生成的文件
<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/nav_graph"
app:startDestination="@id/fragmentA">
<fragment
android:id="@+id/fragmentA"
android:name="com.example.viewmodletest.FragmentA"
android:label="fragment_a"
tools:layout="@layout/fragment_a" >
<action
android:id="@+id/action_fragmentA_to_fragmentB"
app:destination="@id/fragmentB" />
</fragment>
<fragment
android:id="@+id/fragmentB"
android:name="com.example.viewmodletest.FragmentB"
android:label="fragment_b"
tools:layout="@layout/fragment_b" >
<action
android:id="@+id/action_fragmentB_to_fragmentC"
app:destination="@id/fragmentC" />
</fragment>
<fragment
android:id="@+id/fragmentC"
android:name="com.example.viewmodletest.FragmentC"
android:label="fragment_c"
tools:layout="@layout/fragment_c" />
</navigation>
在主布局中引入上面生成的Framelayout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="主布局头部"
android:background="@color/teal_700"/>
<fragment
android:id="@+id/fragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph"/>
<!--app:defaultNavHost="true"表示点击返回按钮时退回到上一个fragment-->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="主布局尾部"
android:background="@color/teal_700"/>
</LinearLayout>
然后在各个Framelayout中加入其布局即可
例如在fragmenta.xml中加入跳转到FragmentB中的按钮,并且传递参数
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FragmentA">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
<Button
android:id="@+id/toFB"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="点击跳转到FB"/>
</FrameLayout>
package com.example.viewmodletest
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.navigation.Navigation
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [FragmentA.newInstance] factory method to
* create an instance of this fragment.
*/
class FragmentA : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_a, container, false)
}
//界面被创建出来之后的组件绑定
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
var toFB = view.findViewById<Button>(R.id.toFB)
toFB.setOnClickListener {
//it是代表匿名函数的参数
val navControl= Navigation.findNavController(it)
val bundle=Bundle()
bundle.putString("name","你是我的笑啊笑啊")
//数据传递
navControl.navigate(R.id.action_fragmentA_to_fragmentB,bundle)
}
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment FragmentA.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
FragmentA().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
到Fragment中进行接收数据
package com.example.viewmodletest
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.navigation.Navigation
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [FragmentB.newInstance] factory method to
* create an instance of this fragment.
*/
class FragmentB : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString("name")
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_b, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
var textB = view.findViewById<TextView>(R.id.textB)
/*textB.text=savedInstanceState?.getString("name").toString()
Log.d("aa",savedInstanceState?.getString("name").toString())*/
textB.text=param1
Log.d("aa", param1.toString())
var toFC=view.findViewById<Button>(R.id.toFC)
toFC.setOnClickListener {
val navControl= Navigation.findNavController(it)
navControl.navigate(R.id.action_fragmentB_to_fragmentC)
}
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment FragmentB.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
FragmentB().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
结果展示