Skip to content

Commit 8d0c52d

Browse files
committed
修复轮播图无法获取焦点
1 parent 941911d commit 8d0c52d

File tree

1 file changed

+163
-14
lines changed
  • app/shared/src/main/kotlin/dev/aaa1115910/bv/component

1 file changed

+163
-14
lines changed

app/shared/src/main/kotlin/dev/aaa1115910/bv/component/Carousel.kt

Lines changed: 163 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,54 @@
11
package dev.aaa1115910.bv.component
22

3+
import androidx.compose.animation.AnimatedContent
4+
import androidx.compose.animation.AnimatedContentScope
5+
import androidx.compose.animation.ContentTransform
36
import androidx.compose.animation.core.tween
47
import androidx.compose.animation.fadeIn
58
import androidx.compose.animation.fadeOut
69
import androidx.compose.animation.togetherWith
10+
import androidx.compose.foundation.background
711
import androidx.compose.foundation.clickable
12+
import androidx.compose.foundation.layout.Box
13+
import androidx.compose.foundation.layout.Column
14+
import androidx.compose.foundation.layout.Row
15+
import androidx.compose.foundation.layout.fillMaxHeight
816
import androidx.compose.foundation.layout.fillMaxWidth
917
import androidx.compose.foundation.layout.height
18+
import androidx.compose.foundation.layout.padding
1019
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.LaunchedEffect
21+
import androidx.compose.runtime.getValue
22+
import androidx.compose.runtime.mutableIntStateOf
23+
import androidx.compose.runtime.mutableStateListOf
24+
import androidx.compose.runtime.mutableStateOf
25+
import androidx.compose.runtime.remember
26+
import androidx.compose.runtime.rememberCoroutineScope
27+
import androidx.compose.runtime.setValue
1128
import androidx.compose.ui.Alignment
1229
import androidx.compose.ui.Modifier
1330
import androidx.compose.ui.draw.clip
31+
import androidx.compose.ui.focus.onFocusChanged
32+
import androidx.compose.ui.graphics.Color
33+
import androidx.compose.ui.input.key.Key
34+
import androidx.compose.ui.input.key.KeyEventType
35+
import androidx.compose.ui.input.key.key
36+
import androidx.compose.ui.input.key.onKeyEvent
37+
import androidx.compose.ui.input.key.type
1438
import androidx.compose.ui.layout.ContentScale
39+
import androidx.compose.ui.tooling.preview.Preview
1540
import androidx.compose.ui.unit.dp
16-
import androidx.tv.material3.Carousel
41+
import androidx.tv.material3.Button
42+
import androidx.tv.material3.CarouselDefaults
1743
import androidx.tv.material3.ExperimentalTvMaterial3Api
1844
import androidx.tv.material3.MaterialTheme
45+
import androidx.tv.material3.Text
1946
import coil.compose.AsyncImage
2047
import dev.aaa1115910.biliapi.entity.CarouselData
2148
import dev.aaa1115910.bv.util.focusedBorder
49+
import kotlinx.coroutines.Dispatchers
50+
import kotlinx.coroutines.delay
51+
import kotlinx.coroutines.launch
2252

2353
@Composable
2454
fun PgcCarousel(
@@ -46,7 +76,6 @@ fun UgcCarousel(
4676
)
4777
}
4878

49-
@OptIn(ExperimentalTvMaterial3Api::class)
5079
@Composable
5180
fun CarouselContent(
5281
modifier: Modifier = Modifier,
@@ -59,32 +88,152 @@ fun CarouselContent(
5988
.height(240.dp)
6089
.clip(MaterialTheme.shapes.large)
6190
.focusedBorder(),
62-
contentTransformEndToStart =
63-
fadeIn(tween(1000)).togetherWith(fadeOut(tween(1000))),
64-
contentTransformStartToEnd =
65-
fadeIn(tween(1000)).togetherWith(fadeOut(tween(1000)))
91+
onClick = { itemIndex ->
92+
onClick(data[itemIndex])
93+
}
6694
) { itemIndex ->
6795
CarouselCard(
68-
data = data[itemIndex],
69-
onClick = { onClick(data[itemIndex]) }
96+
data = data[itemIndex]
7097
)
7198
}
7299
}
73100

