[Android]引导页

使用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/641353.html

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

相关文章

Android studio添加aidl文件时,添加按钮为黑色不可点击添加解决办法

在android studio添加aidl文件时出现下面这个情况只需要在模块的build.gradle.kts文件中的android中添加以下代码即可添加aidl android {// 设置 AIDL 支持buildFeatures {aidl true} }

使用大卫的k8s监控面板(k8s+prometheus+grafana)

问题 书接上回&#xff0c;对EKS&#xff08;AWS云k8s&#xff09;启用AMP&#xff08;AWS云Prometheus&#xff09;监控AMG(AWS云 grafana)&#xff0c;上次我们只是配通了EKSAMPAMG的监控路径。这次使用一位大卫老师的grafana的面板&#xff0c;具体地址如下&#xff1a; ht…

实时数仓选型

实时数仓选型 实时数仓选型第一版实时数仓选型第二版 实时数仓选型第一版 实时数仓分层: 计算框架:Flink;存储框架:消息队列(可以实时读取&可以实时写入)ODS:Kafka 使用场景:每过来一条数据,读取到并加工处理DIM: HBase 使用场景:事实表会根据主键获取一行维表数据(1.永…

JavaWeb开发06-原理-Spring配置优先级-Bean管理-SpringBoot原理-Maven继承和聚合-私服

一、Spring配置优先级 不同配置文件&#xff0c;配置同一个属性谁有效 properties>yml>yaml 命令行参数>Java系统属性 项目打包后要改变属性&#xff1a; 红色是Java系统属性&#xff0c;绿色是命令行参数 ‘ 二、Bean管理 1.获取bean 获取IOC容器&#xff1a;ap…

Oracle EBS Interface/API(54)- GL日记账审批

背景: 客户化创建薪酬凭证或者银企付款入账日记账以后,用户希望自动提交审批流程,无需到系统标准功能点击审批,减少用户操作。 快速参考 参考点内容功能导航N: GL->日记账->输入并发请求None基表GL.GL_JE_BATCHESAPI参考下面介绍错误信息表None接口FormNone接口Reque…

记录一下flume中因为taildir_position.json因位置不对导致数据无法从kafka被采到hdfs上的问题

【背景说明】 我需要用flume将kafka上的数据采集到hdfs上&#xff0c;发现数据怎么到不了hdfs。 【问题排查】 1.kafka上已有相应的数据 2.我的flume配置文档&#xff08;没问题&#xff09;&#xff0c; 3.时间拦截器&#xff08;没问题&#xff09;&#xff0c; 4.JSONObje…

​可视化大屏C位图:​总揽全局的行政区划图

在可视化大屏设计中&#xff0c;放置行政区划图可以起到以下几个作用&#xff1a; 地理位置展示&#xff1a;行政区划图可以直观地展示各个区域的地理位置和边界&#xff0c;帮助用户了解不同区域的空间分布。这对于多地区或分布式生产的企业来说尤为重要&#xff0c;可以帮助…

git merge 和 git rebese的区别

git merge 和 git rebese的区别 拉取分支和合并代码会涉及两种选择&#xff0c;git merge 和 git rebase&#xff1a; rebase&#xff1a;变基&#xff0c;会有一个干净的分支&#xff0c;但是对于记录来源不够清楚merge&#xff1a;合并&#xff0c;git 分支看起来比较混乱&…

go语言实现心跳机制样例

1、服务端代码&#xff1a; package mainimport ("fmt""net" )func handleClient(conn net.Conn) {defer conn.Close()fmt.Println("Client connected:", conn.RemoteAddr())// 读取客户端的数据buffer : make([]byte, 1024)for {n, err : conn…

【linux】Bad owner or permissions on

在root用户下执行scp操作向另外一个节点拷贝文件时发生了如下错误&#xff1a; Bad owner or permissions on /etc/crypto-policies/back-ends/openssh.config 我们查看他的权限时发现它所链接的文件权限为777 解决方法就是&#xff1a; chmod 600 /etc/crypto-policies/back-e…

基于51单片机的宠物自动喂食语音播报,有实物

1. 51仿真&#xff1a; LCD第一屏显示食物重量&#xff0c;当前时间&#xff0c;温湿度。第二屏显示喂食时间&#xff0c;第三屏显示喂食重量。可通过点击查看喂食时间翻转屏幕显示。 点击查看喂食时间后&#xff0c;显示喂食时间&#xff0c;可以设置三个时间&#xff0c;再点…

zigbee cc2530的室内/矿井等定位系统RSSI原理

1. 定位节点软件设计流程 2. 硬件设计 cc2530 最小系统 3. 上位机 c# 设计上位机&#xff0c;通过串口连接协调器节点&#xff0c;传输数据到pc上位机&#xff0c;显示节点坐标信息 4. 实物效果 需要4个节点&#xff0c;其中一个协调器&#xff0c;两个路由器作为参考节点&a…