软件主页背景动画,存在两个问题:1.只有位置改变,没有图片索引改变;2.布局上下比例还要调整
@@ -3,11 +3,16 @@ package com.aiosman.riderpro.ui.login
|
|||||||
import android.content.ContentValues.TAG
|
import android.content.ContentValues.TAG
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
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.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
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.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
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.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
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.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
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.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
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.AppState
|
||||||
import com.aiosman.riderpro.AppStore
|
import com.aiosman.riderpro.AppStore
|
||||||
import com.aiosman.riderpro.LocalNavController
|
import com.aiosman.riderpro.LocalNavController
|
||||||
@@ -42,6 +66,8 @@ import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
|||||||
import com.aiosman.riderpro.utils.GoogleLogin
|
import com.aiosman.riderpro.utils.GoogleLogin
|
||||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -112,46 +138,19 @@ fun LoginPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize().background(Color.White)
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color.White)
|
||||||
) {
|
) {
|
||||||
// bg image
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxWidth().height(900.dp).offset(
|
modifier = Modifier
|
||||||
y = (-72).dp
|
.fillMaxSize()
|
||||||
),
|
.offset(
|
||||||
|
y = (-72).dp
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
Image(
|
MovingImageWall()
|
||||||
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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -230,7 +229,209 @@ fun LoginPage() {
|
|||||||
Spacer(modifier = Modifier.height(120.dp))
|
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 |