使用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.guide
import 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 - 1
Column(
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 Screen
goHome(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 Purchases
Button(
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 Policy
Button(
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 Use
Button(
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, // 设置行间距为20sp
modifier = Modifier
.fillMaxWidth()
.verticalScroll(scrollState)
//.heightIn(max = 100.dp) // 设置最大高度以限制视图高度
.padding(bottom = 10.dp)
)
}
}
}
if (isLast) {
// 跳过按钮
Button(
onClick = {
// Navigate to Home Screen
goHome(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.data
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
object PrefsManager {
private lateinit var sharedPreferences: SharedPreferences
fun 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 ?: defaultValue
is Int -> sharedPreferences.getInt(prefKey.key, defaultValue) as? T ?: defaultValue
is String -> sharedPreferences.getString(prefKey.key, defaultValue) as? T ?: defaultValue
else -> {
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 theme
Surface(
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