jetpack compose中实现丝滑的轮播图效果

news2024/11/14 17:35:41
写在前面

最近在翻Jetpack库,发现了DataStore,官方是这么说的:

Jetpack DataStore 是一种数据存储解决方案,允许您使用协议缓冲区存储键值对或类型化对象。DataStore 使用 Kotlin 协程和 Flow 以异步、一致的事务方式存储数据。 如果您目前是使用 SharedPreferences存储数据的,请考虑迁移到 DataStore。

显而易见,在需要存储较小或简单的数据集时,DataStore比起SP更加简单且安全性更高,所以学习使用DataStore是很有价值的。

基础知识
对比

DataStore的存在是为了替代SP,所以为什么可以替代呢?我们看看官方给的图来看看SP相对于DataStore有什么劣势。

  1. 界面线程上的安全调用
    SP的apply() 方法会阻断 fsync() 上的界面线程。每次有服务启动或停止以及每次 activity 在应用中的任何地方启动或停止时,系统都会触发待处理的 fsync() 调用。 界面线程在 apply() 调度的待处理 fsync() 调用上会被阻断,这通常会导致 ANR。
  2. 运行时的异常影响
    SharedPreferences 会将解析错误作为运行时异常抛出
  3. 类型安全
    例如以下代码,我们先写入数据,其中设置key所对应的值为int类型,但在后面使用相同key获取数据时却调用getString()方法,这样程序一旦运行就会报错java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String。这段代码在编译阶段完全正常,但SharedPreferences却无法对这种操作进行规避,需要完全依靠开发者本身去遵循规范。
val sp = getSharedPreferences("test", Context.MODE_PRIVATE)
val edit = sp.edit()
edit.putInt("key", 0);
edit.apply()
val value = sp.getString("key", "")
注意

在开始DataStore的学习前,我们要记住以下几个规则(引用自官方文档)

  1. 请勿在同一进程中为给定文件创建多个 DataStore 实例,否则会破坏所有 DataStore 功能。如果给定文件在同一进程中有多个有效的 DataStore 实例,DataStore 在读取或更新数据时将抛出 IllegalStateException
  2. DataStore 的通用类型必须不可变。更改 DataStore 中使用的类型会导致 DataStore 提供的所有保证都失效,并且可能会造成严重的、难以发现的 bug。强烈建议您使用可保证不可变性、具有简单的 API 且能够高效进行序列化的协议缓冲区。
  3. 切勿对同一个文件混用 SingleProcessDataStoreMultiProcessDataStore。如果您打算从多个进程访问 DataStore,请始终使用 MultiProcessDataStore
准备

我们通过一个计数器例子,在具体的情景中理解和使用DataStore
xml布局如下:

<?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">  
    
    <TextView          
        android:id="@+id/tv"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="0"  
        android:textSize="28sp"  
        app:layout_constraintBottom_toBottomOf="parent"  
        app:layout_constraintEnd_toEndOf="parent"  
        app:layout_constraintStart_toStartOf="parent"  
        app:layout_constraintTop_toTopOf="parent" />  
  
    <com.google.android.material.floatingactionbutton.FloatingActionButton          
        android:id="@+id/fab"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_margin="24dp"  
        android:src="@drawable/ic_baseline_exposure_plus_1_24"  
        app:backgroundTint="@color/black"  
        app:background="@color/black"  
        app:tint="@color/white"  
        app:layout_constraintBottom_toBottomOf="parent"  
        app:layout_constraintEnd_toEndOf="parent"  
        android:contentDescription="add" />  
  
</androidx.constraintlayout.widget.ConstraintLayout>

activity代码如下:

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

        val tv = findViewById<TextView>(R.id.tv)  
        val fab = findViewById<FloatingActionButton>(R.id.fab)  
    }  
}

我们的最终目的是通过不断点击FloatingActionButton使得TextView内的数字不断+1

Preferences DataStore

Preferences DataStore 根据键访问数据。虽然不确保类型安全,但因为无需事先定义架构,Preferences DataStore相对于Proto DataStore更易上手且创建更快。

添加依赖
implementation("androidx.datastore:datastore-preferences:1.0.0")  
创建 Preferences DataStore

我们使用 preferencesDataStore 创建 Preferences DataStore 的实例,通过 preferencesDataStore 委托可确保我们有一个 DataStore 实例在应用中具有该名称。

private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "count-preferences")
定义键

