1
1
package dev.aaa1115910.bv.component
2
2
3
+ import androidx.compose.animation.AnimatedContent
4
+ import androidx.compose.animation.AnimatedContentScope
5
+ import androidx.compose.animation.ContentTransform
3
6
import androidx.compose.animation.core.tween
4
7
import androidx.compose.animation.fadeIn
5
8
import androidx.compose.animation.fadeOut
6
9
import androidx.compose.animation.togetherWith
10
+ import androidx.compose.foundation.background
7
11
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
8
16
import androidx.compose.foundation.layout.fillMaxWidth
9
17
import androidx.compose.foundation.layout.height
18
+ import androidx.compose.foundation.layout.padding
10
19
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
11
28
import androidx.compose.ui.Alignment
12
29
import androidx.compose.ui.Modifier
13
30
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
14
38
import androidx.compose.ui.layout.ContentScale
39
+ import androidx.compose.ui.tooling.preview.Preview
15
40
import androidx.compose.ui.unit.dp
16
- import androidx.tv.material3.Carousel
41
+ import androidx.tv.material3.Button
42
+ import androidx.tv.material3.CarouselDefaults
17
43
import androidx.tv.material3.ExperimentalTvMaterial3Api
18
44
import androidx.tv.material3.MaterialTheme
45
+ import androidx.tv.material3.Text
19
46
import coil.compose.AsyncImage
20
47
import dev.aaa1115910.biliapi.entity.CarouselData
21
48
import dev.aaa1115910.bv.util.focusedBorder
49
+ import kotlinx.coroutines.Dispatchers
50
+ import kotlinx.coroutines.delay
51
+ import kotlinx.coroutines.launch
22
52
23
53
@Composable
24
54
fun PgcCarousel (
@@ -46,7 +76,6 @@ fun UgcCarousel(
46
76
)
47
77
}
48
78
49
- @OptIn(ExperimentalTvMaterial3Api ::class )
50
79
@Composable
51
80
fun CarouselContent (
52
81
modifier : Modifier = Modifier ,
@@ -59,32 +88,152 @@ fun CarouselContent(
59
88
.height(240 .dp)
60
89
.clip(MaterialTheme .shapes.large)
61
90
.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
+ }
66
94
) { itemIndex ->
67
95
CarouselCard (
68
- data = data[itemIndex],
69
- onClick = { onClick(data[itemIndex]) }
96
+ data = data[itemIndex]
70
97
)
71
98
}
72
99
}
73
100
74
101
@Composable
75
102
fun CarouselCard (
76
103
modifier : Modifier = Modifier ,
77
- data : CarouselData .CarouselItem ,
78
- onClick : () -> Unit = {}
104
+ data : CarouselData .CarouselItem
79
105
) {
80
106
AsyncImage (
81
- modifier = modifier
82
- .fillMaxWidth()
83
- .clip(MaterialTheme .shapes.large)
84
- .clickable { onClick() },
107
+ modifier = modifier.fillMaxWidth(),
85
108
model = data.cover,
86
109
contentDescription = null ,
87
110
contentScale = ContentScale .Crop ,
88
111
alignment = Alignment .TopCenter
89
112
)
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
+ }
90
239
}
0 commit comments