[Kotlin]引导页

使用Kotlin + Jetpack Compose创建一个左右滑动的引导页, 效果如图.

1.添加依赖项

androidx.compose.ui最新版本查询:https://maven.google.com/web/index.html

com.google.accompanist:accompanist-pager最新版本查询:https://central.sonatype.com/

确保在 build.gradle (Module: app) 文件中添加:

dependencies {implementation("androidx.compose.ui:ui:1.7.0-alpha06")implementation("com.google.accompanist:accompanist-pager:0.35.0-alpha")
}

2.定义引导页

  • HorizontalPager 是一个实现水平滑动页面的组件,常用于实现引导页。它是通过Pager库提供的,支持滑动动画和状态保持。
  • rememberPagerState 是用于记忆并管理HorizontalPager的状态,例如当前页面和总页面数。
  • rememberCoroutineScope 用于创建一个协程作用域,允许在Compose函数外异步执行任务(例如页面滚动)。
package com.randomdt.www.main.guideimport androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.randomdt.www.R
import com.randomdt.www.support.data.PrefKey
import com.randomdt.www.support.data.PrefsManager
import com.randomdt.www.ui.theme.customScheme
import kotlinx.coroutines.launch@OptIn(ExperimentalFoundationApi::class)
@Composable
fun GuideScreen(onGuideComplete: (Boolean) -> Unit) {val pages = listOf(GuidePage("Enhance your video recording with smooth script scrolling.", R.drawable.icon_guide1),GuidePage("Personalize settings to meet your recording needs.", R.drawable.icon_guide2),GuidePage("Intelligent scrolling for effortless recording control.", R.drawable.icon_guide3),GuidePage("Subscribe to the premium version and unlock additional features.", R.drawable.icon_guide4))val pagerState = rememberPagerState(pageCount = { pages.count() })val scope = rememberCoroutineScope()Box(modifier = Modifier.fillMaxSize().background(color = MaterialTheme.colorScheme.background)){HorizontalPager(state = pagerState,modifier = Modifier.matchParentSize()  // Use matchParentSize instead) { page ->GuidePageContent(page = pages[page], modifier = Modifier.fillMaxSize())}val isLast = pagerState.currentPage == pages.size - 1Column(modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp),verticalArrangement = Arrangement.Bottom) {if (isLast) {Text("3 Days Trial, \$4.99/week, cancel anytime",fontSize = 14.sp,fontWeight = FontWeight.Normal,color = MaterialTheme.customScheme.text_aux99,textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth()  // 使宽度充满屏幕.padding(horizontal = 16.dp)  // 水平填充.padding(bottom = 16.dp)  // 与按钮之间的空隙)}// 渐变色定义val gradient = Brush.horizontalGradient(colors = listOf(MaterialTheme.customScheme.gradient_start_color,  // 渐变起始颜色MaterialTheme.customScheme.gradient_end_color  // 渐变结束颜色))// Next/Subscribe按钮Button(onClick = {if (pagerState.currentPage < pages.size - 1) {scope.launch { pagerState.animateScrollToPage(pagerState.currentPage + 1) }} else {// Navigate to Home ScreengoHome(onGuideComplete)}},colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent), // 设置背景透明contentPadding = PaddingValues(0.dp),  // 移除内部填充border = BorderStroke(1.dp, Color.White), // 设置按钮的边框和背景shape = RoundedCornerShape(25.dp),  // 按钮圆角设置. Button 的 shape 只影响按钮本身的边界形状,而不会应用到渐变色背景上。modifier = Modifier.fillMaxWidth() // 使宽度充满屏幕.height(50.dp).background(gradient,shape = RoundedCornerShape(25.dp)), // 方式一: 添加渐变色背景, 已经为渐变背景导角) {Text(if (pagerState.currentPage == pages.size - 1) "Subscribe" else "Next",fontSize = 17.sp,fontWeight = FontWeight.Bold)/*// 方式二: 设置Button渐变色Box(modifier = Modifier.fillMaxSize().background(gradient, shape = RoundedCornerShape(25.dp))) {Text(if (pagerState.currentPage == pages.size - 1) "Subscribe" else "Next",modifier = Modifier.align(Alignment.Center))}*/}Box(modifier = Modifier.fillMaxWidth().height(100.dp).alpha(if (isLast) 1f else 0f)) {// Restore PurchasesButton(onClick = {},colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent), // 设置背景透明contentPadding = PaddingValues(0.dp),  // 移除内部填充modifier = Modifier.height(40.dp)) {Text("Restore Purchases",fontSize = 13.sp,fontWeight = FontWeight.Normal,style = TextStyle(textDecoration = TextDecoration.Underline) // 下划线)}// Privacy PolicyButton(onClick = {},colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent), // 设置背景透明contentPadding = PaddingValues(0.dp),  // 移除内部填充modifier = Modifier.align(Alignment.TopEnd).padding(end = 95.dp).height(40.dp)) {Text("Privacy Policy",fontSize = 13.sp,fontWeight = FontWeight.Normal,style = TextStyle(textDecoration = TextDecoration.Underline) // 下划线)}// Terms of UseButton(onClick = {},colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent), // 设置背景透明contentPadding = PaddingValues(0.dp),  // 移除内部填充modifier = Modifier.align(Alignment.TopEnd).height(40.dp)) {Text("Terms of Use",fontSize = 13.sp,fontWeight = FontWeight.Normal,style = TextStyle(textDecoration = TextDecoration.Underline) // 下划线)}//val scrollState = rememberScrollState()Box(modifier = Modifier.fillMaxWidth().padding(top = 40.dp)) {// 可滚动的详细文本视图Text(text = "This subscription automatically renews unless you cancel at least 24 hours before the end of the current subscription period. Your account will be charged for renewal within 24-hours prior to the end of the current subscription period. You can manage your subscription and auto-renewal in your Google Play account settings.",fontSize = 13.sp,color = MaterialTheme.customScheme.text_aux99,fontWeight = FontWeight.Normal,lineHeight = 20.sp,  // 设置行间距为20spmodifier = Modifier.fillMaxWidth().verticalScroll(scrollState)//.heightIn(max = 100.dp)  // 设置最大高度以限制视图高度.padding(bottom = 10.dp))}}}if (isLast) {// 跳过按钮Button(onClick = {// Navigate to Home ScreengoHome(onGuideComplete)},colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent), // 设置背景透明contentPadding = PaddingValues(0.dp),  // 移除内部填充modifier = Modifier.align(Alignment.TopStart).size(60.dp).padding(start = 8.dp, top = 8.dp)) {Image(painter = painterResource(R.drawable.icon_alert_close),contentDescription = "",)}}}}private fun goHome(onGuideComplete: (Boolean) -> Unit) {PrefsManager.set(PrefKey.IS_DID_GUIDE, true)onGuideComplete(true)
}@Composable
fun GuidePageContent(page: GuidePage, modifier: Modifier = Modifier) {Column(modifier = modifier) {Image(painter = painterResource(id = page.imageRes),contentDescription = null,modifier = Modifier.fillMaxWidth() // 填充最大宽度.aspectRatio(1167 / 1320f) // 设置宽高比例,例如 16:9 的比例为 1.77)Text(text = page.description,modifier = Modifier.padding(horizontal = 16.dp) // 设置水平间距.align(Alignment.CenterHorizontally), // 居中style = TextStyle(fontSize = 20.sp,textAlign = TextAlign.Center, // 让换行的文案也居中对齐)) // 高度根据内容自适应}
}// imageRes 是一个整数 (int),通常在 Android 开发中,这种整数类型用来代表资源文件(如图片)的 ID。
data class GuidePage(val description: String, val imageRes: Int)

3.定义PrefsManager

package com.randomdt.www.support.dataimport android.content.Context
import android.content.SharedPreferences
import android.util.Logobject PrefsManager {private lateinit var sharedPreferences: SharedPreferencesfun init(context: Context) {sharedPreferences = context.getSharedPreferences("AppPreferences", Context.MODE_PRIVATE)}fun <T> get(prefKey: PrefKey): T {val defaultValue: T = PrefDefaults.getDefaultValue(prefKey)return when (defaultValue) {is Boolean -> sharedPreferences.getBoolean(prefKey.key, defaultValue) as? T ?: defaultValueis Int -> sharedPreferences.getInt(prefKey.key, defaultValue) as? T ?: defaultValueis String -> sharedPreferences.getString(prefKey.key, defaultValue)  as? T ?: defaultValueelse -> {Log.w("SharedPreferences", "Unsupported type for SharedPreferences.get")defaultValue}}}fun <T> set(prefKey: PrefKey, value: T) {with(sharedPreferences.edit()) {when (value) {is Boolean -> putBoolean(prefKey.key, value)is Int -> putInt(prefKey.key, value)is String -> putString(prefKey.key, value)else -> Log.w("SharedPreferences", "Unsupported type for SharedPreferences.set")}apply()}}
}/// 让 PrefKey 枚举仅包含用户定义的键(key)
enum class PrefKey(val key: String) {IS_DID_GUIDE("isDidGuide"),USER_AGE("userAge"),USER_NAME("userName");
}/// 管理默认值和类型
object PrefDefaults {private val defaultValues = mapOf<PrefKey, Any>(PrefKey.IS_DID_GUIDE to false,PrefKey.USER_AGE to 18,PrefKey.USER_NAME to "John Doe")@Suppress("UNCHECKED_CAST")fun <T> getDefaultValue(prefKey: PrefKey): T = defaultValues[prefKey] as T
}/*
// 初始化(通常在应用启动时进行)
PrefsManager.init(context)// 存储数据
PrefsManager.set(PrefKey.IS_LOGGED_IN, true)
PrefsManager.set(PrefKey.USER_AGE, 30)
PrefsManager.set(PrefKey.USER_NAME, "Alice")// 读取数据
val isLoggedIn: Boolean = PrefsManager.get(PrefKey.IS_LOGGED_IN)
val userAge: Int = PrefsManager.get(PrefKey.USER_AGE)
val userName: String = PrefsManager.get(PrefKey.USER_NAME)
*/

4.引导页进入/离开

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)PrefsManager.init(this)setContent {RandomdtTheme {// A surface container using the 'background' color from the themeSurface(modifier = Modifier.fillMaxSize(),color = MaterialTheme.colorScheme.background) {MainContent()}}}}
}@Composable
fun MainContent() {val isDidGuideState = remember { mutableStateOf(PrefsManager.get<Boolean>(PrefKey.IS_DID_GUIDE)) }if (isDidGuideState.value) {Greeting("Android")} else {GuideScreen { isDidGuideCompleted ->isDidGuideState.value = isDidGuideCompleted}}
}

TO

HorizontalPager用法:https://juejin.cn/post/6978831090693701639

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言——内存函数的实现与模拟

1. memcpy 函数 与strcpy 函数类似 1.头文件 <string.h> 2.基本格式 • 函数memcpy从source的位置开始向后复制num个 字节 的数据到destination指向的内存位置。 • 这个函数在遇到 \0 的时候并不会停下来。 • 如果source和destination有任何的重叠&#xff0…

数据分析_数据分析思维(1)

数据分析_数据分析思维(1) 这篇文章具体的给大家介绍数据分析中最为核心的技术之一: 数据分析思维的相关内容。 一、数据分析的三种核心思维 作为新手数据分析师或数据运营, 在面对数据异常的时候, 好多小伙伴都会出现: “好像是A引起的”, “好像也和B渠道有关”, “也可能…

3D MINS 多模态影像导航系统

3D MINS多模态影像导航系统&#xff08;Multimodal Image Navigation System&#xff09;是SunyaTech研发的建立在DICOM&#xff08;Digital Imaging and Communications in Medicine&#xff09;图像基础之上的多模态影像导航系统&#xff0c;集二维影像PACS管理、三维影像层级…

李宏毅2022机器学习/深度学习 个人笔记(3)

本系列用于推导、记录该系列视频中本人不熟悉、或认为有价值的知识点 本篇记录代码效果不佳时应该怎么做 如下图所示&#xff1a; 接下来探讨&#xff0c;当optimization不佳的时候&#xff0c;如何判断是遇到了鞍点还是遇到了局部最小值点&#xff1f;可以通过多元函数的泰勒…

Redis - Redisson tryLock 函数参数分析

这里有三个参数&#xff1a; waitTime&#xff1a;等待时间leaseTime&#xff1a;超时施放时间TimeUnit&#xff1a;时间单位 等待时间 如果 ABC… 多个线程去抢夺一把锁&#xff0c;A 成功了&#xff0c;如果设置的是 -1&#xff0c;那么 BCD... 就不等待&#xff0c;直接返…

高通发布电脑CPU,比英特尔Ultra9领先51%

要说2024年最热门的关键词&#xff0c;那肯定非 AI 莫属&#xff0c;当前 AI 已经开始深入各行各业&#xff0c;AI 电视、AI 手机、AI 车机、AI 家电&#xff0c;以及 AI PC ,这些都意味着 AI 将对各个行业带来的新风向和不小的冲击。 2024 年了&#xff0c;PC 处理器还能卷出什…

验证线缆(汽车线束、网线、多芯线)破损或断开与正常线缆的区别在哪里?依AEM CV-100 k50测试仪

工厂产线生产的线缆&#xff08;汽车线束、网线、多芯线&#xff09;做成成品&#xff0c;即2端都安装好了模块。在这种情况下如何快速的判定此条线缆是合格的呢&#xff0c;此处的合格为物理层面上的合格&#xff08;不会出现开路、短路&#xff09;&#xff0c;也就是最基本保…

第66天:API攻防-接口安全阿里云KEYPostmanDVWSXXE鉴权泄漏

案例一&#xff1a;安全问题-Dvws泄漏&鉴权&XXE 靶场地址&#xff1a;https://github.com/snoopysecurity/dvws-node 利用docker命令去启动 首先先注册一个账户 注册后登录点击admin area 发现点不进去 这里把bp打开但是不抓包&#xff0c;只做流量转发&#xff0c;进…

JavaEE初阶——多线程(六)——线程池

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 小比特 大梦想 此篇文章与大家分享多线程的第六篇文章,关于线程池 如果有不足的或者错误的请您指出! 目录 3.线程池3.1标准库的线程池3.2 标准库自己提供的几个工厂类3.3自己实现一个线程池完成大体框架接下来完…

单细胞+RIP-seq项目文章| Cell ReportshnRNPU蛋白在小鼠精原干细胞池建立的关键作用

精原干细胞&#xff08;SSCs&#xff09;是负责精子发生的干细胞&#xff0c;具有自我更新和分化产生功能性精子的能力。SSCs的持续再生对于维持雄性生育力至关重要。然而&#xff0c;SSC池的发育起源尚不清楚。在哺乳动物中&#xff0c;SSCs源自名为 prospermatogonia&#xf…

众筹商城源码 众筹农业平台 农业乘科技富强之路 线上农业众筹 养殖系统 种植系统源码

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 前端是编译后的&#xff0c;后端PHP&#xff0c;带商城&#xff0c;详情看下图 特点和功能&#xff1a; 用户管理&#xff1a;包括注册、登录、个人信息管理等。 项目创建与展示&…

报错:测试报错postman(测试接口)

报错如下 c.e.exception.GlobalExceptionHandler : 异常信息&#xff1a; Content type multipart/form-data;boundary--------------------------952399813172082093419475;charsetUTF-8 not supported 解决&#xff1a; 异常信息 Content type multipart/form-data;boundary…