由于 Preferences DataStore 不使用预定义的架构,我们必须使用相应的键类型函数为需要存储在 DataStore<Preferences> 实例中的每个值定义一个键。 官方提供以下方法用于键值的定义,而键的类型可以通过各方法的命名体现。

  • intPreferencesKey()
  • doublePreferencesKey()
  • stringPreferencesKey()
  • booleanPreferencesKey()
  • floatPreferencesKey()
  • longPreferencesKey()
  • stringSetPreferencesKey()

在计数器案例中,TextView展示的是当前计数值,所以我们需要为int值定义一个键,即使用intPreferencesKey()

val COUNTER = intPreferencesKey("counter")
写入数据

在计数器的案例中,我们通过 FloatingActionButton 的点击事件来进行DataStore的写入操作,而Preferences DataStore的写入操作通过edit函数实现。注意edit函数是一个挂起函数,所以我们需要在协程内运行。

fab.setOnClickListener {  
    MainScope().launch {  
        dataStore.edit { preferences -> 
	        // 获取当前存储在dataStore内key为COUNTER的键值
            val currentCounterValue = preferences[COUNTER] ?: 0 
            // 将改键值+1 
            preferences[COUNTER] = currentCounterValue + 1  
        }  
    }
}
读取数据

Preferences DataStore 公开 Flow<Preferences> 中存储的数据,每当偏好设置发生变化时,Flow<Preferences>就会发出该数据。我们使用DataStore.data属性,其返回值是Flow,所以每当我们点击 FloatingActionButton 修改数据,我们能及时接收改变后的数据并修改TextView状态。

MainScope().launch {  
    dataStore.data  
        .map {  
            it[COUNTER] ?: 0  
        }.collect {  
            tv.text = it.toString()  
        }  
}
从 SharedPreferences 迁移到 Preferences DataStore

为了演示怎么迁移,我们重头再来,在准备部分的代码基础上临时创建 SharedPreferences 储存数据。

val sp = getSharedPreferences("test",Context.MODE_PRIVATE)  
val edit = sp.edit()
// 为了验证顺利迁移,我们初始值设置为10
edit.putInt("number",10);  
edit.apply()

运行程序后查看数据已经成功保存在本地

现在可以开始将 SharedPreferences 迁移到 Preferences DataStore 了。因为 DataStore 的存在就是为了替代SP,所以谷歌早提供SharedPreferencesMigration属性用于SP数据迁移。其他代码与前面类似,只需向迁移列表传入 SharedPreferencesMigration属性,其中构造函数第二个参数 sharedPreferencesName 为所创建SP的文件名称,在本例中即为”test“。

private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(  
    name = "preferences-test",  
    // 新增部分
    produceMigrations = { context ->  
        listOf(SharedPreferencesMigration(context, "test"))  
    }  
)

由于键只能从 SharedPreferences 迁移一次,因此在我们迁移完毕后,需要将刚才临时创建的SP相关代码删除,此时的完整代码如下:
有一处需要注意的,在SP文件创建时,我们的key值设置为“number”,迁移后dataStore的key值也会被设置为“number”。所以与前面的例子相比,我们还需要将intPreferencesKey()函数中的key值更改为“number”。

class MainActivity : AppCompatActivity() {  
  
    // 创建:preferencesDataStore 委托可确保我们有一个 DataStore 实例在应用中具有该名称  
    private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(  
        name = "preferences-test",  
        produceMigrations = { context ->  
            listOf(SharedPreferencesMigration(context, "test"))  
        }  
    )  
    private val COUNTER = intPreferencesKey("number")  
	  
    override fun onCreate(savedInstanceState: Bundle?) {  
        super.onCreate(savedInstanceState)  
        setContentView(R.layout.activity_main)  
			  
        val tv = findViewById<TextView>(R.id.tv_test)  
        val fab = findViewById<FloatingActionButton>(R.id.fab)  
			  
        MainScope().launch {  
            dataStore.data  
                .map {  
                    it[COUNTER] ?: 0  
                }.collect {  
                    tv.text = it.toString()  
                }  
        }        
        fab.setOnClickListener {  
            MainScope().launch {  
                dataStore.edit {  
                    val currentCounterValue = it[COUNTER] ?: 0  
                    it[COUNTER] = currentCounterValue + 1  
                }  
            }        
        }    
    }    
}

运行程序后可以看到界面初始数值为10,说明迁移完毕

Proto DataStore

