[Kotlin]引导页

news/2024/11/30 8:34:13/

使用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.ppmy.cn/news/1431237.html

相关文章

[Linux][多线程][二][线程互斥][互斥量][可重入VS线程安全][常见锁概念]

目录 1.线程互斥1.互斥相关背景概念2.多个线程并发的操作共享变量&#xff0c;会带来一些问题3.互斥量mutex 2.互斥量的接口1.初始化互斥量2.销毁互斥量3.加锁4.解锁5.使用 -- 改善上面代码 3.互斥量实现原理探究1.加锁是如何保证原子性的&#xff1f;2.如何保证锁是原子性的&a…

Unity的Animator Animation的使用攻略

Animator 动画控制器 Animation 动画 动画片段 .anin 一、创建Animator 创建动画控制器 模型添加Animator组件 把控制器和模型绑定 二、创建动画 进入动画界面 创建动画片段anin 动画窗口分析 制作动画 点击录制&#xff0c; 移动子对象&#xff0c;在视窗 通过移动线来编辑关…

Linux使用Docker部署DashDot访问本地服务器面板

文章目录 1. 本地环境检查1.1 安装docker1.2 下载Dashdot镜像 2. 部署DashDot应用 本篇文章我们将使用Docker在本地部署DashDot服务器仪表盘&#xff0c;并且结合cpolar内网穿透工具可以实现公网实时监测服务器系统、处理器、内存、存储、网络、显卡等&#xff0c;并且拥有API接…

项目大集成

一 keeplived 高可用 192.168.11.11nginx keeplived192.168.11.12nginx keeplived 两台均编译安装服务器 1 主服务器修改文件&#xff1a; 2 备服务器修改文本 scp keepalived.conf 192.168.11.12:/etc/keepalived/ 3 给主服务器添加虚拟ip ifconfig ens33:0 192.168…

从 Elastic 的 Go APM 代理迁移到 OpenTelemetry Go SDK

作者&#xff1a;来自 Elastic Damien Mathieu 正如我们之前所分享的&#xff0c;Elastic 致力于帮助 OpenTelemetry&#xff08;OTel&#xff09;取得成功&#xff0c;这意味着在某些情况下构建语言 SDK 的分发版本。 Elastic 在观察性和安全数据收集方面战略性地选择了 OTel…

【机器学习】特征筛选实例与代码详解

机器学习中的特征筛选 一、特征筛选的重要性与基本概念二、特征筛选的方法与实践1. 基于统计的特征筛选2. 基于模型的特征筛选3. 嵌入式特征筛选 三、总结与展望 在机器学习领域&#xff0c;特征筛选作为预处理步骤&#xff0c;对于提高模型性能、简化模型结构以及增强模型解释…

[leetcode] 1071. 字符串的最大公因子

对于字符串 s 和 t&#xff0c;只有在 s t t t … t t&#xff08;t 自身连接 1 次或多次&#xff09;时&#xff0c;我们才认定 “t 能除尽 s”。 给定两个字符串 str1 和 str2 。返回 最长字符串 x&#xff0c;要求满足 x 能除尽 str1 且 x 能除尽 str2 。 示例 1&am…

积极探索新质生产力,九河云携手华为云技术交流引领数智跃迁

4月18日&#xff0c;九河云携手华为云举办了华为云SA技术培训会议&#xff0c;培训邀请到华为云技术人员作为主讲人&#xff0c;通过理论讲解与案例结合的方式&#xff0c;围绕ECS和EBS之间的联动&#xff0c;调优和数据保护等方面展开&#xff0c;深入浅出地讲解了基于EBS部署…