1 /*
<lambda>null2 * Copyright 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 @file:Suppress("DEPRECATION")
18
19 package androidx.compose.foundation.layout.samples
20
21 import androidx.annotation.Sampled
22 import androidx.compose.foundation.Canvas
23 import androidx.compose.foundation.background
24 import androidx.compose.foundation.clickable
25 import androidx.compose.foundation.layout.Arrangement
26 import androidx.compose.foundation.layout.Box
27 import androidx.compose.foundation.layout.ExperimentalLayoutApi
28 import androidx.compose.foundation.layout.FlowRow
29 import androidx.compose.foundation.layout.FlowRowOverflow
30 import androidx.compose.foundation.layout.FlowRowOverflowScope
31 import androidx.compose.foundation.layout.fillMaxSize
32 import androidx.compose.foundation.layout.fillMaxWidth
33 import androidx.compose.foundation.layout.height
34 import androidx.compose.foundation.layout.padding
35 import androidx.compose.foundation.layout.width
36 import androidx.compose.foundation.layout.wrapContentHeight
37 import androidx.compose.material.Text
38 import androidx.compose.runtime.Composable
39 import androidx.compose.runtime.getValue
40 import androidx.compose.runtime.mutableStateOf
41 import androidx.compose.runtime.remember
42 import androidx.compose.runtime.setValue
43 import androidx.compose.ui.Alignment
44 import androidx.compose.ui.Modifier
45 import androidx.compose.ui.geometry.Offset
46 import androidx.compose.ui.graphics.Color
47 import androidx.compose.ui.layout.layout
48 import androidx.compose.ui.text.TextLayoutResult
49 import androidx.compose.ui.text.TextStyle
50 import androidx.compose.ui.text.drawText
51 import androidx.compose.ui.text.font.FontWeight
52 import androidx.compose.ui.text.rememberTextMeasurer
53 import androidx.compose.ui.unit.Constraints
54 import androidx.compose.ui.unit.dp
55 import androidx.compose.ui.unit.sp
56 import kotlin.random.Random
57
58 @OptIn(ExperimentalLayoutApi::class)
59 @Sampled
60 @Composable
61 fun SimpleFlowRow() {
62 Text(
63 modifier =
64 Modifier.fillMaxWidth(1f).padding(20.dp).wrapContentHeight(align = Alignment.Top),
65 text = "Flow Row with weights",
66 fontWeight = FontWeight.Bold
67 )
68
69 FlowRow(
70 Modifier.fillMaxWidth(1f).padding(20.dp).wrapContentHeight(align = Alignment.Top),
71 horizontalArrangement = Arrangement.spacedBy(10.dp),
72 verticalArrangement = Arrangement.spacedBy(20.dp),
73 maxItemsInEachRow = 3,
74 ) {
75 repeat(20) {
76 Box(
77 Modifier.align(Alignment.CenterVertically)
78 .width(50.dp)
79 .height(50.dp)
80 .weight(1f, true)
81 .background(Color.Green)
82 ) {
83 Text(text = it.toString(), fontSize = 18.sp, modifier = Modifier.padding(3.dp))
84 }
85 }
86 }
87 }
88
89 @OptIn(ExperimentalLayoutApi::class)
90 @Sampled
91 @Composable
SimpleFlowRowMaxLinesWithSeeMorenull92 fun SimpleFlowRowMaxLinesWithSeeMore() {
93 val totalCount = 20
94 var maxLines by remember { mutableStateOf(2) }
95
96 Text(
97 modifier =
98 Modifier.fillMaxWidth(1f).padding(20.dp).wrapContentHeight(align = Alignment.Top),
99 text =
100 "Flow Row with total items of 40, " +
101 "with Max Lines of 2 and See More, " +
102 "which when clicked, increases max lines by two",
103 fontWeight = FontWeight.Bold
104 )
105 FlowRow(
106 Modifier.fillMaxWidth(1f).padding(20.dp).wrapContentHeight(align = Alignment.Top),
107 horizontalArrangement = Arrangement.spacedBy(10.dp),
108 verticalArrangement = Arrangement.spacedBy(20.dp),
109 maxLines = maxLines,
110 overflow = FlowRowOverflow.expandIndicator { Ellipsis(text = "...") { maxLines += 2 } }
111 ) {
112 repeat(totalCount) {
113 Box(
114 Modifier.align(Alignment.CenterVertically)
115 .width(50.dp)
116 .height(50.dp)
117 .background(Color.Green)
118 ) {
119 Text(
120 text = it.toString(),
121 fontSize = 18.sp,
122 modifier = Modifier.padding(3.dp).align(Alignment.Center)
123 )
124 }
125 }
126 }
127 }
128
129 @OptIn(ExperimentalLayoutApi::class)
130 @Composable
SimpleFlowRowWithMaxHeightnull131 fun SimpleFlowRowWithMaxHeight() {
132 var initialHeight = 200.dp
133 var height by remember { mutableStateOf(initialHeight) }
134
135 Text(
136 modifier =
137 Modifier.fillMaxWidth(1f).padding(20.dp).wrapContentHeight(align = Alignment.Top),
138 text =
139 "Flow Row with total items of 40, " +
140 "with Max height of 200.dp and See More/collapse button, " +
141 "which when clicked, increases height by 200.dp",
142 fontWeight = FontWeight.Bold
143 )
144
145 FlowRow(
146 Modifier.fillMaxWidth(1f)
147 .padding(20.dp)
148 .height(height)
149 .wrapContentHeight(align = Alignment.Top),
150 horizontalArrangement = Arrangement.spacedBy(10.dp),
151 verticalArrangement = Arrangement.spacedBy(20.dp),
152 overflow =
153 FlowRowOverflow.expandOrCollapseIndicator(
154 minHeightToShowCollapse = 200.dp,
155 expandIndicator = { Ellipsis(text = "...") { height += 200.dp } },
156 collapseIndicator = { Ellipsis(text = "^") { height = 100.dp } }
157 )
158 ) {
159 repeat(40) {
160 Box(
161 Modifier.align(Alignment.CenterVertically)
162 .width(50.dp)
163 .height(50.dp)
164 .background(Color.Green)
165 ) {
166 Text(text = it.toString(), fontSize = 18.sp, modifier = Modifier.padding(3.dp))
167 }
168 }
169 }
170 }
171
172 @OptIn(ExperimentalLayoutApi::class)
173 @Sampled
174 @Composable
SimpleFlowRowMaxLinesDynamicSeeMorenull175 fun SimpleFlowRowMaxLinesDynamicSeeMore() {
176 val totalCount = 40
177 var maxLines by remember { mutableStateOf(2) }
178
179 Text(
180 modifier =
181 Modifier.fillMaxWidth(1f).padding(20.dp).wrapContentHeight(align = Alignment.Top),
182 text =
183 "Flow Row with total items of 40, " +
184 "with Max Lines of 2 and See More/collapse button, " +
185 "which when clicked, increases max lines by two",
186 fontWeight = FontWeight.Bold
187 )
188 val moreOrLessIndicator =
189 @Composable { scope: FlowRowOverflowScope ->
190 DynamicSeeMoreForDrawText(
191 isHorizontal = true,
192 totalCount = totalCount,
193 { scope.shownItemCount },
194 onExpand = { maxLines += 2 },
195 onShrink = { maxLines = 2 }
196 )
197 }
198 FlowRow(
199 Modifier.fillMaxWidth(1f)
200 .padding(20.dp)
201 .wrapContentHeight()
202 .padding(20.dp)
203 .fillMaxWidth(1f),
204 horizontalArrangement = Arrangement.spacedBy(10.dp),
205 verticalArrangement = Arrangement.spacedBy(20.dp),
206 maxLines = maxLines,
207 overflow =
208 FlowRowOverflow.expandOrCollapseIndicator(
209 minRowsToShowCollapse = 4,
210 expandIndicator = moreOrLessIndicator,
211 collapseIndicator = moreOrLessIndicator
212 )
213 ) {
214 repeat(totalCount) {
215 Box(
216 Modifier.align(Alignment.CenterVertically)
217 .width(50.dp)
218 .height(50.dp)
219 .background(Color.Green)
220 ) {
221 Text(
222 text = it.toString(),
223 fontSize = 18.sp,
224 modifier = Modifier.padding(3.dp).align(Alignment.Center)
225 )
226 }
227 }
228 }
229 }
230
231 @Composable
DynamicSeeMoreForDrawTextnull232 internal fun DynamicSeeMoreForDrawText(
233 isHorizontal: Boolean,
234 totalCount: Int,
235 shownItemCount: () -> Int?,
236 onExpand: () -> Unit,
237 onShrink: () -> Unit,
238 ) {
239 Box(
240 Modifier.clickable(
241 onClick = {
242 val remainingItems = shownItemCount()?.let { totalCount - it }
243 if (remainingItems == 0) {
244 onShrink()
245 } else {
246 onExpand()
247 }
248 }
249 )
250 .width(50.dp)
251 .height(50.dp)
252 .background(Color.Green)
253 ) {
254 val textMeasurer = rememberTextMeasurer()
255 Canvas(
256 Modifier.fillMaxSize().layout { measurable, constraints ->
257 // TextLayout can be done any time prior to its use in draw, including in a
258 // background thread.
259 // In this sample, text layout is measured in layout modifier. This way the layout
260 // call can be restarted when async font loading completes due to the fact that
261 // `.measure` call is executed in `.layout`.
262 val result =
263 textMeasurer.measure(
264 text = "+2000",
265 style = TextStyle(fontSize = 24.sp),
266 constraints = constraints
267 )
268 val placeable =
269 measurable.measure(
270 Constraints(
271 minWidth = result.size.width,
272 maxWidth = result.size.width,
273 minHeight = result.size.height,
274 maxHeight = result.size.height
275 )
276 )
277 layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
278 }
279 ) {
280 // This happens during draw phase.
281 val collapseText = if (isHorizontal) "^" else "<"
282 val remainingItems = shownItemCount()?.let { totalCount - it }
283 var textLayoutResult: TextLayoutResult =
284 textMeasurer.measure(
285 text = if (remainingItems == 0) collapseText else "+$remainingItems",
286 style = TextStyle(fontSize = 18.sp)
287 )
288 drawText(
289 textLayoutResult,
290 topLeft =
291 Offset(
292 (size.width - textLayoutResult.size.width) / 2,
293 (size.height - textLayoutResult.size.height) / 2,
294 )
295 )
296 }
297 }
298 }
299
300 @Composable
Ellipsisnull301 internal fun Ellipsis(text: String, onClick: () -> Unit) {
302 Box(Modifier.clickable(onClick = onClick).width(50.dp).height(50.dp).background(Color.Green)) {
303 Text(
304 modifier = Modifier.align(Alignment.Center).padding(3.dp),
305 text = text,
306 fontSize = 18.sp
307 )
308 }
309 }
310
311 @OptIn(ExperimentalLayoutApi::class)
312 @Sampled
313 @Composable
SimpleFlowRow_EqualHeightnull314 fun SimpleFlowRow_EqualHeight() {
315 FlowRow(
316 Modifier.fillMaxWidth(1f).padding(20.dp).wrapContentHeight(align = Alignment.Top),
317 horizontalArrangement = Arrangement.spacedBy(10.dp),
318 verticalArrangement = Arrangement.spacedBy(20.dp),
319 maxItemsInEachRow = 3,
320 ) {
321 repeat(9) {
322 Box(Modifier.width(100.dp).background(Color.Green).fillMaxRowHeight(1f)) {
323 val text = generateRandomString(IntRange(10, 50).random())
324 Text(text = text, fontSize = 18.sp, modifier = Modifier.padding(3.dp))
325 }
326 }
327 }
328 }
329
generateRandomStringnull330 fun generateRandomString(length: Int): String {
331 val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
332 val random = Random.Default
333
334 val randomString = StringBuilder(length)
335 repeat(length) {
336 val randomIndex = random.nextInt(0, charPool.size)
337 val randomChar = charPool[randomIndex]
338 randomString.append(randomChar)
339 }
340 return randomString.toString()
341 }
342