SharedPreferences 和 Preferences DataStore 的一个缺点是无法定义架构,保证不了存取键时使用了正确的数据类型。而 Proto DataStore 利用协议缓冲区来定义架构来解决此问题,确保了类型安全。 协议缓冲区可持久保留强类型数据。与 XML 和其他类似的数据格式相比,协议缓冲区速度更快、规格更小、使用更简单,并且更清楚明了。虽然使用 Proto DataStore 需要学习新的序列化机制,但因为 Proto DataStore 的强大类型优势,所以非常值得我们学习。

添加依赖
  1. 添加协议缓冲区插件
plugins {  
    ……
    id "com.google.protobuf" version "0.8.17"  
}
  1. 添加协议缓冲区和 Proto DataStore 依赖项
dependencies {  
    ……
    implementation "androidx.datastore:datastore:1.0.0"
    implementation "com.google.protobuf:protobuf-javalite:3.18.0"  
}
  1. 配置协议缓冲区
    如果在这步Sync Now报错,先Sync Now前两步再Sync Now对协议缓冲区的配置
protobuf {  
    protoc{  
        // 设置 protoc 的版本号
        artifact = "com.google.protobuf:protoc:3.14.0"  
    }  
    generateProtoTasks {  
        all().each { task ->  
            task.builtins {  
                java {  
                    option 'lite'  
                }  
            }  
        }    
    }
}
定义架构

app/src/main/ 目录下创建proto文件夹,我们将在里面创建proto文件,如图所示

创建count.proto文件,文件和相关注释内容如下:
其中内部类字段类型与Java的对应关系:
string->String,int32->int,int64->long,bool->Boolean,float->float,double->double

// 声明proto的版本
syntax = "proto3";

option java_package = "com.wg.jetpackDemos.dataStore.proto";  // 指定了生成的Java类的包名
option java_multiple_files = true;  // 设置生成的Java类是一个文件还是多个文件

// message 声明的是内部类
message Count {  
  int32 counter = 1;  
}

每当我们创建或者变更proto文件时都需要Rebuild Project,即可生成对应的Java文件

创建 Proto DataStore
  1. 创建序列化器:
    定义一个实现 Serializer<T>的类,其中 T 是 proto 文件中定义的类型。通过实现序列化器告知DataStore如何读取和写入我们在 proto 文件中定义的数据类型,如果磁盘上没有数据,序列化器还会定义默认返回值。
object CountData : Serializer<Count> {  
    override val defaultValue: Count  
        get() = Count.getDefaultInstance()  
		  
    override suspend fun readFrom(input: InputStream): Count {  
        try {  
            return Count.parseFrom(input)  
        }catch (exception:InvalidProtocolBufferException){  
            throw CorruptionException("Cannot read proto.", exception)  
        }  
    }  
		  
    override suspend fun writeTo(t: Count, output: OutputStream)  
        = t.writeTo(output)  
}
  1. 创建 Proto DataStore 实例
    使用 dataStore 所创建的属性委托来创建 DataStore<T> 实例,其中 T 是在 proto 文件中定义的类型。
    fileName参数:告知 DataStore 使用哪个文件存储数据
    serializer 参数:告知 DataStore 在第一步中定义的序列化器类的名称
val Context.counterDataStore : DataStore<Count> by dataStore(  
    fileName = "count.pb",  
    serializer = CountData  
)
写入数据

与Preferences DataStore不同,Proto DataStore使用updatData()函数用于更新存储的对象。

fab.setOnClickListener {  
    MainScope().launch {  
        counterDataStore.updateData { count ->
            count.toBuilder()  
                .setCounter(count.counter + 1)  
                .build()  
        }  
    }
}
读取数据

读取数据则与 Preferences DataStore 类似

MainScope().launch {  
    counterDataStore.data.collect {  count ->
        tv.text = count.counter.toString()  
    }  
}
从 SharedPreferences 迁移到 Proto DataStore

前期准备与上面“从 SharedPreferences 迁移到 Preferences DataStore”部分相同,如果有跳过 Preferences DataStore 部分直接看 Proto DataStore 的朋友需要往回翻看一下。
同迁移到 Preferences DataStore 的思路一样,我们只需在DataStore构造器中向迁移列表传入 SharedPreferencesMigration属性。这里需要注意的是,SharedPreferencesMigration的包为androidx.datastore.migrations.SharedPreferencesMigration,我那时候因为导错包找了好久的bug,请以我为戒。

