软件主页背景动画,存在两个问题:1.只有位置改变,没有图片索引改变;2.布局上下比例还要调整

This commit is contained in:
2024-10-11 05:55:29 +08:00
parent d728ef2043
commit 36739b1615
11 changed files with 239 additions and 38 deletions

View File

@@ -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(
y = (-72).dp
),
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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 KiB