软件主页背景动画,存在两个问题:1.只有位置改变,没有图片索引改变;2.布局上下比例还要调整
@@ -3,11 +3,16 @@ package com.aiosman.riderpro.ui.login
|
||||
import android.content.ContentValues.TAG
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -16,20 +21,39 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import com.aiosman.riderpro.AppState
|
||||
import com.aiosman.riderpro.AppStore
|
||||
import com.aiosman.riderpro.LocalNavController
|
||||
@@ -42,6 +66,8 @@ import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
||||
import com.aiosman.riderpro.utils.GoogleLogin
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
@@ -112,46 +138,19 @@ fun LoginPage() {
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().background(Color.White)
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.White)
|
||||
) {
|
||||
// bg image
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(900.dp).offset(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.offset(
|
||||
y = (-72).dp
|
||||
),
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.rider_pro_login_bg),
|
||||
contentDescription = "Login Background",
|
||||
modifier = Modifier.fillMaxWidth().fillMaxHeight(),
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop,
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.background(Color.White.copy(alpha = 0.8f)),
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(300.dp)
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
Color.White
|
||||
),
|
||||
startY = 0f,
|
||||
endY = 300f
|
||||
)
|
||||
)
|
||||
)
|
||||
MovingImageWall()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@@ -230,7 +229,209 @@ fun LoginPage() {
|
||||
Spacer(modifier = Modifier.height(120.dp))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun MovingImageWall() {
|
||||
val imageList1 = remember { mutableStateListOf(R.drawable.wall_1_1, R.drawable.wall_1_2, R.drawable.wall_1_3) }
|
||||
val imageList2 = remember { mutableStateListOf(R.drawable.wall_2_1, R.drawable.wall_2_2, R.drawable.wall_2_3) }
|
||||
val imageList3 = remember { mutableStateListOf(R.drawable.wall_3_1, R.drawable.wall_3_2, R.drawable.wall_3_3) }
|
||||
|
||||
var offset1 by remember { mutableFloatStateOf(0f) }
|
||||
var offset2 by remember { mutableFloatStateOf(0f) }
|
||||
var offset3 by remember { mutableFloatStateOf(0f) }
|
||||
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val coroutineScope = rememberCoroutineScope() // 使用 rememberCoroutineScope
|
||||
LaunchedEffect(key1 = lifecycleOwner) {
|
||||
lifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver {
|
||||
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_RESUME -> {
|
||||
coroutineScope.launch { // 在 coroutineScope 中启动协程
|
||||
animateImageWall(imageList1, offset1, speed = 1f) { offset1 = it }
|
||||
}
|
||||
coroutineScope.launch {
|
||||
animateImageWall(imageList2, offset2, speed = 1.5f, reverse = true) { offset2 = it }
|
||||
}
|
||||
coroutineScope.launch {
|
||||
animateImageWall(imageList3, offset3, speed = 2f) { offset3 = it }
|
||||
}
|
||||
}
|
||||
Lifecycle.Event.ON_PAUSE -> {
|
||||
// 可选: 在Composable暂停时取消协程
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.offset(y = (-72).dp)
|
||||
) {
|
||||
|
||||
Row (modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(700.dp)){
|
||||
// 第1列
|
||||
ImageColumn(imageList1, offset1,
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f))
|
||||
// 第2列
|
||||
ImageColumn(imageList2, offset2,
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f), reverse = true)
|
||||
// 第3列
|
||||
ImageColumn(imageList3, offset3,
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f))
|
||||
}
|
||||
|
||||
// 白色叠加层
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.background(Color.White.copy(alpha = 0.5f)),
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(650.dp)
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
Color.White
|
||||
),
|
||||
startY = 0f,
|
||||
endY = 650f
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ImageColumn(
|
||||
imageList: List<Int>,
|
||||
offset: Float,
|
||||
modifier: Modifier,
|
||||
reverse: Boolean = false
|
||||
) {
|
||||
val imageCount = imageList.size
|
||||
val imageHeight = 208.dp
|
||||
var currentImage by remember { mutableStateOf(0) }
|
||||
val totalHeight = imageHeight.value * imageCount // 计算总高度
|
||||
Column(modifier = modifier) {
|
||||
for (i in 0 until imageCount) {
|
||||
Box(modifier = Modifier
|
||||
.width(156.dp)
|
||||
.height(208.dp)
|
||||
.scale(1f)
|
||||
.graphicsLayer {
|
||||
var translation = if (reverse) {
|
||||
offset
|
||||
} else {
|
||||
offset
|
||||
}
|
||||
// 检查是否超出屏幕范围
|
||||
if (translation < -imageHeight.value) { // 移出屏幕底部
|
||||
translation += totalHeight
|
||||
} else if (translation > totalHeight) { // 移出屏幕顶部
|
||||
translation -= totalHeight
|
||||
}
|
||||
translationY = translation
|
||||
}
|
||||
.clip(RoundedCornerShape(16.dp)),
|
||||
contentAlignment = Alignment.Center){
|
||||
Image(
|
||||
painter = painterResource(id = imageList[(currentImage + i) % imageCount]),
|
||||
contentDescription = "背景图片",
|
||||
modifier = Modifier
|
||||
.width(156.dp)
|
||||
.height(208.dp)
|
||||
.scale(0.9f)
|
||||
.clip(RoundedCornerShape(16.dp)),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun animateImageWall(
|
||||
imageList: MutableList<Int>,
|
||||
initialOffset: Float,
|
||||
speed: Float,
|
||||
reverse: Boolean = false,
|
||||
onUpdate: (Float) -> Unit,
|
||||
) {
|
||||
var currentOffset = initialOffset
|
||||
val imageCount = imageList.size
|
||||
val imageHeight = 208.dp
|
||||
var currentIndex = 0 // 添加一个变量来跟踪当前显示的图片索引
|
||||
while (true) {
|
||||
onUpdate(currentOffset)
|
||||
if (reverse) {
|
||||
currentOffset -= speed
|
||||
if (currentOffset < -imageHeight.value * (imageCount - 1)) {
|
||||
// val firstImage = imageList.first() // 将第一个元素移除
|
||||
// imageList.add(firstImage) // 将第一个元素添加到末尾
|
||||
currentOffset += imageHeight.value * imageCount // 将图片移动到末尾
|
||||
// currentIndex = (currentIndex - 1 + imageCount) % imageCount // 反向旋转
|
||||
}
|
||||
} else {
|
||||
currentOffset += speed
|
||||
if (currentOffset > imageHeight.value * (imageCount - 1)) {
|
||||
// val lastImage = imageList.last() // 将最后一个元素移除
|
||||
// imageList.add(0, lastImage) // 将最后一个元素添加到开头
|
||||
currentOffset -= imageHeight.value * imageCount // 将图片移动到开头
|
||||
// currentIndex = (currentIndex + 1) % imageCount // 正向旋转
|
||||
}
|
||||
}
|
||||
// 更新 ImageColumn 中的 currentImage
|
||||
// onUpdate(currentIndex.toFloat()) // 将当前索引传递给 onUpdate
|
||||
delay(16)
|
||||
}
|
||||
}
|
||||
|
||||
// 计算图片的透明度
|
||||
private fun calculateAlpha(
|
||||
offset: Float,
|
||||
index: Int,
|
||||
imageHeight: Float,
|
||||
totalHeight: Float,
|
||||
reverse: Boolean
|
||||
): Float {
|
||||
val offsetDp = if (reverse) {
|
||||
-offset
|
||||
} else {
|
||||
offset
|
||||
}
|
||||
val imageTop = index * imageHeight
|
||||
val imageBottom = imageTop + imageHeight
|
||||
return when {
|
||||
offsetDp in imageTop..imageBottom -> {
|
||||
(offsetDp - imageTop) / imageHeight
|
||||
}
|
||||
(offsetDp + totalHeight) in imageTop..imageBottom -> { // 考虑循环滚动的情况
|
||||
(offsetDp + totalHeight - imageTop) / imageHeight
|
||||
}
|
||||
(offsetDp - totalHeight) in imageTop..imageBottom -> { // 考虑循环滚动的情况
|
||||
(offsetDp - totalHeight - imageTop) / imageHeight
|
||||
}
|
||||
else -> 0f
|
||||
}
|
||||
}
|
||||
BIN
app/src/main/res/drawable/wall_1_1.jpg
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
app/src/main/res/drawable/wall_1_2.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/drawable/wall_1_3.jpg
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
app/src/main/res/drawable/wall_2_1.jpg
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
app/src/main/res/drawable/wall_2_2.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
app/src/main/res/drawable/wall_2_3.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
app/src/main/res/drawable/wall_3_1.jpg
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
app/src/main/res/drawable/wall_3_2.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
app/src/main/res/drawable/wall_3_3.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
app/src/main/res/drawable/wall_demo.jpg
Normal file
|
After Width: | Height: | Size: 639 KiB |