val Context.counterDataStore : DataStore<Count> by dataStore(  
    fileName = "count.pb",  
    serializer = CountData,  
    produceMigrations = { context ->  
        listOf(  
            SharedPreferencesMigration(context,"test"){  
                sharedPreferencesView, counter ->  
                // 获取 SharedPreferences 的数据  
                val count = sharedPreferencesView.getInt("number",0)  
                counter.toBuilder().setCounter(count).build()  
            }  
        )  
    }  
)

在运行前记得删掉SP相关代码,迁移完毕结果如下:

Android 学习笔录

Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Android 性能优化篇:https://qr18.cn/FVlo89
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android 音视频篇:https://qr18.cn/Ei3VPD
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1237895.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

18张值得收藏的高清卫星影像

这里分享的18张高清卫星影像&#xff0c;由吉林一号卫星拍摄。 原图来自长光卫星嘉宾在直播中分享的PPT演示文档。 18张高清卫星影像 吉林一号高分04A星&#xff0c;于2022年05月21日拍摄的北京紫禁城高清卫星影像。 北京紫禁城 云南昆明滇池国际会展中心高清卫星影像&…

劲松HPV防治诊疗中心提醒:做完HPV检查后,需留意这些事项!

在接受HPV检查后&#xff0c;有一些注意事项需要您注意。首先&#xff0c;要遵循医生的建议&#xff0c;并按照医生的指示进行后续治疗和随访。 其次&#xff0c;检查后可能会有些不适感&#xff0c;这是正常的现象&#xff0c;不必过于担心。但是&#xff0c;如果不适感持续加…

枚举 蓝桥oj DNA序列修正

题目详情&#xff1a; 简单翻译&#xff1a; 主要思路&#xff1a; 1 本题采用贪心思路&#xff0c;要使调整次数最少&#xff0c;就是尽量交换两个碱基对&#xff0c;而不是单个替换&#xff0c;因为本题已经说明只能每个碱基对只能交换一次&#xff0c;所以不考虑A与B交换再…

Java Swing管理系统万能模板 课程设计素材

JavaSwing管理系统万能模板 视频教程&#xff1a; 【课程设计】2小时学会JavaSwing课程设计-万能模板-图书管理系统-[你的课程我设计] 万能模板是用Java Swing开发的&#xff0c;包含管理系统常用的多角色登录、数据查询、添加、修改、删除。常用的管理系统都可以使用万能模板…

跨越行业边界,CodeMeter护航AI领域安全与合规

在人工智能&#xff08;AI&#xff09;技术如ChatGPT的推动下&#xff0c;工业视觉、医疗诊断和智能驾驶等领域正在经历重大变革。这些技术不仅扩大了应用范围&#xff0c;也带来了数据安全、软件授权保护和合规性等新挑战。 AI工业视觉正在推动制造和自动化的快速发展&#x…

格式化名称节点,启动Hadoop

1.循环删除hadoop目录下的tmp文件&#xff0c;记住在hadoop目录下进行 rm tmp -rf 使用上述命令&#xff0c;hadoop目录下为&#xff1a; 2.格式化名称节点 # 格式化名称节点 ./bin/hdfs namenode -format 3.启动所有节点 ./sbin/start-all.sh 效果图&#xff1a; 4.查看节…

【从入门到起飞】JavaSE—多线程(2)(生命周期,线程安全问题,同步方法)

&#x1f38a;专栏【JavaSE】 &#x1f354;喜欢的诗句&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。 &#x1f386;音乐分享【如愿】 &#x1f384;欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f354;生命周期&#x1f384;线程的安全问题&#…

代码随想录刷题】Day17 二叉树04

文章目录 1.【110】平衡二叉树&#xff08;优先掌握递归&#xff09;1.1 题目描述1.2 解题思路1.3 java代码实现 2.【257】二叉树的所有路径&#xff08;优先掌握递归&#xff09;2.1 题目描述2.2 解题思路2.3 java代码实现 3.【404】左叶子之和&#xff08;优先掌握递归&#…

从零开始安装并运行YOLOv5

从零开始安装并运行YOLOv5 该文主要实现用YOLOv5的基准检测为自己的视频片段渲染对象检测结果和边界框&#xff0c;本文大部分都是实操&#xff0c;帮助大家快速上手。 什么是YOLOv5&#xff1f; ​ yolo是一种用于对象检测的最先进的机器学习模型&#xff0c;yolo有不同的版…

