1 /*
2 * Copyright 2020 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 package androidx.compose.foundation.demos.text
18
19 import android.annotation.SuppressLint
20 import androidx.compose.animation.animateColor
21 import androidx.compose.animation.core.RepeatMode
22 import androidx.compose.animation.core.infiniteRepeatable
23 import androidx.compose.animation.core.rememberInfiniteTransition
24 import androidx.compose.animation.core.tween
25 import androidx.compose.foundation.Canvas
26 import androidx.compose.foundation.background
27 import androidx.compose.foundation.border
28 import androidx.compose.foundation.layout.Arrangement
29 import androidx.compose.foundation.layout.Box
30 import androidx.compose.foundation.layout.Column
31 import androidx.compose.foundation.layout.ColumnScope
32 import androidx.compose.foundation.layout.Row
33 import androidx.compose.foundation.layout.Spacer
34 import androidx.compose.foundation.layout.fillMaxHeight
35 import androidx.compose.foundation.layout.fillMaxSize
36 import androidx.compose.foundation.layout.fillMaxWidth
37 import androidx.compose.foundation.layout.height
38 import androidx.compose.foundation.layout.heightIn
39 import androidx.compose.foundation.layout.size
40 import androidx.compose.foundation.layout.width
41 import androidx.compose.foundation.layout.widthIn
42 import androidx.compose.foundation.lazy.LazyColumn
43 import androidx.compose.foundation.rememberScrollState
44 import androidx.compose.foundation.selection.selectable
45 import androidx.compose.foundation.selection.selectableGroup
46 import androidx.compose.foundation.text.BasicText
47 import androidx.compose.foundation.text.InlineTextContent
48 import androidx.compose.foundation.text.TextAutoSize
49 import androidx.compose.foundation.text.appendInlineContent
50 import androidx.compose.foundation.verticalScroll
51 import androidx.compose.material.ExperimentalMaterialApi
52 import androidx.compose.material.Icon
53 import androidx.compose.material.IconButton
54 import androidx.compose.material.ListItem
55 import androidx.compose.material.RadioButton
56 import androidx.compose.material.Slider
57 import androidx.compose.material.Switch
58 import androidx.compose.material.Text
59 import androidx.compose.material.icons.Icons
60 import androidx.compose.material.icons.filled.KeyboardArrowDown
61 import androidx.compose.material.icons.filled.KeyboardArrowUp
62 import androidx.compose.runtime.Composable
63 import androidx.compose.runtime.CompositionLocalProvider
64 import androidx.compose.runtime.getValue
65 import androidx.compose.runtime.mutableFloatStateOf
66 import androidx.compose.runtime.mutableStateOf
67 import androidx.compose.runtime.remember
68 import androidx.compose.runtime.setValue
69 import androidx.compose.ui.Alignment
70 import androidx.compose.ui.Modifier
71 import androidx.compose.ui.geometry.Offset
72 import androidx.compose.ui.graphics.Color
73 import androidx.compose.ui.graphics.Shadow
74 import androidx.compose.ui.graphics.graphicsLayer
75 import androidx.compose.ui.platform.LocalLayoutDirection
76 import androidx.compose.ui.semantics.Role
77 import androidx.compose.ui.text.AnnotatedString
78 import androidx.compose.ui.text.ExperimentalTextApi
79 import androidx.compose.ui.text.ParagraphStyle
80 import androidx.compose.ui.text.Placeholder
81 import androidx.compose.ui.text.PlaceholderVerticalAlign
82 import androidx.compose.ui.text.SpanStyle
83 import androidx.compose.ui.text.TextStyle
84 import androidx.compose.ui.text.buildAnnotatedString
85 import androidx.compose.ui.text.drawText
86 import androidx.compose.ui.text.font.FontFamily
87 import androidx.compose.ui.text.font.FontStyle
88 import androidx.compose.ui.text.font.FontWeight
89 import androidx.compose.ui.text.intl.LocaleList
90 import androidx.compose.ui.text.rememberTextMeasurer
91 import androidx.compose.ui.text.samples.BaselineShiftSample
92 import androidx.compose.ui.text.samples.FontFamilyCursiveSample
93 import androidx.compose.ui.text.samples.FontFamilyMonospaceSample
94 import androidx.compose.ui.text.samples.FontFamilySansSerifSample
95 import androidx.compose.ui.text.samples.FontFamilySerifSample
96 import androidx.compose.ui.text.samples.ParagraphStyleAnnotatedStringsSample
97 import androidx.compose.ui.text.samples.ParagraphStyleSample
98 import androidx.compose.ui.text.samples.TextDecorationCombinedSample
99 import androidx.compose.ui.text.samples.TextDecorationLineThroughSample
100 import androidx.compose.ui.text.samples.TextDecorationUnderlineSample
101 import androidx.compose.ui.text.samples.TextOverflowClipSample
102 import androidx.compose.ui.text.samples.TextOverflowEllipsisSample
103 import androidx.compose.ui.text.samples.TextOverflowVisibleFixedSizeSample
104 import androidx.compose.ui.text.samples.TextOverflowVisibleMinHeightSample
105 import androidx.compose.ui.text.samples.TextStyleSample
106 import androidx.compose.ui.text.style.Hyphens
107 import androidx.compose.ui.text.style.TextAlign
108 import androidx.compose.ui.text.style.TextOverflow
109 import androidx.compose.ui.text.withStyle
110 import androidx.compose.ui.tooling.preview.Preview
111 import androidx.compose.ui.unit.Dp
112 import androidx.compose.ui.unit.LayoutDirection
113 import androidx.compose.ui.unit.TextUnit
114 import androidx.compose.ui.unit.dp
115 import androidx.compose.ui.unit.em
116 import androidx.compose.ui.unit.sp
117 import androidx.compose.ui.window.Popup
118 import androidx.compose.ui.window.PopupProperties
119
120 private const val longText =
121 "This is a very-very long string that wraps into a few lines " + "given the width restrictions."
122 const val displayText = "Text Demo"
123 const val displayTextChinese = "文本演示"
124 const val displayTextArabic = "\u0639\u0631\u0636\u0020\u0627\u0644\u0646\u0635"
125 const val displayTextHindi = "पाठ डेमो"
126 const val displayTextBidi = "Text \u0639\u0631\u0636"
127
128 val fontSize4 = 16.sp
129 val fontSize6 = 20.sp
130 val fontSize8 = 25.sp
131 val fontSize10 = 30.sp
132
133 @SuppressLint("PrimitiveInCollection")
134 private val overflowOptions =
135 listOf(
136 TextOverflow.Clip,
137 TextOverflow.Visible,
138 TextOverflow.StartEllipsis,
139 TextOverflow.MiddleEllipsis,
140 TextOverflow.Ellipsis
141 )
142 private val boolOptions = listOf(true, false)
143 @SuppressLint("PrimitiveInCollection")
144 private val textAlignments =
145 listOf(
146 TextAlign.Left,
147 TextAlign.Start,
148 TextAlign.Center,
149 TextAlign.Right,
150 TextAlign.End,
151 TextAlign.Justify
152 )
153
154 @Preview
155 @Composable
TextDemonull156 fun TextDemo() {
157 LazyColumn {
158 item {
159 TagLine(tag = "color, fontSize, fontWeight and fontStyle")
160 TextDemoBasic()
161 }
162 item {
163 TagLine(
164 tag =
165 "color, fontSize, fontWeight, fontFamily, fontStyle, letterSpacing, " +
166 "background, decoration"
167 )
168 TextDemoComplexStyling()
169 }
170 item {
171 TagLine(tag = "Chinese, Arabic, and Hindi")
172 TextDemoLanguage()
173 }
174 item {
175 TagLine(tag = "FontFamily generic names")
176 TextDemoFontFamily()
177 }
178 item {
179 TagLine(tag = "FontFamily default values")
180 TextDemoFontFamilyDefaultValues()
181 }
182 item {
183 TagLine(tag = "decoration, decorationColor and decorationStyle")
184 TextDemoTextDecoration()
185 }
186 item {
187 TagLine(tag = "letterSpacing")
188 TextDemoLetterSpacing()
189 }
190 item {
191 TagLine(tag = "baselineShift")
192 TextDemoBaselineShift()
193 }
194 item {
195 TagLine(tag = "lineHeight")
196 TextDemoHeight()
197 }
198 item {
199 TagLine(tag = "background")
200 TextDemoBackground()
201 }
202 item {
203 TagLine(tag = "Locale: Japanese, Simplified and Traditional Chinese")
204 TextDemoLocale()
205 }
206 item {
207 TagLine(tag = "textAlign and textDirection")
208 TextDemoTextAlign()
209 }
210 item {
211 TagLine(tag = "softWrap: on and off")
212 TextDemoSoftWrap()
213 }
214 item {
215 TagLine(tag = "shadow")
216 TextDemoShadowEffect()
217 }
218 item {
219 TagLine(tag = "fontSizeScale")
220 TextDemoFontSizeScale()
221 }
222 item {
223 TagLine(tag = "complex paragraph styling")
224 TextDemoParagraphStyling()
225 }
226
227 item {
228 TagLine(tag = "textOverflow: Clip, Ellipsis, Visible")
229 TextDemoTextOverflow()
230 }
231 item {
232 TagLine(tag = "inline content")
233 TextDemoInlineContent()
234 }
235 }
236 }
237
238 @Composable
TagLinenull239 fun TagLine(tag: String) {
240 Text(
241 style = TextStyle(fontSize = fontSize8),
242 text =
243 buildAnnotatedString {
244 append("\n")
245 withStyle(style = SpanStyle(color = Color(0xFFAAAAAA), fontSize = fontSize6)) {
246 append(tag)
247 }
248 }
249 )
250 }
251
252 @Composable
SecondTagLinenull253 fun SecondTagLine(tag: String) {
254 Text(
255 text =
256 buildAnnotatedString {
257 withStyle(style = SpanStyle(color = Color(0xFFAAAAAA), fontSize = fontSize4)) {
258 append(tag)
259 }
260 }
261 )
262 }
263
264 @Composable
TextDemoBasicnull265 fun TextDemoBasic() {
266 // This group of text composables show different color, fontSize, fontWeight and fontStyle in
267 // English.
268 Text(
269 text =
270 buildAnnotatedString {
271 withStyle(
272 SpanStyle(
273 color = Color(0xFFFF0000),
274 fontSize = fontSize6,
275 fontWeight = FontWeight.W200,
276 fontStyle = FontStyle.Italic
277 )
278 ) {
279 append("$displayText ")
280 }
281
282 withStyle(
283 SpanStyle(
284 color = Color(0xFF00FF00),
285 fontSize = fontSize8,
286 fontWeight = FontWeight.W500,
287 fontStyle = FontStyle.Normal
288 )
289 ) {
290 append("$displayText ")
291 }
292
293 withStyle(
294 SpanStyle(
295 color = Color(0xFF0000FF),
296 fontSize = fontSize10,
297 fontWeight = FontWeight.W800,
298 fontStyle = FontStyle.Normal
299 )
300 ) {
301 append(displayText)
302 }
303 }
304 )
305 }
306
307 @Composable
TextDemoComplexStylingnull308 fun TextDemoComplexStyling() {
309 TextStyleSample()
310 }
311
312 @Composable
TextDemoLanguagenull313 fun TextDemoLanguage() {
314 // This group of text composables show different color, fontSize, fontWeight and fontStyle in
315 // Chinese, Arabic, and Hindi.
316 Text(
317 text =
318 buildAnnotatedString {
319 withStyle(
320 style =
321 SpanStyle(
322 color = Color(0xFFFF0000),
323 fontSize = fontSize6,
324 fontWeight = FontWeight.W200,
325 fontStyle = FontStyle.Italic
326 )
327 ) {
328 append("$displayTextChinese ")
329 }
330
331 withStyle(
332 style =
333 SpanStyle(
334 color = Color(0xFF00FF00),
335 fontSize = fontSize8,
336 fontWeight = FontWeight.W500,
337 fontStyle = FontStyle.Normal
338 )
339 ) {
340 append("$displayTextArabic ")
341 }
342
343 withStyle(
344 style =
345 SpanStyle(
346 color = Color(0xFF0000FF),
347 fontSize = fontSize10,
348 fontWeight = FontWeight.W800,
349 fontStyle = FontStyle.Normal
350 )
351 ) {
352 append(displayTextHindi)
353 }
354 }
355 )
356 }
357
358 @Composable
TextDemoFontFamilynull359 fun TextDemoFontFamily() {
360 // This group of text composables show different fontFamilies in English.
361 Text(
362 buildAnnotatedString {
363 withStyle(style = SpanStyle(fontSize = fontSize8, fontFamily = FontFamily.SansSerif)) {
364 append("$displayText sans-serif\n")
365 }
366
367 withStyle(style = SpanStyle(fontSize = fontSize8, fontFamily = FontFamily.Serif)) {
368 append("$displayText serif\n")
369 }
370
371 withStyle(style = SpanStyle(fontSize = fontSize8, fontFamily = FontFamily.Monospace)) {
372 append("$displayText monospace")
373 }
374 }
375 )
376 }
377
378 @Composable
TextDemoFontFamilyDefaultValuesnull379 fun TextDemoFontFamilyDefaultValues() {
380 // This group of text composables show the default font families in English.
381 FontFamilySerifSample()
382 FontFamilySansSerifSample()
383 FontFamilyMonospaceSample()
384 FontFamilyCursiveSample()
385 }
386
387 @Composable
TextDemoTextDecorationnull388 fun TextDemoTextDecoration() {
389 // This group of text composables show different decoration, decorationColor and
390 // decorationStyle.
391 TextDecorationLineThroughSample()
392 TextDecorationUnderlineSample()
393 TextDecorationCombinedSample()
394 }
395
396 @Composable
TextDemoLetterSpacingnull397 fun TextDemoLetterSpacing() {
398 // This group of text composables show different letterSpacing.
399 Text(
400 text =
401 buildAnnotatedString {
402 withStyle(style = SpanStyle(fontSize = fontSize8)) { append("$displayText ") }
403 withStyle(style = SpanStyle(fontSize = fontSize8, letterSpacing = 0.5.em)) {
404 append(displayText)
405 }
406 }
407 )
408 }
409
410 @Composable
TextDemoBaselineShiftnull411 fun TextDemoBaselineShift() {
412 BaselineShiftSample()
413 }
414
415 @Composable
TextDemoHeightnull416 fun TextDemoHeight() {
417 // This group of text composables show different height.
418 Row(Modifier.fillMaxWidth()) {
419 Text(text = "$displayText\n$displayText ", style = TextStyle(fontSize = fontSize8))
420 Text(
421 text = "$displayText\n$displayText ",
422 style = TextStyle(fontSize = fontSize8, lineHeight = 50.sp)
423 )
424 }
425 }
426
427 @Composable
TextDemoBackgroundnull428 fun TextDemoBackground() {
429 // This group of text composables show different background.
430 Text(
431 text =
432 buildAnnotatedString {
433 withStyle(style = SpanStyle(background = Color(0xFFFF0000))) {
434 append("$displayText ")
435 }
436
437 withStyle(style = SpanStyle(background = Color(0xFF00FF00))) {
438 append("$displayText ")
439 }
440
441 withStyle(style = SpanStyle(background = Color(0xFF0000FF))) { append(displayText) }
442 },
443 style = TextStyle(fontSize = fontSize8)
444 )
445 }
446
447 @Composable
TextDemoLocalenull448 fun TextDemoLocale() {
449 // This group of text composables show different Locales of the same Unicode codepoint.
450 val text = "\u82B1"
451 Text(
452 text =
453 buildAnnotatedString {
454 withStyle(style = SpanStyle(localeList = LocaleList("ja-JP"))) {
455 append("$text ")
456 }
457
458 withStyle(style = SpanStyle(localeList = LocaleList("zh-CN"))) {
459 append("$text ")
460 }
461
462 withStyle(style = SpanStyle(localeList = LocaleList("zh-TW"))) { append(text) }
463 },
464 style = TextStyle(fontSize = fontSize8)
465 )
466 }
467
468 @Composable
TextDemoTextAlignnull469 fun TextDemoTextAlign() {
470 // This group of text composables show different TextAligns: LEFT, RIGHT, CENTER, JUSTIFY, START
471 // for
472 // LTR and RTL, END for LTR and RTL.
473 var text = ""
474 for (i in 1..10) {
475 text = "$text$displayText "
476 }
477 Column(Modifier.fillMaxHeight()) {
478 SecondTagLine(tag = "textAlign = TextAlign.Left")
479 Text(
480 modifier = Modifier.fillMaxWidth(),
481 text = displayText,
482 style = TextStyle(fontSize = fontSize8, textAlign = TextAlign.Left)
483 )
484
485 SecondTagLine(tag = "textAlign = TextAlign.Right")
486 Text(
487 modifier = Modifier.fillMaxWidth(),
488 text = displayText,
489 style = TextStyle(fontSize = fontSize8, textAlign = TextAlign.Right)
490 )
491
492 SecondTagLine(tag = "textAlign = TextAlign.Center")
493 Text(
494 modifier = Modifier.fillMaxWidth(),
495 text = displayText,
496 style = TextStyle(fontSize = fontSize8, textAlign = TextAlign.Center)
497 )
498
499 SecondTagLine(tag = "textAlign = default and TextAlign.Justify")
500 Text(
501 modifier = Modifier.fillMaxWidth(),
502 text = text,
503 style = TextStyle(fontSize = fontSize8, color = Color(0xFFFF0000))
504 )
505 Text(
506 modifier = Modifier.fillMaxWidth(),
507 text = text,
508 style =
509 TextStyle(
510 fontSize = fontSize8,
511 color = Color(0xFF0000FF),
512 textAlign = TextAlign.Justify
513 )
514 )
515
516 SecondTagLine(tag = "textAlign = TextAlign.Start for Ltr")
517 Text(
518 modifier = Modifier.fillMaxWidth(),
519 text = displayText,
520 style = TextStyle(fontSize = fontSize8, textAlign = TextAlign.Start)
521 )
522 SecondTagLine(tag = "textAlign = TextAlign.Start for Rtl")
523 Text(
524 modifier = Modifier.fillMaxWidth(),
525 text = displayTextArabic,
526 style = TextStyle(fontSize = fontSize8, textAlign = TextAlign.Start)
527 )
528 SecondTagLine(tag = "textAlign = TextAlign.End for Ltr")
529 Text(
530 modifier = Modifier.fillMaxWidth(),
531 text = displayText,
532 style = TextStyle(fontSize = fontSize8, textAlign = TextAlign.End)
533 )
534 SecondTagLine(tag = "textAlign = TextAlign.End for Rtl")
535 Text(
536 modifier = Modifier.fillMaxWidth(),
537 text = displayTextArabic,
538 style = TextStyle(fontSize = fontSize8, textAlign = TextAlign.End)
539 )
540 }
541 }
542
543 @Composable
TextDemoSoftWrapnull544 fun TextDemoSoftWrap() {
545 // This group of text composables show difference between softWrap is true and false.
546 var text = ""
547 for (i in 1..10) {
548 text = "$text$displayText"
549 }
550 val textStyle = TextStyle(fontSize = fontSize8, color = Color(0xFFFF0000))
551
552 Column(Modifier.fillMaxHeight()) {
553 Text(text = text, style = textStyle)
554 Text(text = text, style = textStyle, softWrap = false)
555 }
556 }
557
558 @Composable
TextDemoHyphensnull559 fun TextDemoHyphens() {
560 val text = "Transformation"
561 val textStyleHyphensOn =
562 TextStyle(fontSize = fontSize8, color = Color.Red, hyphens = Hyphens.Auto)
563 val textStyleHyphensOff =
564 TextStyle(fontSize = fontSize8, color = Color.Blue, hyphens = Hyphens.None)
565 Column {
566 var width by remember { mutableFloatStateOf(30f) }
567 Slider(value = width, onValueChange = { width = it }, valueRange = 20f..400f)
568 Column(Modifier.width(width.dp)) {
569 Text(text = text, style = textStyleHyphensOn)
570 Text(text = text, style = textStyleHyphensOff)
571 }
572 }
573 }
574
575 @Composable
TextDemoShadowEffectnull576 fun TextDemoShadowEffect() {
577 val shadow = Shadow(Color(0xFFE0A0A0), Offset(5f, 5f), blurRadius = 5.0f)
578 Text(
579 style = TextStyle(fontSize = fontSize8),
580 text =
581 buildAnnotatedString {
582 append("text with ")
583 withStyle(style = SpanStyle(shadow = shadow)) { append("shadow!") }
584 }
585 )
586 }
587
588 @Composable
TextDemoFontSizeScalenull589 fun TextDemoFontSizeScale() {
590 Text(
591 style = TextStyle(fontSize = fontSize8),
592 text =
593 buildAnnotatedString {
594 for (i in 4..12 step 4) {
595 val scale = i * 0.1f
596 withStyle(style = SpanStyle(fontSize = scale.em)) {
597 append("fontSizeScale=$scale\n")
598 }
599 }
600 }
601 )
602 }
603
604 @Composable
TextDemoParagraphStylingnull605 fun TextDemoParagraphStyling() {
606 ParagraphStyleSample()
607 ParagraphStyleAnnotatedStringsSample()
608 }
609
610 @Composable
TextDemoTextOverflownull611 fun TextDemoTextOverflow() {
612 SecondTagLine(tag = "overflow = TextOverflow.Clip")
613 TextOverflowClipSample()
614 SecondTagLine(tag = "overflow = TextOverflow.Ellipsis")
615 TextOverflowEllipsisSample()
616 SecondTagLine(tag = "overflow = TextOverflow.Visible with fixed size")
617 TextOverflowVisibleFixedSizeSample()
618 Spacer(modifier = Modifier.size(30.dp))
619 SecondTagLine(tag = "overflow = TextOverflow.Visible with fixed width and min height")
620 TextOverflowVisibleMinHeightSample()
621 }
622
623 @Composable
TextOverflowVisibleInPopupDemonull624 fun TextOverflowVisibleInPopupDemo() {
625 Popup(alignment = Alignment.Center, properties = PopupProperties(clippingEnabled = false)) {
626 val text = "Line\n".repeat(10)
627 Box(Modifier.background(Color.Magenta).size(100.dp)) {
628 Text(text, fontSize = fontSize6, overflow = TextOverflow.Visible)
629 }
630 }
631 }
632
633 @OptIn(ExperimentalTextApi::class)
634 @Composable
TextOverflowVisibleInDrawTextnull635 fun TextOverflowVisibleInDrawText() {
636 val textMeasurer = rememberTextMeasurer()
637 val text = "Line\n".repeat(10)
638 Box(Modifier.fillMaxSize()) {
639 Canvas(
640 Modifier.graphicsLayer().align(Alignment.Center).background(Color.Green).size(100.dp)
641 ) {
642 drawText(
643 textMeasurer = textMeasurer,
644 text = text,
645 style = TextStyle(fontSize = fontSize6),
646 overflow = TextOverflow.Visible
647 )
648 }
649 }
650 }
651
652 @Composable
TextOverflowDemonull653 fun TextOverflowDemo() {
654 Column(
655 verticalArrangement = Arrangement.spacedBy(10.dp),
656 modifier = Modifier.verticalScroll(rememberScrollState())
657 ) {
658 var singleParagraph by remember { mutableStateOf(boolOptions[0]) }
659 var selectedOverflow by remember { mutableStateOf(overflowOptions[0]) }
660 var singleLinePerPar by remember { mutableStateOf(boolOptions[1]) }
661 var width by remember { mutableFloatStateOf(250f) }
662 var height by remember { mutableFloatStateOf(50f) }
663 var letterSpacing by remember { mutableFloatStateOf(0f) }
664 var textAlign by remember { mutableStateOf(TextAlign.Left) }
665 var softWrap by remember { mutableStateOf(true) }
666
667 TextOverflowDemo(
668 singleParagraph,
669 selectedOverflow,
670 singleLinePerPar,
671 width.dp,
672 height.dp,
673 letterSpacing.sp,
674 textAlign,
675 softWrap
676 )
677
678 Row(Modifier.fillMaxWidth()) {
679 Column(Modifier.selectableGroup().weight(1f)) {
680 Text("TextOverflow", fontWeight = FontWeight.Bold)
681 overflowOptions.forEach {
682 Row(
683 Modifier.fillMaxWidth()
684 .selectable(
685 selected = (it == selectedOverflow),
686 onClick = { selectedOverflow = it },
687 role = Role.RadioButton
688 ),
689 verticalAlignment = Alignment.CenterVertically
690 ) {
691 RadioButton(
692 selected = (it == selectedOverflow),
693 onClick = null // null recommended for accessibility with screenreaders
694 )
695 Text(text = it.toString())
696 }
697 }
698 }
699 Column(Modifier.selectableGroup().weight(1f)) {
700 Text("Paragraph", fontWeight = FontWeight.Bold)
701 boolOptions.forEach {
702 Row(
703 Modifier.fillMaxWidth()
704 .selectable(
705 selected = (it == singleParagraph),
706 onClick = { singleParagraph = it },
707 role = Role.RadioButton
708 ),
709 verticalAlignment = Alignment.CenterVertically
710 ) {
711 RadioButton(
712 selected = (it == singleParagraph),
713 onClick = null // null recommended for accessibility with screenreaders
714 )
715 Text(text = if (it) "Single" else "Multi")
716 }
717 }
718 }
719 Column(Modifier.selectableGroup().weight(1f)) {
720 Text("Single line", fontWeight = FontWeight.Bold)
721 boolOptions.forEach {
722 Row(
723 Modifier.fillMaxWidth()
724 .selectable(
725 selected = (it == singleLinePerPar),
726 onClick = { singleLinePerPar = it },
727 role = Role.RadioButton
728 ),
729 verticalAlignment = Alignment.CenterVertically
730 ) {
731 RadioButton(selected = (it == singleLinePerPar), onClick = null)
732 Text(text = it.toString())
733 }
734 }
735 }
736 }
737 Column {
738 Text("Width " + "%.1f".format(width) + "dp")
739 Slider(width, { width = it }, valueRange = 30f..300f)
740 }
741 Column {
742 Text("Height " + "%.1f".format(height) + "dp")
743 Slider(height, { height = it }, valueRange = 5f..300f)
744 }
745 Column {
746 Text("Letter spacing " + "%.1f".format(letterSpacing) + "sp")
747 Slider(letterSpacing, { letterSpacing = it }, valueRange = -4f..8f, steps = 11)
748 }
749 Row(Modifier.fillMaxWidth()) {
750 Column(Modifier.weight(1f)) {
751 Text("Text Align", fontWeight = FontWeight.Bold)
752 textAlignments.forEach {
753 Row(
754 Modifier.fillMaxWidth()
755 .selectable(
756 selected = (it == textAlign),
757 onClick = { textAlign = it },
758 role = Role.RadioButton
759 ),
760 verticalAlignment = Alignment.CenterVertically
761 ) {
762 RadioButton(selected = (it == textAlign), onClick = null)
763 Text(text = it.toString())
764 }
765 }
766 }
767 Column(Modifier.weight(1f)) {
768 Text("Soft wrap", fontWeight = FontWeight.Bold)
769 boolOptions.forEach {
770 Row(
771 Modifier.fillMaxWidth()
772 .selectable(
773 selected = (it == softWrap),
774 onClick = { softWrap = it },
775 role = Role.RadioButton
776 ),
777 verticalAlignment = Alignment.CenterVertically
778 ) {
779 RadioButton(selected = (it == softWrap), onClick = null)
780 Text(text = it.toString())
781 }
782 }
783 }
784 }
785 }
786 }
787
788 @Composable
TextOverflowDemonull789 private fun ColumnScope.TextOverflowDemo(
790 singleParagraph: Boolean,
791 textOverflow: TextOverflow,
792 singeLine: Boolean,
793 width: Dp,
794 height: Dp,
795 letterSpacing: TextUnit,
796 textAlign: TextAlign,
797 softWrap: Boolean
798 ) {
799 Box(Modifier.weight(1f).fillMaxWidth()) {
800 val text =
801 if (singleParagraph) {
802 AnnotatedString(longText)
803 } else {
804 buildAnnotatedString {
805 append(longText)
806 withStyle(ParagraphStyle(textAlign = TextAlign.End)) {
807 append("This is a second paragraph.")
808 }
809 }
810 }
811 val textStyle =
812 TextStyle(fontSize = fontSize6, letterSpacing = letterSpacing, textAlign = textAlign)
813 BasicText(
814 text = text,
815 modifier =
816 Modifier.align(Alignment.Center)
817 .background(Color.Magenta)
818 .widthIn(max = width)
819 .heightIn(max = height),
820 style = textStyle,
821 overflow = textOverflow,
822 maxLines = if (singeLine) 1 else Int.MAX_VALUE,
823 softWrap = softWrap
824 )
825 }
826 }
827
828 @Composable
TextDemoInlineContentnull829 fun TextDemoInlineContent() {
830 val inlineContentId = "box"
831 val inlineTextContent =
832 InlineTextContent(
833 placeholder =
834 Placeholder(
835 width = 5.em,
836 height = 1.em,
837 placeholderVerticalAlign = PlaceholderVerticalAlign.AboveBaseline
838 )
839 ) {
840 val colorAnimation = rememberInfiniteTransition()
841 val color by
842 colorAnimation.animateColor(
843 initialValue = Color.Red,
844 targetValue = Color.Blue,
845 animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse)
846 )
847 Box(modifier = Modifier.fillMaxSize().background(color))
848 }
849
850 Text(
851 text =
852 buildAnnotatedString {
853 append("Here is a wide inline composable ")
854 appendInlineContent(inlineContentId)
855 append(" that is repeatedly changing its color.")
856 },
857 inlineContent = mapOf(inlineContentId to inlineTextContent),
858 modifier = Modifier.fillMaxWidth()
859 )
860
861 SecondTagLine(tag = "RTL Layout")
862 CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
863 Text(
864 text =
865 buildAnnotatedString {
866 append("Here is a wide inline composable ")
867 appendInlineContent(inlineContentId)
868 append(" that is repeatedly changing its color.")
869 },
870 inlineContent = mapOf(inlineContentId to inlineTextContent),
871 modifier = Modifier.fillMaxWidth()
872 )
873 }
874
875 SecondTagLine(tag = "Bidi Text - LTR/RTL")
876 Text(
877 text =
878 buildAnnotatedString {
879 append("$displayText ")
880 appendInlineContent(inlineContentId)
881 append("$displayTextArabic ")
882 },
883 inlineContent = mapOf(inlineContentId to inlineTextContent),
884 modifier = Modifier.fillMaxWidth()
885 )
886
887 SecondTagLine(tag = "Bidi Text - RTL/LTR")
888 Text(
889 text =
890 buildAnnotatedString {
891 append("$displayTextArabic ")
892 appendInlineContent(inlineContentId)
893 append("$displayText ")
894 },
895 inlineContent = mapOf(inlineContentId to inlineTextContent),
896 modifier = Modifier.fillMaxWidth()
897 )
898 }
899
900 @OptIn(ExperimentalMaterialApi::class)
901 @Composable
EllipsizeDemonull902 fun EllipsizeDemo() {
903 var softWrap by remember { mutableStateOf(true) }
904 var ellipsis by remember { mutableStateOf(true) }
905 var withSpans by remember { mutableStateOf(false) }
906 val lineHeight = remember { mutableStateOf(16.sp) }
907 val heightRestriction = remember { mutableStateOf(45.dp) }
908
909 Column {
910 ListItem(
911 Modifier.selectable(softWrap) { softWrap = !softWrap },
912 trailing = { Switch(softWrap, null) }
913 ) {
914 Text("Soft wrap")
915 }
916 ListItem(
917 Modifier.selectable(ellipsis) { ellipsis = !ellipsis },
918 trailing = { Switch(ellipsis, null) }
919 ) {
920 Text("Ellipsis")
921 }
922 ListItem(
923 Modifier.selectable(withSpans) { withSpans = !withSpans },
924 trailing = { Switch(withSpans, null) },
925 secondaryText = { Text("Text with spans") }
926 ) {
927 Text("Spans")
928 }
929
930 Row(horizontalArrangement = Arrangement.SpaceAround, modifier = Modifier.fillMaxWidth()) {
931 Column(horizontalAlignment = Alignment.CenterHorizontally) {
932 IconButton({
933 heightRestriction.value = (heightRestriction.value + 5.dp).coerceAtMost(300.dp)
934 }) {
935 Icon(Icons.Default.KeyboardArrowUp, "Increase height")
936 }
937 Text("Max height ${heightRestriction.value}")
938 IconButton({
939 heightRestriction.value = (heightRestriction.value - 5.dp).coerceAtLeast(0.dp)
940 }) {
941 Icon(Icons.Default.KeyboardArrowDown, "Decrease height")
942 }
943 }
944
945 Column(horizontalAlignment = Alignment.CenterHorizontally) {
946 IconButton({
947 lineHeight.value = ((lineHeight.value.value + 2f)).coerceAtMost(100f).sp
948 }) {
949 Icon(Icons.Default.KeyboardArrowUp, "Increase line height")
950 }
951 Text("Line height ${lineHeight.value.value.toInt().sp}")
952 IconButton({
953 lineHeight.value = ((lineHeight.value.value - 2f)).coerceAtLeast(5f).sp
954 }) {
955 Icon(Icons.Default.KeyboardArrowDown, "Decrease line height")
956 }
957 }
958 }
959
960 val fontSize = 16.sp
961 val text =
962 "This is a very-very " +
963 "long text that has a limited height and width to test how it's ellipsized." +
964 " This is a second sentence of the text."
965 val textWithSpans = buildAnnotatedString {
966 withStyle(SpanStyle(fontSize = fontSize / 2)) {
967 append("This is a very-very long text that has ")
968 }
969 withStyle(SpanStyle(fontSize = fontSize * 2)) { append("a limited height") }
970 append(" and width to test how it's ellipsized. This is a second sentence of the text.")
971 }
972 Text(
973 text = if (withSpans) textWithSpans else AnnotatedString(text),
974 fontSize = fontSize,
975 lineHeight = lineHeight.value,
976 modifier =
977 Modifier.background(Color.Magenta)
978 .width(200.dp)
979 .heightIn(max = heightRestriction.value),
980 softWrap = softWrap,
981 overflow = if (ellipsis) TextOverflow.Ellipsis else TextOverflow.Clip
982 )
983 }
984 }
985
986 @Composable
AutoSizeTextDemonull987 fun AutoSizeTextDemo() {
988 val text = "This is a sample string!"
989 Column(Modifier.fillMaxWidth()) {
990 // This text will be sized according to default values of AutoSize.StepBased
991 BasicText(
992 text,
993 modifier = Modifier.border(1.dp, Color.Red).height(40.dp),
994 autoSize = TextAutoSize.StepBased()
995 )
996 // This text can either have a font size of 10, 20, 30, 40, 50 or 60 sp
997 BasicText(
998 text,
999 autoSize =
1000 TextAutoSize.StepBased(minFontSize = 10.sp, maxFontSize = 60.sp, stepSize = 10.sp)
1001 )
1002 }
1003 }
1004