74101
@Composable
75102
fun CarouselCard(
76103
modifier: Modifier = Modifier,
77-
data: CarouselData.CarouselItem,
78-
onClick: () -> Unit = {}
104+
data: CarouselData.CarouselItem
79105
) {
80106
AsyncImage(
81-
modifier = modifier
82-
.fillMaxWidth()
83-
.clip(MaterialTheme.shapes.large)
84-
.clickable { onClick() },
107+
modifier = modifier.fillMaxWidth(),
85108
model = data.cover,
86109
contentDescription = null,
87110
contentScale = ContentScale.Crop,
88111
alignment = Alignment.TopCenter
89112
)
113+
}
114+
115+
@OptIn(ExperimentalTvMaterial3Api::class)
116+
@Composable
117+
fun Carousel(
118+
itemCount: Int,
119+
modifier: Modifier = Modifier,
120+
autoScrollInterval: Long = CarouselDefaults.TimeToDisplayItemMillis,
121+
contentTransformStartToEnd: ContentTransform = fadeIn(tween(1000))
122+
.togetherWith(fadeOut(tween(1000))),
123+
contentTransformEndToStart: ContentTransform = fadeIn(tween(1000))
124+
.togetherWith(fadeOut(tween(1000))),
125+
onClick: (index: Int) -> Unit,
126+
content: @Composable AnimatedContentScope.(index: Int) -> Unit
127+
) {
128+
var hasFocus by remember { mutableStateOf(false) }
129+
var isMovingBackward by remember { mutableStateOf(false) }
130+
var currentIndex by remember { mutableIntStateOf(0) }
131+
132+
LaunchedEffect(currentIndex, itemCount) {
133+
while (true) {
134+
delay(autoScrollInterval)
135+
if (itemCount == 0 || hasFocus) continue
136+
isMovingBackward = false
137+
currentIndex = (currentIndex + 1) % itemCount
138+
}
139+
}
140+
141+
Box(
142+
modifier = modifier
143+
.onFocusChanged { focusState ->
144+
hasFocus = focusState.isFocused
145+
}
146+
.clickable { onClick(currentIndex) }
147+
.onKeyEvent {
148+
when {
149+
itemCount == 0 -> false
150+
it.type == KeyEventType.KeyUp -> false
151+
it.key == Key.DirectionLeft -> {
152+
isMovingBackward = true
153+
currentIndex = (currentIndex - 1 + itemCount) % itemCount
154+
true
155+
}
156+
157+
it.key == Key.DirectionRight -> {
158+
isMovingBackward = false
159+
currentIndex = (currentIndex + 1) % itemCount
160+
true
161+
}
162+
163+
else -> false
164+
}
165+
}
166+
) {
167+
AnimatedContent(
168+
targetState = currentIndex,
169+
transitionSpec = {
170+
if (isMovingBackward) {
171+
contentTransformEndToStart
172+
} else {
173+
contentTransformStartToEnd
174+
}
175+
},
176+
label = "CarouselAnimation"
177+
) { activeItemIndex ->
178+
if (itemCount > 0) content(activeItemIndex)
179+
}
180+
CarouselDefaults.IndicatorRow(
181+
itemCount = itemCount,
182+
activeItemIndex = currentIndex,
183+
modifier = Modifier
184+
.align(Alignment.BottomEnd)
185+
.padding(16.dp),
186+
)
187+
}
188+
}
189+
190+
@Preview
191+
@Composable
192+
private fun CarouselPreview() {
193+
val colors = remember { mutableStateListOf<Color>() }
194+
195+
val scope = rememberCoroutineScope()
196+
LaunchedEffect(Unit) {
197+
scope.launch(Dispatchers.IO) {
198+
delay(8000)
199+
colors.addAll(
200+
listOf(
201+
Color.Red,
202+
Color.Yellow,
203+
Color.Green,
204+
Color.Blue,
205+
Color.Cyan,
206+
Color.Magenta,
207+
Color.Gray,
208+
)
209+
)
210+
}
211+
}
212+
213+
Column {
214+
Button(onClick = {}) { Text(text = "button") }
215+
Row {
216+
Button(onClick = {}) { Text(text = "button") }
217+
Carousel(
218+
itemCount = colors.size,
219+
modifier = Modifier
220+
.fillMaxWidth()
221+
.height(200.dp)
222+
.clip(MaterialTheme.shapes.large)
223+
.focusedBorder(),
224+
onClick = {
225+
226+
}
227+
) {
228+
Box(
229+
modifier = Modifier
230+
.fillMaxWidth()
231+
.fillMaxHeight()
232+
.background(color = colors[it])
233+
) {}
234+
}
235+
}
236+
237+
Button(onClick = {}) { Text(text = "button") }
238+
}
90239
}

0 commit comments

Comments
 (0)