简于外 强于内,联想全新ThinkCentre M90a Pro Gen4以强劲性能开启商用新体验

近日&#xff0c;联想发布了最新一代商用台式一体机联想ThinkCentre M90a Pro Gen4。作为联想ThinkCentre M大师系列的旗舰产品&#xff0c;其配备了优质的显示屏&#xff0c;拥有强大的性能和稳定安全的特性&#xff0c;能够满足多样的工作场合&#xff0c;为商用一体机的行业…

3D电路板在线渲染案例

从概念上讲,这是有道理的,因为PCB印制电路板上的走线从一个连接到下一个连接的路线基本上是平面的。 然而,我们生活在一个 3 维世界中,能够以这种方式可视化电路以及相应的组件,对于设计过程很有帮助。本文将介绍KiCad中基本的3D查看功能,以及如何使用NSDT 3DConvert在线…

连线长光卫星:吉林一号的在线产品与生态体系!

我们在《连线长光卫星&#xff1a;探索卫星应用的更多可能&#xff01;》一文中&#xff0c;通过直播连线嘉宾的分享&#xff0c;让大家了解到了长光卫星的生产基地、三次技术飞跃、亚米级影像产品、150公里大幅宽卫星、卫星在灾害监测及经济分析等多个场景中的应用。 这里我们…

探索分销小程序商城开发和搭建

在数字化时代&#xff0c;小程序已经成为了企业营销的重要工具。其中&#xff0c;分销小程序商城以其独特的优势&#xff0c;吸引了众多企业的关注。下面给大家介绍如何开发分销小程序商城&#xff0c;以及小程序分销搭建的流程。 首先&#xff0c;我们需要明确什么是分销小程…

【计算机网络】多路复用的三种方案

文章目录 1. selectselect函数select的工作特性select的缺点 2. pollpoll函数poll与select的对比 3. epollepoll的三个接口epoll的工作原理epoll的优点LT和ET模式epoll的应用场景 &#x1f50e;Linux提供三种不同的多路转接&#xff08;又称多路复用&#xff09;的方案&#xf…

禁止安装新软件怎么设置(超详细图文介绍)

很多公司的网管向我们反应&#xff0c;总是有员工随意下载软件&#xff0c;并且不去正规网站、正规官网下载&#xff0c;导致公司的电脑总是又卡又慢&#xff0c;网管的工作很难开展。 此时就需要对公司安装软件的情况&#xff0c;进行统一管控了。 方法一&#xff1a;适合个人…

CodeWhisperer 一款好玩的 AI 插件

忙里抽闲&#xff0c;今天试了试 CodeWhisperer 这款插件&#xff0c;我是在 IDEA 中做的测试&#xff0c;下面是我的一些使用感想&#xff1a; 安装 CodeWhisperer 插件&#xff1a;在 IntelliJ IDEA 中&#xff0c;可以通过插件管理器安装 CodeWhisperer 插件&#xff0c;然…

UE 进阶篇一:动画系统

导语: 下面的动画部分功能比较全,可以参考这种实现方式,根据自己项目的颗粒度选择部分功能参考,我们商业项目动画部分也是这么实现的。 最后实现的效果如下: 最终效果 目录: ------------------------------------------- 文末有视频教程/工程地址链接 -------------…

log4j2远程代码执行漏洞原理与漏洞复现(基于vulhub,保姆级的详细教程)

漏洞原理 啥是log4j2? log4j2是apache下的java应用常见的开源日志库&#xff0c;是一个就Java的日志记录工具。在log4j框架的基础上进行了改进&#xff0c;并引入了丰富的特性&#xff0c;可以控制日志信息输送的目的地为控制台、文件、GUI组建等&#xff0c;被应用于业务系…

Pycharm run 输出界面控制一行能够输出的元素个数

Pycharm run 输出界面控制一行能够输出的元素个数 今天遇到了一个问题&#xff0c;当我们在 Pycharm 中打印输出数组时&#xff0c;如果数组一行的元素个数过多&#xff0c;那么我们在打印时就会出现以下问题。 代码如下&#xff1a; import numpy as npx np.array([[0., 0.7…

jQuery【菜单功能、淡入淡出轮播图(上)、淡入淡出轮播图(下)、折叠面板】(五)-全面详解(学习总结---从入门到深化)

目录 菜单功能 淡入淡出轮播图(上) 淡入淡出轮播图(下) 折叠面板 菜单功能 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><…