Basic components in Jetpack Compose

Button

default style

ButtonAny component lambdacan be passed in the block Composable, but generally one is placed Textinside

Button(onClick = {
    
     println("确认onClick") }) {
    
    
   Text("默认样式")
}

insert image description here

The width and height of the button

If you want to be wider or taller Button, you can Modifiermodify the width and height, for example, Columnyou can Modifier.fillMaxWidth()specify to fill up the parent control in , and you can also modify the rounded arc by shapeparametersButton

Column(horizontalAlignment = Alignment.CenterHorizontally) {
    
    
    Button(
        onClick = {
    
     println("确认onClick") },
        modifier = Modifier.fillMaxWidth().padding(all = 5.dp),
        shape = RoundedCornerShape(15.dp)
    ) {
    
    
        Text("指定圆角弧度")
    }
}

insert image description here

button border

Specify the border of the button via the parameter Buttonofborder

Button(
     onClick = {
    
     println("click the button") },
     border = BorderStroke(1.dp, Color.Red)
 ) {
    
    
     Text(text = "按钮的边框")
 }

insert image description here

disabled state of the button

Specify the disabled state of the button via the parameter Buttonofenabled

Button(
   onClick = {
    
     println("click the button") },
   enabled = false
) {
    
    
    Text(text = "禁用的按钮")
}

insert image description here

button content

Since Buttonthe internal use of a Rowcomponent is used for packaging, multiple components lambdacan actually be passed in the block , and they will be arranged in horizontal rowsComposable

Button(onClick = {
    
     println("喜欢onClick") }) {
    
    
    Icon(
        // Material 库中的图标,Icons.Filled下面有很多自带的图标
        Icons.Filled.Favorite,
        contentDescription = null,
        modifier = Modifier.size(ButtonDefaults.IconSize)
    ) 
    Spacer(Modifier.size(ButtonDefaults.IconSpacing))
    Text("喜欢")
}

insert image description here

Customize the background of the button pressed state

An object can interactionSourcebe specified through the parameter, and the object can be used to collect the interactive state of the button through the method of the object , and then create different background or text colors according to the state valueMutableInteractionSourceremembercollectIsPressedAsState()

// 创建 Data class 来记录不同状态下按钮的样式
data class ButtonState(var text: String, var textColor: Color, var buttonColor: Color)

// 获取按钮的状态
val interactionState = remember {
    
     MutableInteractionSource() }
val pressState = interactionState.collectIsPressedAsState()
// 使用 Kotlin 的解构方法
val (text, textColor, buttonColor) = when {
    
    
    pressState.value -> ButtonState("按下状态", Color.Red, Color.Black)
    else -> ButtonState( "正常状态", Color.White, Color.Red)
}
Button(
    onClick = {
    
     println(" onClick") },
    interactionSource = interactionState,
    elevation = null,
    shape = RoundedCornerShape(50),
    colors = ButtonDefaults.buttonColors(backgroundColor = buttonColor, contentColor = textColor),
    modifier = Modifier
        .width(200.dp)
        .height(IntrinsicSize.Min)
) {
    
    
    Text(text = text, fontSize = 16.sp)
}

insert image description here

text button

In normal state, it is just a text, but it can be clicked, and there will be a ripple effect when clicked

TextButton(onClick = {
    
     println("click the TextButton") },) {
    
    
    Text(text = "文本按钮")
}

insert image description here

border button

OutlinedButton(onClick = {
    
     println("click the OutlinedButton") }) {
    
    
    Text(text = "边框按钮")
}

insert image description here

icon button

Row {
    
    
   IconButton(onClick = {
    
     println("click the Add")}) {
    
    
        Icon(imageVector = Icons.Default.Add, contentDescription = null)
    }
    IconButton(onClick = {
    
     println("click the Search")}) {
    
    
        Icon(imageVector = Icons.Default.Search, contentDescription = null)
    }
    IconButton(onClick = {
    
     println("click the ArrowBack")}) {
    
    
        Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null)
    }
    IconButton(onClick = {
    
     println("click the Done")}) {
    
    
        Icon(imageVector = Icons.Default.Done, contentDescription = null)
    } 
}

insert image description here

Cancel IconButton's ripple

IconButtonIn the source code, the parameter of Boxin is actually set to ripple, we just need to copy the source code and add it to our own project, and set it toModifier.clickableIndicationindicationnull

@Composable
fun IconButtonWithNoRipple(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    interactionSource: MutableInteractionSource = remember {
    
     MutableInteractionSource() },
    content: @Composable () -> Unit
) {
    
    
    Box(
        modifier =
        modifier
            .size(48.dp)
            .clickable(
                onClick = onClick,
                enabled = enabled,
                role = Role.Button,
                interactionSource = interactionSource,
                indication = null
            ),
        contentAlignment = Alignment.Center
    ) {
    
    
        content()
    }
}

use:

var myColor by remember {
    
     mutableStateOf(Color.Gray) }
var flg by remember {
    
     mutableStateOf(false) }
IconButtonWithNoRipple(onClick = {
    
    
    flg = !flg
    myColor = if (flg) Color.Red else Color.Gray
}) {
    
    
    Icon(imageVector = Icons.Default.Favorite, contentDescription = null, tint = myColor)
}

insert image description here

FloatingActionButton

FloatingActionButton(
	onClick = {
    
     println("click the FloatingActionButton") },
    shape = CircleShape
) {
    
    
    Icon(Icons.Filled.Add, contentDescription = "Add")
}
 
ExtendedFloatingActionButton(
    icon = {
    
     Icon(Icons.Filled.Favorite, contentDescription = null) },
    text = {
    
     Text("添加到我喜欢的") },
    onClick = {
    
     println("click the ExtendedFloatingActionButton") },
    shape = RoundedCornerShape(5.dp)
)

insert image description here

ElevatedButton

Note that this is in androidx.compose.material3the package. elevationThe height effect of the button can be specified by the parameter

 ElevatedButton(
	 onClick = {
    
       },
     elevation = ButtonDefaults.buttonElevation(defaultElevation = 5.dp, pressedElevation = 10.dp)
 ) {
    
    
     Text(text = "ElevatedButton")
 }

insert image description here
You can also not specify elevation, it has a default value, the default effect is as follows
insert image description here

Divider

DividerIs a dividing line, you can specify the color, thickness, etc.

@Composable
fun DividerExample() {
    
    
    Column {
    
    
        Text("11111111111")
        Divider()
        Text("2222222222222")
        Divider(color = Color.Blue, thickness = 2.dp)
        Text("33333333333")
        Text("4444444444")
        androidx.compose.material.Divider(color = Color.Red, thickness = 10.dp, startIndent = 10.dp)
        Text("66666666666666666")
        Divider(color = Color.Blue, modifier = Modifier.padding(horizontal = 15.dp))
        Text("888888888888888888")
    }
}

insert image description here

Icon

IconThe main parameters of:

  • ImageVector: vector graphics object, which can display icons in SVG format
  • ImageBitmap: Bitmap object, which can display icons in JPG, PNG and other formats
  • tint: the color of the icon
  • Painter: Represents a custom brush, which can be used to Canvasdirectly draw icons on . In addition to directly passing in specific types of instances, we can also res/set icons through image resources under .

ImageVectorand ImageBitmapboth provide the corresponding Drawablemethod of loading resources, vectorResourcewhich are used to load a vector XML, imageResourceused to load jpgor pnga picture. painterResourceBoth of the above two types Drawableare supported, and the corresponding brushes will be created internally according to different types of resources to draw icons.

Icon(
 	imageVector = Icons.Filled.Favorite,
    contentDescription = null,
    tint = Color.Red
)
Icon(
    imageVector = ImageVector.vectorResource(id = R.drawable.ic_launcher_foreground),
    contentDescription = "矢量图资源",
    tint = Color.Blue
)

insert image description here

IconLoading resource image shows black image not loaded?

  • Don't panic, because the default tintmode is LocalContentColor.current, we need to remove its default coloring mode and tintset the property toColor.Unspecified
Icon(
   bitmap = ImageBitmap.imageResource(id = R.drawable.ic_head3),
   contentDescription = "图片资源",
   tint = Color.Unspecified,
   modifier = Modifier.size(100.dp).clip(CircleShape),
)

IconSupports any type of image resource, and can be used as an image component

Icon(
     painter = painterResource(id = R.drawable.ic_head),
     contentDescription = "任意类型资源",
     tint = Color.Unspecified,
     modifier = Modifier.size(100.dp)
         .clip(CircleShape)
         .border(1.dp, MaterialTheme.colorScheme.primary, CircleShape),
 )

insert image description here

Image

ImageWhen a component loads a local resource image, it Iconis similar to the use of , and the resource image is loaded by painterspecifying parameterspainterResourceR.drawable

@Composable
fun ImageExample() {
    
    
     Column(modifier = Modifier.padding(10.dp)){
    
    
         Row{
    
    
             Image(
                 painter = painterResource(id = R.drawable.ic_sky),
                 contentDescription = null,
                 modifier = Modifier.size(100.dp),
             )
             Surface(
                 shape = CircleShape,
                 border = BorderStroke(5.dp, Color.Red)
             ) {
    
    
                 Image(
                     painter = painterResource(id = R.drawable.ic_sky),
                     contentDescription = null,
                     modifier = Modifier.size(100.dp),
                     contentScale = ContentScale.Crop
                 )
             }
             Image(
                 painter = painterResource(id = R.drawable.ic_sky),
                 contentDescription = null,
                 modifier = Modifier
                     .size(100.dp)
                     .clip(CircleShape),
                 contentScale = ContentScale.Crop,
             )
             Image(
                 painter = painterResource(id = R.drawable.ic_sky),
                 contentDescription = null,
                 modifier = Modifier.size(80.dp),
                 colorFilter = ColorFilter.tint(Color.Red, blendMode = BlendMode.Color)
             )
         }
     }
}

insert image description here
Among them, contentScalethe parameter is the image scaling type, and the value is in ContentScalethe companion object. The meaning refers to the native scaling type ImageViewof Android scaleType, which is almost similar.

In addition, there are two ways to set the circular image, as in the above code, one is to use Surfacethe component to wrap it, and then specify Surfacethe shapeas CircleShape, and the other is to use Modifier.clip(CircleShape)to directly act on Imagethe component. However, in both methods, the width and height must be set to fixed and equal values, otherwise it is not a perfect circle.

Sometimes when it is set to a circle, the top and bottom of the picture are clipped,

insert image description here

This is because the default parameter Imageof the source code in contentScaleis ContentScale.Fit, that is, to maintain the aspect ratio of the image and shrink it to the full display of the entire image. And ContentScale.Cropalso maintain the aspect ratio, but try to make the width or height completely full. So we contentScaleset ContentScale.Cropto solve this problem.

Compose's built-in Imagecan only load image files in the resource manager. If you want to load network images or files in other local paths, you can use to Coilload network images: https://coil-kt.github.io/coil/compose /

@Composable
fun CoilImageLoaderExample() {
    
    
    Row {
    
    
        Image(
            painter = rememberAsyncImagePainter("https://picsum.photos/300/300"),
            contentDescription = null
        )
        AsyncImage(
            model = "https://picsum.photos/300/300",
            contentDescription = null
        )
        AsyncImage(
            model = ImageRequest.Builder(LocalContext.current)
                .data("https://picsum.photos/300/300")
                .crossfade(true)
                .build(),
            placeholder = painterResource(R.drawable.ic_launcher_background),
            contentDescription = null,
            contentScale = ContentScale.Crop,
            // error = painterResource(),
            onSuccess = {
    
     success ->

            },
            onError = {
    
     error ->

            },
            onLoading = {
    
     loading ->

            },
            modifier = Modifier.clip(CircleShape)
        )
    }
}

SubcomposeAsyncImage

SubcomposeAsyncImageThe final size of the image will be determined according to the constraint space of the component, which means that before the image is loaded, the SubcomposeAsyncImageconstraint information needs to be obtained in advance, and Subcomposelayoutthe constraint information of the parent component or the constraint information of other components can be obtained before the subcomponent is synthesized. SubcomposeAsyncImageIt is achieved by
relying on the ability, the subcomponent is the content we pass in, and it will be combined when the component is measured.SubcomposelayoutcontentSubcomposeAsyncImage

@Composable
fun CoilImageLoaderExample2() {
    
    
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
    
    
        SubcomposeAsyncImage(
            model = "https://picsum.photos/350/350" ,
            loading = {
    
     CircularProgressIndicator() },
            contentDescription = null,
            modifier = Modifier.size(200.dp)
        )
        SubcomposeAsyncImage(
            model = "https://picsum.photos/400/400" ,
            contentDescription = null,
            modifier = Modifier.size(200.dp)
        ) {
    
    
            val state = painter.state
            when(state) {
    
    
                is AsyncImagePainter.State.Loading -> CircularProgressIndicator()
                is AsyncImagePainter.State.Error -> Text("${
      
      state.result.throwable}")
                is AsyncImagePainter.State.Success -> SubcomposeAsyncImageContent()
                is AsyncImagePainter.State.Empty -> Text("Empty")
            }
        }
        // 如果指定了图片加载到内存时的尺寸大小,那么在加载时就不会获取组件的约束信息
        SubcomposeAsyncImage(
            model = ImageRequest.Builder(LocalContext.current)
                .data("https://picsum.photos/800/600")
                .size(800, 600)
                .crossfade(true)
                .build(),
            contentDescription = null,
        ) {
    
    
            val state = painter.state
            when(state) {
    
    
                is AsyncImagePainter.State.Loading -> CircularProgressIndicator()
                is AsyncImagePainter.State.Error -> Text("${
      
      state.result.throwable}")
                is AsyncImagePainter.State.Success -> SubcomposeAsyncImageContent()
                is AsyncImagePainter.State.Empty -> Text("Empty")
            }
        }
    }
}

Coil loads network svg images

@Composable
fun CoilSVGExample() {
    
    
    Row {
    
    
        // 加载网络svg
        val context = LocalContext.current
        val imageLoader = ImageLoader.Builder(context)
            .components {
    
     add(SvgDecoder.Factory()) }
            .build()
        Image(
            painter = rememberAsyncImagePainter (
                "https://coil-kt.github.io/coil/images/coil_logo_black.svg",
                imageLoader = imageLoader
            ),
            contentDescription = null,
            modifier = Modifier.size(100.dp)
        )
        // svg放大和缩小使用Coil有问题,不是矢量图,可以使用Landscapist:https://github.com/skydoves/Landscapist
        var flag by remember {
    
     mutableStateOf(false) }
        val size by animateDpAsState(targetValue = if(flag) 300.dp else 100.dp)
        CoilImage(
            imageModel = {
    
     "https://coil-kt.github.io/coil/images/coil_logo_black.svg" },
            imageOptions = ImageOptions(
                contentScale = ContentScale.Crop,
                alignment = Alignment.Center
            ),
            modifier = Modifier
                .size(size)
                .clickable(
                    onClick = {
    
     flag = !flag },
                    indication = null,
                    interactionSource = MutableInteractionSource()
                ),
            imageLoader = {
    
     imageLoader }
        )
    }
}

ProgressIndicator

ProgressIndicatorThe progress bar is also divided into two types in Compose, horizontal LinearProgressIndicatorand circular CircularProgressIndicator. If the progress value is not specified, it is a progress bar with infinite animation.

@Composable
fun ProgressIndicatorExample() {
    
    
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
    
    
        CircularProgressIndicator(
            Modifier.size(100.dp),
            color = Color.Red,
            strokeWidth = 5.dp
        )
        var progress by remember {
    
     mutableStateOf(0.5f) }
        Button(onClick = {
    
     progress += 0.1f }) {
    
     Text(text = "进度") }
        CircularProgressIndicator(
            modifier = Modifier.size(100.dp),
            progress = progress,
            strokeWidth = 5.dp,
            color = Color.Blue,
        )
        Spacer(modifier = Modifier.height(20.dp))
        LinearProgressIndicator(
            color = Color.Red,
            trackColor = Color.Green,
        )
        Spacer(modifier = Modifier.height(20.dp))
        LinearProgressIndicator(
            progress = progress,
            modifier = Modifier
                .width(300.dp)
                .height(10.dp)
                .clip(RoundedCornerShape(5.dp)),
        )
    }
}

insert image description here

Slider

SliderEquivalent to Android's originalSeekBar

@Composable
fun SliderExample() {
    
    
    var progress by remember{
    
     mutableStateOf(0f)}
    Column(modifier = Modifier.padding(15.dp)) {
    
    
        Text("${
      
      "%.1f".format(progress * 100)}%")
        Slider(
            value = progress,
            onValueChange = {
    
     progress = it },
        )
        Slider(
            value = progress,
            colors = SliderDefaults.colors(
                thumbColor = Color.Magenta, // 圆圈的颜色
                activeTrackColor = Color.Blue, // 滑条经过的部分的颜色
                inactiveTrackColor = Color.LightGray // 滑条未经过的部分的颜色
            ),
            onValueChange = {
    
    
                progress = it
                println(it)
            },
        )
    }
}

insert image description here

RangeSlider

The SeekBar for range selection is currently an experimental API

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun SliderExample2() {
    
    
    //注意此时值是一个范围
    var values by remember {
    
     mutableStateOf(5f..30f) }
    RangeSlider(
        value = values,
        onValueChange = {
    
    
            values = it
            println(it.toString())
        },
        valueRange = 0f..100f,
        steps = 3
    )
}

insert image description here

Spacer

a placeholder component

@Composable
fun SpacerExample() {
    
    
    Column {
    
    
        Row {
    
    
            MyText("First", Color.Blue)

            Spacer(Modifier.weight(1f))

            MyText("Second", Color.Blue)

            Spacer(Modifier.weight(1f))

            MyText("Three", Color.Blue)
        }
        Row {
    
    
            MyText("First", Color.Red)

            Spacer(Modifier.width(10.dp))

            MyText("Second", Color.Red)

            Spacer(Modifier.width(10.dp))

            MyText("Three", Color.Red)
        }
        Row {
    
    
            MyText("First", Color.Blue)

            Spacer(Modifier.weight(1f))

            MyText("Second", Color.Blue)

            Spacer(Modifier.weight(2f))

            MyText("Three", Color.Blue)
        }
        Spacer(Modifier.height(50.dp))

        MyText("Column Item 0", Color.Blue)

        Spacer(Modifier.height(10.dp))

        MyText("Column Item 1", Color.Red)

        Spacer(Modifier.weight(1f))

        MyText("Column Item 2", Color.Blue)

        Spacer(Modifier.weight(1f))

        MyText("Column Item 3", Color.Blue)
    }
}

@Composable
fun MyText(text : String, color : Color) {
    
    
    Text(text,
        modifier = Modifier.background(color).padding(5.dp),
        fontSize = 20.sp,
        color = Color.White
    )
}

insert image description here

Switch、Checkbox、RadioButton

Example of use

@Composable
fun SwitchExample() {
    
    
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
    
    
        var value by remember {
    
     mutableStateOf(false) }
        Switch(
            checked = value,
            onCheckedChange = {
    
     value = it },
            colors = SwitchDefaults.colors(
                checkedThumbColor = Color.Blue,
                checkedTrackColor = Color.Blue,
                uncheckedThumbColor = Color.DarkGray,
                uncheckedTrackColor = Color.Gray
            ),
        )
        var boxState by remember {
    
     mutableStateOf(true) }
        Checkbox(
            checked = boxState,
            onCheckedChange = {
    
     boxState = it },
            colors = CheckboxDefaults.colors(
                checkedColor = Color.Red,
                uncheckedColor = Color.Gray
            )
        )
        var selectState by remember {
    
     mutableStateOf(true) }
        RadioButton(
            selected = selectState,
            onClick = {
    
     selectState = !selectState },
            colors = RadioButtonDefaults.colors(
                selectedColor = Color.Blue,
                unselectedColor = Color.Gray
            )
        )
        Text("CheckBox构建多选组:")
        CheckBoxMultiSelectGroup()
        Text("CheckBox构建单选组:")
        CheckBoxSingleSelectGroup()
        Text("RadioButton构建多选组:")
        RadioButtonMultiSelectGroup()
        Text("RadioButton构建单选组:")
        RadioButtonSingleSelectGroup()
    }
}

@Composable
fun CheckBoxMultiSelectGroup() {
    
    
    var checkedList by remember {
    
     mutableStateOf(listOf(false, false)) }
    Column {
    
    
        checkedList.forEachIndexed {
    
     i, item ->
            Checkbox(checked = item, onCheckedChange = {
    
    
                checkedList = checkedList.mapIndexed {
    
     j, b ->
                    if (i == j) it else b
                }
            })
        }
    }
}

@Composable
fun CheckBoxSingleSelectGroup() {
    
    
    var checkedList by remember {
    
     mutableStateOf(listOf(false, false)) }
    Column {
    
    
        checkedList.forEachIndexed {
    
     i, item ->
            Checkbox(checked = item, onCheckedChange = {
    
    
                checkedList = List(checkedList.size) {
    
     j -> i == j }
            })
        }
    }
}

@Composable
fun RadioButtonMultiSelectGroup() {
    
    
    var checkedList by remember {
    
     mutableStateOf(listOf(false, false)) }
    LazyColumn {
    
    
        items(checkedList.size) {
    
     i ->
            RadioButton(selected = checkedList[i], onClick = {
    
    
                checkedList = checkedList.mapIndexed {
    
     j, b ->
                    if (i == j) !b else b
                }
            })
        }
    }
}

@Composable
fun RadioButtonSingleSelectGroup() {
    
    
    var checkedList by remember {
    
     mutableStateOf(listOf(false, false)) }
    LazyColumn {
    
    
        items(checkedList.size) {
    
     i ->
            RadioButton(selected = checkedList[i], onClick = {
    
    
                checkedList = List(checkedList.size) {
    
     j -> i == j }
            })
        }
    }
}

insert image description here

Chip

Components similar to labels can set borders and colors, etc., and can respond to click events.

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ChipSample() {
    
    
    val context = LocalContext.current
    Chip(onClick = {
    
     context.showToast("Action Chip") }) {
    
    
        Text("Action Chip")
    }
}

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun OutlinedChipWithIconSample() {
    
    
    val context = LocalContext.current
    Chip(
        onClick = {
    
     context.showToast("Change settings")},
        border = ChipDefaults.outlinedBorder,
        colors = ChipDefaults.outlinedChipColors(),
        leadingIcon = {
    
    
            Icon(Icons.Filled.Settings, contentDescription = "Localized description")
        }
    ) {
    
    
        Text("Change settings")
    }
}

insert image description here

ChipThe default is not selected, but it can be achieved by changing the background color when clicking, for example:

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun SelectableChip(
    modifier: Modifier = Modifier,
    chipText: String,
    isSelected: Boolean,
    onSelectChanged: (Boolean) -> Unit
) {
    
    
    Chip(
        modifier = modifier,
        onClick = {
    
     onSelectChanged(!isSelected) },
        border = if (isSelected) ChipDefaults.outlinedBorder else null,
        colors = ChipDefaults.chipColors(
            backgroundColor = when {
    
    
                isSelected -> MaterialTheme.colors.primary.copy(alpha = 0.75f)
                else -> MaterialTheme.colors.onSurface.copy(alpha = 0.12f)
                    .compositeOver(MaterialTheme.colors.surface)
            }
        ),
    ) {
    
    
        Text(chipText, color = if (isSelected) Color.White else Color.Black)
    }

}
@Composable
fun ChipGroupSingleLineSample() {
    
    
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
    
    
        Row(modifier = Modifier.horizontalScroll(rememberScrollState())) {
    
    
            repeat(9) {
    
     index ->
                var selected by remember {
    
     mutableStateOf(false) }
                SelectableChip(
                    modifier = Modifier.padding(horizontal = 4.dp),
                    chipText = "Chip $index",
                    isSelected = selected,
                    onSelectChanged = {
    
     selected = it}
                )
            }
        }
    }
}

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun ChipGroupReflowSample() {
    
    
    Column {
    
    
        FlowRow(
            Modifier.fillMaxWidth(1f)
                .wrapContentHeight(align = Alignment.Top),
            horizontalArrangement = Arrangement.Start,
        ) {
    
    
            repeat(10) {
    
     index ->
                var selected by remember {
    
     mutableStateOf(false) }
                SelectableChip(
                    modifier = Modifier.padding(horizontal = 4.dp)
                        .align(alignment = Alignment.CenterVertically),
                    chipText = "Chip $index",
                    isSelected = selected,
                    onSelectChanged = {
    
     selected = it}
                )
            }
        }
    }
}

insert image description here

FilterChip

FilterChipIt has a selected state Chip, and you can configure the background color, content color, and selected icon in the selected state.

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun FilterChipSample() {
    
    
    var selected by remember {
    
     mutableStateOf(false) }
    FilterChip(
        selected = selected,
        onClick = {
    
     selected = !selected },
        colors = ChipDefaults.filterChipColors(
            selectedBackgroundColor = MaterialTheme.colors.primary.copy(alpha = 0.75f),
            selectedContentColor = Color.White,
            selectedLeadingIconColor = Color.White
        ),
        selectedIcon = {
    
    
            Icon(
                imageVector = Icons.Filled.Done,
                contentDescription = "Localized Description",
                modifier = Modifier.requiredSize(ChipDefaults.SelectedIconSize)
            )
        }
    ) {
    
    
        Text("Filter chip")
    }
}

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun OutlinedFilterChipSample() {
    
    
    var selected by remember {
    
     mutableStateOf(false) }
    FilterChip(
        selected = selected,
        onClick = {
    
     selected = !selected },
        border = if (!selected) ChipDefaults.outlinedBorder
                else BorderStroke(ChipDefaults.OutlinedBorderSize, MaterialTheme.colors.primary),
        colors = ChipDefaults.outlinedFilterChipColors(
            selectedBackgroundColor = Color.White,
            selectedContentColor = MaterialTheme.colors.primary,
            selectedLeadingIconColor = MaterialTheme.colors.primary
        ),
        selectedIcon = {
    
    
            Icon(
                imageVector = Icons.Filled.Done,
                contentDescription = "Localized Description",
                modifier = Modifier.requiredSize(ChipDefaults.SelectedIconSize)
            )
        }
    ) {
    
    
        Text("Filter chip")
    }
}

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun FilterChipWithLeadingIconSample() {
    
    
    var selected by remember {
    
     mutableStateOf(false) }
    FilterChip(
        selected = selected,
        onClick = {
    
     selected = !selected },
        colors = ChipDefaults.filterChipColors(
            selectedBackgroundColor = MaterialTheme.colors.primary.copy(alpha = 0.75f),
            selectedContentColor = Color.White,
            selectedLeadingIconColor = Color.White
        ),
        leadingIcon = {
    
    
            Icon(
                imageVector = Icons.Filled.Home,
                contentDescription = "Localized description",
                modifier = Modifier.requiredSize(ChipDefaults.LeadingIconSize)
            )
        },
        selectedIcon = {
    
    
            Icon(
                imageVector = Icons.Filled.Done,
                contentDescription = "Localized Description",
                modifier = Modifier.requiredSize(ChipDefaults.SelectedIconSize)
            )
        }
    ) {
    
    
        Text("Filter chip")
    }
}

insert image description here

Text

@Composable
fun TextExample() {
    
    
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.padding(10.dp)
    ) {
    
    
        Text(
            text = "Hello Compose!",
            color = Color.Red,
            fontSize = 16.sp,
            textDecoration = TextDecoration.LineThrough,
            letterSpacing = 2.sp, // 这里设置dp就会报错,只能用sp,sp是TextUnit和Dp类型不太一样, Text的属性好像只能全部都用sp
        )
        Text(
            text = stringResource(id = R.string.app_name),
            color = Color.Blue,
            fontSize = 16.sp,
            textDecoration = TextDecoration.Underline,
            modifier = Modifier.fillMaxWidth(),
            textAlign = TextAlign.Right, // 居右
            style = TextStyle(color = Color.White) // 这个优先级没有上面直接写出来的优先级高
        )
        Text(
            text = "很长很长很长很长很长很长很长很长很长很长很长很长很长很长",
            color = Color.Red,
            fontSize = 16.sp,
            maxLines = 1,
            overflow = TextOverflow.Ellipsis
        )
        SelectionContainer {
    
    
            Text(
                text = "这段文字支持长按选择复制",
                color = Color.Blue,
                fontSize = 16.sp,
            )
        }
        Text(text = "这段文字支持Click点击",
            modifier = Modifier.clickable( onClick = {
    
     println("点击了") } ),
            fontSize = 16.sp
        )
        ClickableText(
            text = buildAnnotatedString {
    
     append("这段文字支持点击, 且可以获取点击的字符位置") },
            style = TextStyle(color = Color.Blue, fontSize = 16.sp),
            onClick = {
    
    
                println("点击的字符位置是$it")
            }
        )
        // AnnotatedString多样式文本 可内嵌超链接、电话号码等
        val annotatedString = buildAnnotatedString {
    
    
            append("点击登录代表你已知悉")

            pushStringAnnotation("protocol", "https://jetpackcompose.cn/docs/elements/text")
            withStyle(style = SpanStyle(Color.Red, textDecoration = TextDecoration.Underline)){
    
    
                append("用户协议")
            }
            pop()

            append("和")

            pushStringAnnotation("privacy", "https://docs.bughub.icu/compose/")
            withStyle(style = SpanStyle(Color.Red, textDecoration = TextDecoration.Underline)){
    
    
                append("隐私政策")
            }
            pop()
        }
        ClickableText(
            text = annotatedString,
            style = TextStyle(fontSize = 16.sp),
            onClick = {
    
     offset ->
            annotatedString.getStringAnnotations("protocol", offset, offset)
                .firstOrNull()?.let {
    
     annotation ->
                println("点击到了${
      
      annotation.item}")
            }
            annotatedString.getStringAnnotations("privacy", offset, offset)
                .firstOrNull()?.let {
    
     annotation ->
                    println("点击到了${
      
      annotation.item}")
                }
        })
        Text(
            text = "这是一个标题",
            style = MaterialTheme.typography.headlineSmall
        )
        Text(
            text ="你好呀陌生人,我是内容",
            style = MaterialTheme.typography.bodyLarge
        )
        Text(
            text = "测试行高".repeat(20),
            lineHeight = 30.sp,
            fontSize = 16.sp
        )
    }
}

insert image description here
In addition to textAlignsetting Textthe alignment position in the parent component, you can also set the Textalignment position of the text inside the component by

@Composable
fun TextExample2() {
    
    
    Column {
    
    
        Text(
            text = "wrapContentWidth.Start".repeat(1),
            modifier = Modifier
                .width(300.dp)
                .background(Color.Yellow)
                .padding(10.dp)
                .wrapContentWidth(Alignment.Start),
            fontSize = 16.sp,
        )
        Text(
            text = "wrapContentWidth.Center".repeat(1),
            modifier = Modifier
                .width(300.dp)
                .background(Color.Yellow)
                .padding(10.dp)
//                .fillMaxWidth()
                .wrapContentWidth(Alignment.CenterHorizontally),
            fontSize = 16.sp,
        )
        Text(
            text = "wrapContentWidth.End".repeat(1),
            modifier = Modifier
                .width(300.dp)
                .background(Color.Yellow)
                .padding(10.dp)
                .wrapContentWidth(Alignment.End),
            fontSize = 16.sp,
        )
    }
}

insert image description here

Gaussian Blur (Android 12+ only)

// Modifier.blur() only supported on Android 12+
// 如果兼容12以下,可以使用这个库https://github.com/skydoves/Cloudy
@Preview(showBackground = true, widthDp = 300)
@Composable
fun PreviewTextExample4() {
    
    
    Box(Modifier.height(50.dp)) {
    
    
        var checked by remember {
    
     mutableStateOf(true) }
        val radius by animateDpAsState(targetValue = if (checked) 10.dp else 0.dp)
        Text(
            text = "高斯模糊效果",
            Modifier.blur(
                radius = radius,
                edgeTreatment = BlurredEdgeTreatment.Unbounded
            ),
            fontSize = 20.sp
        )

        Switch(
            checked = checked,
            onCheckedChange = {
    
     checked = it },
            modifier = Modifier.align(Alignment.TopEnd)
        )
    }
}

TextField

@Composable
fun TextFieldExample() {
    
    
    val textFieldColors = TextFieldDefaults.textFieldColors(
        textColor = Color(0xFF0079D3),
        backgroundColor = Color.Transparent // 修改输入框背景色
    )
    Column {
    
    
        var text by remember {
    
     mutableStateOf("")}
        TextField(
            value = text,
            onValueChange = {
    
     text = it },
            singleLine = true, // 单行
            label = {
    
     Text("邮箱:") }, // 可选
            placeholder = {
    
     Text("请输入邮箱") },
            // trailingIcon 参数可以在 TextField 尾部布置 lambda 表达式所接收到的东西
            trailingIcon = {
    
    
                IconButton(onClick = {
    
     println("你输入的邮箱是:$text") } ) {
    
    
                    Icon(Icons.Filled.Send, null)
                }
            },
            colors = textFieldColors,
            modifier = Modifier.fillMaxWidth()
        )
        var text2 by remember {
    
     mutableStateOf("")}
        TextField(
            value = text2,
            onValueChange = {
    
     text2 = it },
            singleLine = true, // 单行
            placeholder = {
    
     Text("请输入关键字") },
            leadingIcon = {
    
    
                Icon(Icons.Filled.Search, null)
            },
            colors = textFieldColors,
            modifier = Modifier.fillMaxWidth()
        )

        var text3 by remember {
    
     mutableStateOf("") }
        var passwordHidden by remember{
    
     mutableStateOf(false)}
        TextField(
            value = text3,
            onValueChange = {
    
     text3 = it },
            singleLine = true,
            trailingIcon = {
    
    
                IconButton(onClick = {
    
      passwordHidden = !passwordHidden } ) {
    
    
                    Icon(Icons.Filled.Lock, null)
                }
            },
            visualTransformation = if (passwordHidden) PasswordVisualTransformation()
                                    else VisualTransformation.None,
            label = {
    
     Text("密码:") },
            colors = textFieldColors,
            modifier = Modifier.fillMaxWidth()
        )

        var text4 by remember {
    
     mutableStateOf("") }
        TextField(
            value = text4,
            onValueChange = {
    
     text4 = it },
            singleLine = true,
            label = {
    
     Text("姓名:") },
            colors = textFieldColors,
            modifier = Modifier.fillMaxWidth()
        )
    }
}

insert image description here

BasicTextField

TextFieldThey are all designed according to Material Design, so some spacing inside is fixed. If you want to customize the TextFieldheight of a and other custom effects, you should useBasicTextField

// 可自定义的BasicText
@Composable
fun BasicTextFieldExample() {
    
    
    var text by remember {
    
     mutableStateOf("") }
    Box(modifier = Modifier
        .background(Color(0xFFD3D3D3)),
        contentAlignment = Alignment.Center) {
    
    
        BasicTextField(
            value = text,
            onValueChange = {
    
     text = it },
            modifier = Modifier
                .background(Color.White)
                .fillMaxWidth(),
            decorationBox = {
    
     innerTextField ->
                Column(modifier = Modifier.padding(vertical = 10.dp)) {
    
    
                    Row(verticalAlignment = Alignment.CenterVertically) {
    
    
                        IconButton(onClick = {
    
    }) {
    
     Icon(Icons.Filled.Search, contentDescription = null) }
                        IconButton(onClick = {
    
    }) {
    
     Icon(Icons.Filled.Favorite, contentDescription = null) }
                        IconButton(onClick = {
    
    }) {
    
     Icon(Icons.Filled.Share, contentDescription = null) }
                        IconButton(onClick = {
    
    }) {
    
     Icon(Icons.Filled.Done, contentDescription = null) }
                    }
                    Box(modifier = Modifier.padding(horizontal = 10.dp)) {
    
    
                        innerTextField() // 这个是框架提供好的,我们只需在合适的地方调用它即可
                    }
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        verticalAlignment = Alignment.CenterVertically,
                        horizontalArrangement = Arrangement.End
                    ) {
    
    
                        TextButton(onClick = {
    
     }) {
    
     Text("发送") }
                        Spacer(Modifier.padding(horizontal = 10.dp))
                        TextButton(onClick = {
    
     }) {
    
     Text("关闭") }
                    }
                }
            }
        )
    }
}

insert image description here

A search box similar to Bilibili App:

@Composable
fun SearchBar() {
    
    
    var text by remember {
    
     mutableStateOf("") }
    var showPlaceHolder by remember {
    
     mutableStateOf(true) }

    Box(
        modifier = Modifier
            .height(50.dp)
            .background(Color(0xFFD3D3D3)),
        contentAlignment = Alignment.Center
    ) {
    
    
        BasicTextField(
            value = text,
            onValueChange = {
    
    
                text = it
                showPlaceHolder = it.isEmpty()
            },
            decorationBox = {
    
     innerTextField ->
                Row(
                    verticalAlignment = Alignment.CenterVertically,
                    modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp)
                ) {
    
    
                    Icon(
                        imageVector = Icons.Filled.Search,
                        contentDescription = "搜索",
                    )
                    Box(
                        modifier = Modifier
                            .padding(horizontal = 10.dp)
                            .weight(1f),
                        contentAlignment = Alignment.CenterStart
                    ) {
    
    
                        if (showPlaceHolder) {
    
    
                            Text(
                                text = "输入点东西看看吧~",
                                color = Color(0x7F000000),
                                modifier = Modifier.clickable {
    
     showPlaceHolder = false }
                            )
                        }
                        innerTextField()
                    }
                    if (text.isNotEmpty()) {
    
    
                        IconButton(
                            onClick = {
    
     text = "" },
                            modifier = Modifier.size(16.dp)
                        ) {
    
    
                            Icon(imageVector = Icons.Filled.Close, contentDescription = "清除")
                        }
                    }
                }
            },
            modifier = Modifier
                .padding(horizontal = 10.dp)
                .background(Color.White, CircleShape)
                .height(30.dp)
                .fillMaxWidth()
        )
    }
}

insert image description here

OutlinedTextField

input box with border

@Composable
fun OutlinedTextFieldExample() {
    
    
    val textValue = remember {
    
     mutableStateOf("") }
    val context = LocalContext.current
    Box(contentAlignment = Alignment.Center, modifier = Modifier.padding(10.dp)) {
    
    
        OutlinedTextField(
            value = textValue.value,
            onValueChange = {
    
     textValue.value = it },
            placeholder = {
    
     Text(text = "用户名") },
            label = {
    
     Text(text = "用户名标签") },
            singleLine = true,
            leadingIcon = {
    
     Icon(Icons.Filled.Person, contentDescription = "") },
            trailingIcon = {
    
    
                if (textValue.value.isNotEmpty()) {
    
    //文本框输入内容不为空时显示删除按钮
                    IconButton(onClick = {
    
     textValue.value = "" }) {
    
    
                        Icon(imageVector = Icons.Filled.Clear, contentDescription = "清除")
                    }
                }
            },
            //文本框通常和键盘配合使用
            keyboardOptions = KeyboardOptions(
                //设置键盘选项,如键盘类型和操作
                keyboardType = KeyboardType.Text,//设置键盘类型:数字,email等
                imeAction = ImeAction.Send,//设置键盘操作,如next send search 等
            ),
            keyboardActions = KeyboardActions(//键盘事件回调,与imeAction一一对应
                onSend = {
    
     // 点击键盘发送事件
                    context.showToast("输入的内容为${
      
      textValue.value}")
                }
            ),
            colors = TextFieldDefaults.outlinedTextFieldColors(
                //colors属性设置文本框不同状态下的颜色
                focusedBorderColor = Color.Red,//设置文本框焦点状态下的边框颜色
                unfocusedBorderColor = Color.Blue,//设置文本框未获取焦点状态时的边框颜色
                disabledBorderColor = Color.Gray,//设置文本框禁用时的边框颜色
                cursorColor = Color.Blue,//设置光标颜色
                //错误状态下的样式调整
                errorLabelColor = Color.Red,//设置错误状态下的标签颜色
                errorLeadingIconColor = Color.Red,//设置错误状态下文本框前端图标颜色
                errorTrailingIconColor = Color.Red,//设置错误状态下尾端图标颜色
                errorBorderColor = Color.Red,//设置错误状态下文本框边框颜色
                errorCursorColor = Color.Red,//设置错误状态下光标颜色
            ),
            isError = false, //true-显示错误状态的样式,false-普通状态样式
            enabled = true, //true-设置状态为可用,false-设置状态为禁用
            modifier = Modifier.height(100.dp).fillMaxWidth()
        )
    }
}

insert image description here

Card

a card component

@Composable
fun MyCard(width: Dp, height: Dp, title: String, imgId: Int = R.drawable.ic_sky) {
    
    
    Box {
    
    
        Card(
            modifier = Modifier.size(width, height),
            backgroundColor = MaterialTheme.colorScheme.background,
            contentColor = MaterialTheme.colorScheme.background,
//        border = BorderStroke(1.dp, Color.Gray),
            elevation = 10.dp
        ) {
    
    
            Column {
    
    
                Image(
                    painter = painterResource(id = imgId),
                    contentDescription = null,
                    modifier = Modifier
                        .fillMaxWidth()
                        .weight(1f),
                    contentScale = ContentScale.Crop
                )
                Divider(Modifier.fillMaxWidth())
                Text(
                    title,
                    Modifier.padding(8.dp),
                    color = MaterialTheme.colorScheme.onBackground,
                    fontSize = 20.sp
                )
            }
        }
    }
}

@Preview(showBackground = true, widthDp = 400, heightDp = 300)
@Composable
fun CardExamplePreview() {
    
    
    Box(contentAlignment = Alignment.Center) {
    
    
        MyCard(300.dp, 200.dp, "Cart Content")
    }
}

insert image description here

Box

Stacked in order, something likeFrameLayout

@Composable
fun BoxExample() {
    
    
    Box(
        contentAlignment = Alignment.Center
    ) {
    
    
        Box(modifier = Modifier.size(150.dp).background(Color.Red))
        Box(modifier = Modifier.size(80.dp).background(Color.Blue))
        Text("Box", color = Color.Yellow)
    }
}

insert image description here

BoxScopeThere are two Boxchild-specific Modifierattributes in the : alignandmatchParentSize

@Composable
fun ModifierSample2() {
    
    
    // 父元素
    Box(modifier = Modifier
        .width(200.dp)
        .height(300.dp)
        .background(Color.Yellow)){
    
    
        // 子元素
        Box(modifier = Modifier
            .align(Alignment.Center) // align是父级数据修饰符
            .size(50.dp)
            .background(Color.Blue))
    }
}

insert image description here

BoxWithConstraints

Sample code 1:

@Composable
fun BoxWithConstraintsExample() {
    
    
    BoxWithConstraints {
    
    
        val boxWithConstraintsScope = this
        if (maxHeight < 200.dp) {
    
    
            Column(Modifier
                .fillMaxWidth()
                .background(Color.Cyan)) {
    
    
                Text("只在最大高度 < 200dp 时显示", fontSize = 20.sp)
                with(boxWithConstraintsScope) {
    
    
                    Text("minHeight: $minHeight", fontSize = 20.sp)
                    Text("maxHeight: $maxHeight", fontSize = 20.sp)
                    Text("minWidth: $minWidth", fontSize = 20.sp)
                    Text("maxWidth: $maxWidth", fontSize = 20.sp)
                }
            }
        } else {
    
    
            Column(Modifier
                .fillMaxWidth()
                .background(Color.Green)) {
    
    
                Text("当 maxHeight >= 200dp 时显示", fontSize = 20.sp)
                with(boxWithConstraintsScope) {
    
    
                    Text("minHeight: $minHeight", fontSize = 20.sp)
                    Text("maxHeight: $maxHeight", fontSize = 20.sp)
                    Text("minWidth: $minWidth", fontSize = 20.sp)
                    Text("maxWidth: $maxWidth", fontSize = 20.sp)
                }
             }
        }
    }
}

@Preview(heightDp = 150, showBackground = true)
@Composable
fun BoxWithConstraintsExamplePreview() {
    
    
    BoxWithConstraintsExample()
}

@Preview(heightDp = 250, showBackground = true)
@Composable
fun BoxWithConstraintsExamplePreview2() {
    
    
    BoxWithConstraintsExample()
}

insert image description here

Sample code 2:

@Composable
private fun BoxWithConstraintsExample2(modifier: Modifier = Modifier) {
    
    
    BoxWithConstraints(modifier.background(Color.LightGray)) {
    
    
        val boxWithConstraintsScope = this
        val topHeight = maxHeight * 2 / 3f
        // 也可以通过父组件传入的constraints来获取,不过这样得到的值是px,需要按需转成成dp使用
        // val topHeight = (this.constraints.maxHeight * 2 / 3f).toDp()
        Column(Modifier.fillMaxWidth()) {
    
    
            Column(Modifier.background(Color.Magenta).fillMaxWidth().height(topHeight)) {
    
    
                Text("占整个组件高度的 2/3 \ntopHeight: $topHeight", fontSize = 20.sp)
                with(boxWithConstraintsScope) {
    
    
                    Text("minHeight: $minHeight", fontSize = 20.sp)
                    Text("maxHeight: $maxHeight", fontSize = 20.sp)
                    Text("minWidth: $minWidth", fontSize = 20.sp)
                    Text("maxWidth: $maxWidth", fontSize = 20.sp)
                }
            }
            val bottomHeight = boxWithConstraintsScope.maxHeight * 1 / 3f
            Box(Modifier.background(Color.Cyan).fillMaxWidth().height(bottomHeight)) {
    
    
                Text("占整个组件高度的 1/3 \nbottomHeight: $bottomHeight", fontSize = 20.sp)
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun BoxWithConstraintsExample2Preview() {
    
    
    Column(verticalArrangement = Arrangement.SpaceBetween) {
    
    
        var height by remember {
    
     mutableStateOf(200f) }
        BoxWithConstraintsExample2(
            Modifier.fillMaxWidth()
                .height(height.dp)
        )
        Slider(value = height, onValueChange = {
    
     height = it}, valueRange = 200f..600f)
    }
}

insert image description here

Surface

SurfaceYou can quickly set the shape, shadow, border, color, etc. of the interface, which can reduce Modifierthe amount of usage

@Composable
fun SurfaceExample() {
    
    
    Box(
        Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
    
    
        Surface(
            shape = RoundedCornerShape(8.dp),
            elevation = 10.dp,
            // border = BorderStroke(1.dp, Color.Red),
            modifier = Modifier
                .width(300.dp)
                .height(100.dp)
        ) {
    
    
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
    
    
                Text(text = "Surface")
            }
        }
    }
}

insert image description here

@Composable
fun SurfaceExample2() {
    
    
    Box(
        Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
    
    
        Surface(
            shape = CircleShape,
            elevation = 10.dp,
            // border = BorderStroke(1.dp, Color.Red),
            modifier = Modifier
                .width(300.dp)
                .height(100.dp)
        ) {
    
    
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
    
    
                Image(
                    painter = painterResource(id = R.drawable.ic_sky),
                    contentDescription = null,
                    contentScale = ContentScale.Crop,
                    modifier = Modifier.fillMaxSize(),
                )
            }
        }
    }
}

insert image description here

@Composable
fun SurfaceExample3() {
    
    
    Box(
        Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
    
    
        Surface(
            shape = CutCornerShape(35),
            elevation = 10.dp,
            // border = BorderStroke(1.dp, Color.Red),
            modifier = Modifier
                .width(300.dp)
                .height(100.dp)
        ) {
    
    
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
    
    
                Image(
                    painter = painterResource(id = R.drawable.ic_sky),
                    contentDescription = null,
                    contentScale = ContentScale.Crop,
                    modifier = Modifier.fillMaxSize(),
                )
            }
        }
    }
}

insert image description here

ModalBottomSheetLayout

Typically used for bottom popup menus

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ModalBottomSheetExample() {
    
    
    val state = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
    val scope = rememberCoroutineScope()
    ModalBottomSheetLayout(
        sheetState = state,
        sheetContent = {
    
    
            Column{
    
    
                ListItem(
                    text = {
    
     Text("选择分享到哪里吧~") }
                )
                ListItem(
                    text = {
    
     Text("github") },
                    icon = {
    
    
                        Surface(
                            shape = CircleShape,
                            color = Color(0xFF181717)
                        ) {
    
    
                            Icon(
                                Icons.Default.Share,
                                null,
                                tint = Color.White,
                                modifier = Modifier.padding(4.dp)
                            )
                        }
                    },
                    modifier = Modifier.clickable {
    
     scope.launch {
    
     state.hide() } }
                )
                ListItem(
                    text = {
    
     Text("微信") },
                    icon = {
    
    
                        Surface(
                            shape = CircleShape,
                            color = Color(0xFF07C160)
                        ) {
    
    
                            Icon(Icons.Default.Person,
                                null,
                                tint = Color.White,
                                modifier = Modifier.padding(4.dp)
                            )
                        }
                    },
                    modifier = Modifier.clickable {
    
     scope.launch {
    
     state.hide() } }
                )
                ListItem(
                    text = {
    
     Text("更多") },
                    icon = {
    
    
                        Surface(
                            shape = CircleShape,
                            color = Color(0xFF07C160)
                        ) {
    
    
                            Icon(Icons.Default.MoreVert,
                                null,
                                tint = Color.White,
                                modifier = Modifier.padding(4.dp)
                            )
                        }
                    },
                    modifier = Modifier.clickable {
    
     scope.launch {
    
     state.hide() } }
                )
            }
        }
    ) {
    
    
        Column(
            modifier = Modifier.fillMaxSize().padding(16.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
    
    
            Button(
                onClick = {
    
    
                    scope.launch {
    
    
                        state.show()
                    }
                }
            ) {
    
    
                Text("点我展开")
            }
        }
    }
    BackHandler(
        enabled = (state.currentValue == ModalBottomSheetValue.HalfExpanded
                || state.currentValue == ModalBottomSheetValue.Expanded),
        onBack = {
    
    
            scope.launch {
    
     state.hide() }
        }
    )
}

insert image description here

Scaffold

ScaffoldIt is a scaffold for configuring the Material Design layout structure, providing some default pits for configuration

data class Item(val name : String, val icon : ImageVector)

val items = listOf(
    Item("首页", Icons.Default.Home),
    Item("列表", Icons.Filled.List),
    Item("设置", Icons.Filled.Settings)
)

@Composable
fun ScaffoldExample() {
    
    
    var selectedItem by remember {
    
     mutableStateOf(0) }
    val scaffoldState = rememberScaffoldState()
    val scope = rememberCoroutineScope()
    Scaffold(
        topBar = {
    
    
            TopAppBar(
                title = {
    
     Text("首页", color = MaterialTheme.colors.onPrimary) },
//                contentPadding = WindowInsets.statusBars.asPaddingValues(),
                navigationIcon = {
    
    
                    IconButton(onClick = {
    
    
                        scope.launch {
    
    
                            scaffoldState.drawerState.open()
                        }
                    }) {
    
    
                        Icon(Icons.Filled.Menu, null)
                    }
                }
            )
        },
        bottomBar = {
    
    
//            val paddingValues =  WindowInsets.navigationBars.asPaddingValues()
//            BottomNavigation(contentPadding = PaddingValues(top = 12.dp, bottom = paddingValues.calculateBottomPadding())) {
    
    
            BottomNavigation {
    
    
                items.forEachIndexed {
    
     index, item ->
                    BottomNavigationItem(
                        selected = selectedItem == index,
                        onClick = {
    
     selectedItem = index },
                        icon = {
    
    
                            BadgedBox(
                                badge = {
    
    
                                    Badge(modifier = Modifier.padding(top = 5.dp)) {
    
    
                                        val badgeNumber = "9"
                                        Text(badgeNumber, color = Color.White)
                                    }
                                },
                            ) {
    
    
                                Icon(item.icon, null,
//                                    modifier = Modifier.padding(bottom = 5.dp)
                                )
                            }
                        },
                        label = {
    
     Text(text = item.name, color = MaterialTheme.colors.onPrimary)}
                    )
                }
            }
        },
        drawerContent = {
    
    
            Text("Hello")
        },
        scaffoldState = scaffoldState,
        floatingActionButton = {
    
    
            ExtendedFloatingActionButton(
//                icon = { Icon(Icons.Default.Add, null) },
                text = {
    
     /*Text("Add")*/ Icon(Icons.Default.Add, null)},
                onClick = {
    
     println("floatingActionButton") },
                shape = CircleShape,
                modifier = Modifier.size(80.dp)
            )
        },
        floatingActionButtonPosition = FabPosition.End,
        isFloatingActionButtonDocked = false
    ) {
    
     padding ->
        Box(
            modifier = Modifier
                .fillMaxSize()
                .padding(padding),
            contentAlignment = Alignment.Center
        ) {
    
    
            Text("主页界面")
        }
    }
    BackHandler(enabled = scaffoldState.drawerState.isOpen) {
    
    
        scope.launch {
    
    
            scaffoldState.drawerState.close()
        }
    }
}

insert image description here

Let be embedded in the middle of the bottom floatingActionButtonas :DockedbottomBar

@Composable
fun ScaffoldExample3() {
    
    
    val scaffoldState = rememberScaffoldState()
    val scope = rememberCoroutineScope()

    Scaffold(
        topBar = {
    
    
            TopAppBar(
                title = {
    
     Text("首页", color = MaterialTheme.colors.onPrimary) },
            )
        },
        bottomBar = {
    
    
            BottomAppBar(cutoutShape = CircleShape) {
    
    
                Row(
                    horizontalArrangement = Arrangement.SpaceAround,
                    modifier = Modifier.fillMaxWidth()
                ) {
    
    
                    Text("Android", color = MaterialTheme.colors.onPrimary)
                    Text("Compose", color = MaterialTheme.colors.onPrimary)
                }
            }
        },
        drawerContent = {
    
    
            Text("Hello")
        },
        scaffoldState = scaffoldState,
        floatingActionButton = {
    
    
            ExtendedFloatingActionButton(
//                icon = { Icon(Icons.Default.Add, null) },
                text = {
    
     Text("Add") },
                onClick = {
    
    
                      scope.launch {
    
    
                          scaffoldState.snackbarHostState.showSnackbar("添加成功", actionLabel = "Done")
                      }
                },
                shape = CircleShape, // RoundedCornerShape(15),
                modifier = Modifier.size(80.dp),
                backgroundColor = Color.Green
            )
        },
        floatingActionButtonPosition = FabPosition.Center,
        isFloatingActionButtonDocked = true
    ) {
    
     padding ->
        Box(
            modifier = Modifier
                .fillMaxSize()
                .padding(padding),
            contentAlignment = Alignment.Center
        ) {
    
    
            Text("主页界面")
        }
    }
}

insert image description here

Change the shape of floatingActionButtonthe embedded bottom bottomBar:

@Composable
fun ScaffoldExample2() {
    
    
    val scaffoldState = rememberScaffoldState()

    // Consider negative values to mean 'cut corner' and positive values to mean 'round corner'
    val sharpEdgePercent = -50f
    val roundEdgePercent = 45f
    // Start with sharp edges
    val animatedProgress = remember {
    
     Animatable(sharpEdgePercent) }
    // Create a coroutineScope for the animation
    val coroutineScope = rememberCoroutineScope()
    // animation value to animate shape
    val progress = animatedProgress.value.roundToInt()

    // When progress is 0, there is no modification to the edges so we are just drawing a rectangle.
    // This allows for a smooth transition between cut corners and round corners.
    val fabShape = if (progress < 0) {
    
    
        CutCornerShape(abs(progress))
    } else if (progress == roundEdgePercent.toInt()) {
    
    
        CircleShape
    } else {
    
    
        RoundedCornerShape(progress)
    }
    // lambda to call to trigger shape animation
    val changeShape: () -> Unit = {
    
    
        val target = animatedProgress.targetValue
        val nextTarget = if (target == roundEdgePercent) sharpEdgePercent else roundEdgePercent
        coroutineScope.launch {
    
    
            animatedProgress.animateTo(
                targetValue = nextTarget,
                animationSpec = TweenSpec(durationMillis = 600)
            )
        }
    }

    Scaffold(
        topBar = {
    
    
            TopAppBar(
                title = {
    
     Text("首页", color = MaterialTheme.colors.onPrimary) },
            )
        },
        bottomBar = {
    
    
            BottomAppBar(cutoutShape = fabShape) {
    
    
                Row(
                    horizontalArrangement = Arrangement.SpaceAround,
                    modifier = Modifier.fillMaxWidth()
                ) {
    
    
                    Text("Android", color = MaterialTheme.colors.onPrimary)
                    Text("Compose", color = MaterialTheme.colors.onPrimary)
                }
            }
        },
        drawerContent = {
    
    
            Text("Hello")
        },
        scaffoldState = scaffoldState,
        floatingActionButton = {
    
    
            ExtendedFloatingActionButton(
//                icon = { Icon(Icons.Default.Add, null) },
                text = {
    
     Text("ChangeShape") },
                onClick = changeShape,
                shape = fabShape,
            )
        },
        floatingActionButtonPosition = FabPosition.Center,
        isFloatingActionButtonDocked = true
    ) {
    
     padding ->
        Box(
            modifier = Modifier
                .fillMaxSize()
                .padding(padding),
            contentAlignment = Alignment.Center
        ) {
    
    
            Text("主页界面")
        }
    }
}

insert image description here

BackdropScaffold

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun BackdropScaffoldExample() {
    
    
    val scope = rememberCoroutineScope()

    val scaffoldState = rememberBackdropScaffoldState(BackdropValue.Concealed)
    LaunchedEffect(scaffoldState) {
    
    
        scaffoldState.reveal()
    }
    BackdropScaffold(
        scaffoldState = scaffoldState,
        appBar = {
    
    
            TopAppBar(
                title = {
    
     Text("Backdrop scaffold") },
                navigationIcon = {
    
    
                    if (scaffoldState.isConcealed) {
    
    
                        IconButton(onClick = {
    
     scope.launch {
    
     scaffoldState.reveal() } }) {
    
    
                            Icon(Icons.Default.Menu, contentDescription = "Localized description")
                        }
                    } else {
    
    
                        IconButton(onClick = {
    
     scope.launch {
    
     scaffoldState.conceal() } }) {
    
    
                            Icon(Icons.Default.Close, contentDescription = "Localized description")
                        }
                    }
                },
                actions = {
    
    
                    var clickCount by remember {
    
     mutableStateOf(0) }
                    IconButton(
                        onClick = {
    
    
                            // show snackbar as a suspend function
                            scope.launch {
    
    
                                scaffoldState.snackbarHostState
                                    .showSnackbar("Snackbar #${
      
      ++clickCount}")
                            }
                        }
                    ) {
    
    
                        Icon(Icons.Default.Favorite, contentDescription = "Localized description")
                    }
                },
                elevation = 0.dp,
                backgroundColor = Color.Transparent
            )
        },
        backLayerContent = {
    
    
            LazyColumn {
    
    
                items(15) {
    
    
                    ListItem(
                        Modifier.clickable {
    
     scope.launch {
    
     scaffoldState.conceal() } },
                        text = {
    
     Text("Select $it") }
                    )
                }
            }
        },
        frontLayerContent = {
    
    
            LazyColumn {
    
    
                items(50) {
    
    
                    ListItem(
                        text = {
    
     Text("Item $it") },
                        icon = {
    
    
                            Icon(Icons.Default.Favorite, "Localized description")
                        }
                    )
                }
            }
        }
    )
}

insert image description here

BottomSheetScaffold

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun BottomSheetScaffoldExample() {
    
    
    val scope = rememberCoroutineScope()
    val scaffoldState = rememberBottomSheetScaffoldState()
    BottomSheetScaffold(
        sheetContent = {
    
    
           Column(Modifier.background(Color.Magenta)) {
    
    
               Box(
                   Modifier
                       .fillMaxWidth()
                       .height(128.dp),
                   contentAlignment = Alignment.Center
               ) {
    
    
                   Text("Swipe up to expand sheet")
               }
               Column(
                   Modifier
                       .fillMaxWidth()
                       .padding(64.dp),
                   horizontalAlignment = Alignment.CenterHorizontally
               ) {
    
    
                   Text("Sheet content")
                   Spacer(Modifier.height(20.dp))
                   Button(
                       onClick = {
    
    
                           scope.launch {
    
     scaffoldState.bottomSheetState.collapse() }
                       }
                   ) {
    
    
                       Text("Click to collapse sheet")
                   }
               }
           }
        },
        scaffoldState = scaffoldState,
        topBar = {
    
    
            TopAppBar(
                title = {
    
     Text("Bottom sheet scaffold") },
                navigationIcon = {
    
    
                    IconButton(onClick = {
    
     scope.launch {
    
     scaffoldState.drawerState.open() } }) {
    
    
                        Icon(Icons.Default.Menu, contentDescription = "Localized description")
                    }
                }
            )
        },
        floatingActionButton = {
    
    
            var clickCount by remember {
    
     mutableStateOf(0) }
            FloatingActionButton(
                onClick = {
    
    
                    // show snackbar as a suspend function
                    scope.launch {
    
    
                        scaffoldState.snackbarHostState.showSnackbar("Snackbar #${
      
      ++clickCount}")
                    }
                }
            ) {
    
    
                Icon(Icons.Default.Favorite, contentDescription = "Localized description")
            }
        },
        floatingActionButtonPosition = FabPosition.End,
        sheetPeekHeight = 128.dp,
        drawerContent = {
    
    
            Column(
                Modifier
                    .fillMaxWidth()
                    .padding(16.dp),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
    
    
                Text("Drawer content")
                Spacer(Modifier.height(20.dp))
                Button(onClick = {
    
     scope.launch {
    
     scaffoldState.drawerState.close() } }) {
    
    
                    Text("Click to close drawer")
                }
            }
        }
    ) {
    
     innerPadding ->
        LazyColumn(contentPadding = innerPadding) {
    
    
            items(100) {
    
    
                Box(
                    Modifier
                        .fillMaxWidth()
                        .height(50.dp)
                )
            }
        }
    }
}

insert image description here

TabRow

@Composable
fun TabRowExample() {
    
    
    var state by remember {
    
     mutableStateOf(0) }
    val titles = listOf("TAB 1", "TAB 2", "TAB 3 WITH LOTS OF TEXT")
    Column {
    
    
        TabRow(selectedTabIndex = state) {
    
    
            titles.forEachIndexed {
    
     index, title ->
                Tab(
                    text = {
    
     Text(title) },
                    selected = state == index,
                    onClick = {
    
     state = index }
                )
            }
        }
        Text(
            modifier = Modifier.align(Alignment.CenterHorizontally),
            text = "Text tab ${
      
      state + 1} selected",
            style = MaterialTheme.typography.titleLarge
        )
    }
}

insert image description here

ScrollableTabRow

@Composable
fun ScrollableTabRowExample() {
    
    
    var state by remember {
    
     mutableStateOf(0) }
    val titles = listOf("TAB 1", "TAB 2", "TAB 3", "TAB 4", "TAB 5", "TAB 6", "TAB 7", "TAB 8", "TAB 9")
    Column {
    
    
        ScrollableTabRow(selectedTabIndex = state) {
    
    
            titles.forEachIndexed {
    
     index, title ->
                Tab(
                    text = {
    
     Text(title) },
                    selected = state == index,
                    onClick = {
    
     state = index }
                )
            }
        }
        Text(
            modifier = Modifier.align(Alignment.CenterHorizontally),
            text = "Text tab ${
      
      state + 1} selected",
            style = MaterialTheme.typography.titleLarge
        )
    }
}

insert image description here

Customize the Tab and Indicator of TabRow

fun CustomTabRowExample(withAnimatedIndicator : Boolean = false) {
    
    
    var state by remember {
    
     mutableStateOf(0) }
    val titles = listOf("TAB 1", "TAB 2", "TAB 3")
    // 复用默认的偏移动画modifier,但使用我们自己的指示器
    val indicator = @Composable {
    
     tabPositions: List<TabPosition> ->
        if (withAnimatedIndicator) {
    
    
            FancyAnimatedIndicator(tabPositions = tabPositions, selectedTabIndex = state)
        } else {
    
    
            FancyIndicator(Color.Blue, Modifier.tabIndicatorOffset(tabPositions[state]))
        }
    }
    Column {
    
    
        TabRow(selectedTabIndex = state, indicator = indicator) {
    
    
            titles.forEachIndexed {
    
     index, title ->
                FancyTab(title = title, onClick = {
    
     state = index }, selected = (index == state))
            }
        }
        Text(
            modifier = Modifier.align(Alignment.CenterHorizontally),
            text = "Fancy tab ${
      
      state + 1} selected",
            style = MaterialTheme.typography.titleLarge
        )
    }
}

where FancyAnimatedIndicatorand FancyIndicatorare defined as follows:

@Composable
fun FancyAnimatedIndicator(tabPositions: List<TabPosition>, selectedTabIndex: Int) {
    
    
    val colors = listOf(Color.Blue, Color.Red, Color.Magenta)
    val transition = updateTransition(selectedTabIndex, label = "")
    val indicatorStart by transition.animateDp(
        transitionSpec = {
    
    
            // 这里处理方向性,如果我们向右移动,我们希望指示器的右侧移动得更快,如果我们向左移动,我们想要左侧移动得更快。
            if (initialState < targetState) {
    
    
                spring(dampingRatio = 1f, stiffness = 50f)
            } else {
    
    
                spring(dampingRatio = 1f, stiffness = 1000f)
            }
        }, label = ""
    ) {
    
    
        tabPositions[it].left
    }

    val indicatorEnd by transition.animateDp(
        transitionSpec = {
    
    
            // 这里处理方向性,如果我们向右移动,我们希望指示器的右侧移动得更快,如果我们向左移动,我们想要左侧移动得更快。
            if (initialState < targetState) {
    
    
                spring(dampingRatio = 1f, stiffness = 1000f)
            } else {
    
    
                spring(dampingRatio = 1f, stiffness = 50f)
            }
        }, label = ""
    ) {
    
    
        tabPositions[it].right
    }

    val indicatorColor by transition.animateColor(label = "") {
    
    
        colors[it % colors.size]
    }

    FancyIndicator(
        indicatorColor, // 将当前颜色传递给指示器
        modifier = Modifier
            .fillMaxSize() // 填满整个TabRow,并将指示器放在起始处
            .wrapContentSize(align = Alignment.BottomStart)
            .offset(x = indicatorStart) // 从起点开始应用偏移量,以便将指示器正确定位在选项卡周围
            .width(indicatorEnd - indicatorStart) // 当我们在选项卡之间移动时,使指示器的宽度与动画宽度一致
    )
}
@Composable
fun FancyIndicator(color : Color, modifier : Modifier) {
    
    
    // 在Tab周围绘制一个带边框的圆角矩形,边框外层边缘带有5.dp的padding, 边框颜色通过参数[color]传入
    Box(modifier.padding(5.dp).fillMaxSize()
            .border(BorderStroke(2.dp, color), RoundedCornerShape(5.dp)))
}

FancyTabIt is defined as follows:

@Composable
fun FancyTab(selected: Boolean, onClick: () -> Unit, title: String, ) {
    
    
    Tab(selected, onClick) {
    
    
        Column(
            Modifier.padding(10.dp).height(50.dp).fillMaxWidth(),
            verticalArrangement = Arrangement.SpaceBetween
        ) {
    
    
            Text(
                text = title,
                style = MaterialTheme.typography.bodyMedium,
                modifier = Modifier.align(Alignment.CenterHorizontally)
            )
            Box(
                Modifier.height(5.dp).fillMaxWidth().align(Alignment.CenterHorizontally)
                    .background(color = if (selected) Color.Red else Color.White)
            )
        }
    }
}

With animation:
insert image description here

Without animation:

insert image description here

TopAppBar

TopAppBarIt is in the Material3 package and is generally Scaffoldused with scaffolding

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun SimpleTopAppBar() {
    
    
    Scaffold(
        topBar = {
    
    
            TopAppBar(
                title = {
    
    
                    Text(
                        "Simple TopAppBar",
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis
                    )
                },
                navigationIcon = {
    
    
                    IconButton(onClick = {
    
     /* doSomething() */ }) {
    
    
                        Icon(
                            imageVector = Icons.Filled.Menu,
                            contentDescription = "Localized description"
                        )
                    }
                },
                actions = {
    
    
                    IconButton(onClick = {
    
     /* doSomething() */ }) {
    
    
                        Icon(
                            imageVector = Icons.Filled.Favorite,
                            contentDescription = "Localized description"
                        )
                    }
                }
            )
        },
        content = {
    
     innerPadding ->
            LazyColumn(
                contentPadding = innerPadding,
                verticalArrangement = Arrangement.spacedBy(8.dp)
            ) {
    
    
                val list = (0..75).map {
    
     it.toString() }
                items(count = list.size) {
    
    
                    Text(
                        text = list[it],
                        style = MaterialTheme.typography.bodyLarge,
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(horizontal = 16.dp)
                    )
                }
            }
        }
    )
}

insert image description here

fixed top bar

Use the specified TopAppBarparameter scrollBehaviorto TopAppBarDefaults.pinnedScrollBehavior()achieve the fixed top bar effect. A color that can change dynamically when the content is scrolled TopAppBar.

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun PinnedTopAppBar() {
    
    
    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
    val topAppBarState = remember{
    
     scrollBehavior.state }
    val isAppBarCoveredContent = topAppBarState.overlappedFraction > 0f
    val titleAndIconColor = if (isAppBarCoveredContent) Color.White else Color.Black
    Scaffold(
        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
        topBar = {
    
    
            TopAppBar(
                scrollBehavior = scrollBehavior,
                colors = TopAppBarDefaults.topAppBarColors(
                    scrolledContainerColor = MaterialTheme.colorScheme.primary,
                    navigationIconContentColor = titleAndIconColor,
                    titleContentColor = titleAndIconColor,
                    actionIconContentColor = titleAndIconColor,
                ),
                title = {
    
    
                    Text(
                        "TopAppBar",
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis
                    )
                },
                navigationIcon = {
    
    
                    IconButton(onClick = {
    
     /* doSomething() */ }) {
    
    
                        Icon(
                            imageVector = Icons.Filled.Menu,
                            contentDescription = "Localized description"
                        )
                    }
                },
                actions = {
    
    
                    // RowScope here, so these icons will be placed horizontally
                    IconButton(onClick = {
    
     /* doSomething() */ }) {
    
    
                        Icon(
                            imageVector = Icons.Filled.Favorite,
                            contentDescription = "Localized description"
                        )
                    }
                    IconButton(onClick = {
    
     /* doSomething() */ }) {
    
    
                        Icon(
                            imageVector = Icons.Filled.Favorite,
                            contentDescription = "Localized description"
                        )
                    }
                },
            )
        },
        content = {
    
     innerPadding ->
            LazyColumn(
                contentPadding = innerPadding,
                verticalArrangement = Arrangement.spacedBy(8.dp)
            ) {
    
    
                val list = (0..75).map {
    
     it.toString() }
                items(count = list.size) {
    
    
                    Text(
                        text = list[it],
                        style = MaterialTheme.typography.bodyLarge,
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(horizontal = 16.dp)
                    )
                }
            }
        }
    )
}

insert image description here

collapse top bar

Through the specified TopAppBarparameter scrollBehaviorto TopAppBarDefaults.enterAlwaysScrollBehavior()achieve the effect of collapsing the top bar

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun EnterAlwaysTopAppBar() {
    
    
    val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
    Scaffold(
        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
        topBar = {
    
    
            TopAppBar(
                scrollBehavior = scrollBehavior,
                title = {
    
    
                    Text(
                        "TopAppBar",
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis
                    )
                },
                navigationIcon = {
    
    
                    IconButton(onClick = {
    
     /* doSomething() */ }) {
    
    
                        Icon(
                            imageVector = Icons.Filled.Menu,
                            contentDescription = "Localized description"
                        )
                    }
                },
                actions = {
    
    
                    IconButton(onClick = {
    
     /* doSomething() */ }) {
    
    
                        Icon(
                            imageVector = Icons.Filled.Favorite,
                            contentDescription = "Localized description"
                        )
                    }
                },
            )
        },
        content = {
    
     innerPadding ->
            LazyColumn(
                contentPadding = innerPadding,
                verticalArrangement = Arrangement.spacedBy(8.dp)
            ) {
    
    
                val list = (0..75).map {
    
     it.toString() }
                items(count = list.size) {
    
    
                    Text(
                        text = list[it],
                        style = MaterialTheme.typography.bodyLarge,
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(horizontal = 16.dp)
                    )
                }
            }
        }
    )
}

insert image description here

TopAppBarDefaultsThere are other scrollBehaviorsuch as in exitUntilCollapsedScrollBehavior(), the effect is similar, you can choose according to your needs.

CenterAlignedTopAppBar

This is TopAppBarnot much different from , that is, the title is centered, but it will not respond to any scrolling events.

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun SimpleCenterAlignedTopAppBar() {
    
    
    Scaffold(
        topBar = {
    
    
            CenterAlignedTopAppBar(
                title = {
    
    
                    Text(
                        "Centered TopAppBar",
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis
                    )
                },
                navigationIcon = {
    
    
                    IconButton(onClick = {
    
     /* doSomething() */ }) {
    
    
                        Icon(
                            imageVector = Icons.Filled.Menu,
                            contentDescription = "Localized description"
                        )
                    }
                },
                actions = {
    
    
                    IconButton(onClick = {
    
     /* doSomething() */ }) {
    
    
                        Icon(
                            imageVector = Icons.Filled.Favorite,
                            contentDescription = "Localized description"
                        )
                    }
                }
            )
        },
        content = {
    
     innerPadding ->
            LazyColumn(
                contentPadding = innerPadding,
                verticalArrangement = Arrangement.spacedBy(8.dp)
            ) {
    
    
                val list = (0..75).map {
    
     it.toString() }
                items(count = list.size) {
    
    
                    Text(
                        text = list[it],
                        style = MaterialTheme.typography.bodyLarge,
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(horizontal = 16.dp)
                    )
                }
            }
        }
    )
}

insert image description here

MediumTopAppBar

One size bigger TopAppBar, MediumTopAppBarwith a titleslot, and the default is expanded, which can achieve the effect of collapsing and expanding the title when the top bar is folded.

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun ExitUntilCollapsedMediumTopAppBar() {
    
    
    val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
    Scaffold(
        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
        topBar = {
    
    
            MediumTopAppBar(
            	scrollBehavior = scrollBehavior,
                title = {
    
    
                    Text(
                        "Medium TopAppBar",
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis
                    )
                },
                navigationIcon = {
    
    
                    IconButton(onClick = {
    
     /* doSomething() */ }) {
    
    
                        Icon(
                            imageVector = Icons.Filled.Menu,
                            contentDescription = "Localized description"
                        )
                    }
                },
                actions = {
    
    
                    IconButton(onClick = {
    
     /* doSomething() */ }) {
    
    
                        Icon(
                            imageVector = Icons.Filled.Favorite,
                            contentDescription = "Localized description"
                        )
                    }
                }, 
            )
        },
        content = {
    
     innerPadding ->
            LazyColumn(
                contentPadding = innerPadding,
                verticalArrangement = Arrangement.spacedBy(8.dp)
            ) {
    
    
                val list = (0..75).map {
    
     it.toString() }
                items(count = list.size) {
    
    
                    Text(
                        text = list[it],
                        style = MaterialTheme.typography.bodyLarge,
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(horizontal = 16.dp)
                    )
                }
            }
        }
    )
}

insert image description here

LargeTopAppBar

One size larger TopAppBar, but the height LargeTopAppBarof titlethe area is fixed and cannot be modified. It is more suitable for two lines of titles.

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun ExitUntilCollapsedLargeTopAppBar() {
    
    
    val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
    val isCollapsed = scrollBehavior.state.collapsedFraction == 1.0f
    val alpha = 1.0f - scrollBehavior.state.collapsedFraction
    Scaffold(
        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
        topBar = {
    
    
            LargeTopAppBar(
                scrollBehavior = scrollBehavior,
                title = {
    
    
                    Column {
    
    
                        Text(
                            "Large TopAppBar",
                            maxLines = 1,
                            overflow = TextOverflow.Ellipsis,
                        )
                        if (!isCollapsed) {
    
    
                            Text(
                                "Sub title",
                                maxLines = 1,
                                overflow = TextOverflow.Ellipsis,
                                color = Color.Black.copy(alpha = alpha),
                                fontSize = MaterialTheme.typography.headlineSmall.fontSize * alpha,
                            )
                        }
                    }
                },
                navigationIcon = {
    
    
                    IconButton(onClick = {
    
     /* doSomething() */ }) {
    
    
                        Icon(
                            imageVector = Icons.Filled.Menu,
                            contentDescription = "Localized description"
                        )
                    }
                },
                actions = {
    
    
                    IconButton(onClick = {
    
     /* doSomething() */ }) {
    
    
                        Icon(
                            imageVector = Icons.Filled.Favorite,
                            contentDescription = "Localized description"
                        )
                    }
                },
            )
        },
        content = {
    
     innerPadding ->
            LazyColumn(
                contentPadding = innerPadding,
                verticalArrangement = Arrangement.spacedBy(8.dp)
            ) {
    
    
                val list = (0..75).map {
    
     it.toString() }
                items(count = list.size) {
    
    
                    Text(
                        text = list[it],
                        style = MaterialTheme.typography.bodyLarge,
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(horizontal = 16.dp)
                    )
                }
            }
        }
    )
}

insert image description here

BottomAppBar

BottomAppBarUsually Scaffoldused in conjunction with Scaffolding, placed in the slot Scaffoldof bottomBarthe , but can also be used independently.

@Preview
@Composable
fun SimpleBottomAppBar() {
    
    
    Column(
        verticalArrangement = Arrangement.Center,
        modifier = Modifier.padding(10.dp)
    ) {
    
    
        BottomAppBar(
            containerColor = MaterialTheme.colorScheme.primary,
            modifier = Modifier.height(50.dp)
        ) {
    
    
            IconButton(onClick = {
    
     /* doSomething() */ }) {
    
    
                Icon(Icons.Filled.Menu, contentDescription = "Localized description")
            }
        }
        Spacer(modifier = Modifier.height(10.dp))
        BottomAppBarWithFAB()
    }
}

@Preview
@Composable
fun BottomAppBarWithFAB() {
    
    
    BottomAppBar(
        actions = {
    
    
            IconButton(onClick = {
    
     /* doSomething() */ }) {
    
    
                Icon(Icons.Filled.Check, contentDescription = "Localized description")
            }
            IconButton(onClick = {
    
     /* doSomething() */ }) {
    
    
                Icon(
                    Icons.Filled.Edit,
                    contentDescription = "Localized description",
                )
            }
        },
        floatingActionButton = {
    
    
            FloatingActionButton(
                onClick = {
    
     /* do something */ },
                containerColor = BottomAppBarDefaults.bottomAppBarFabColor,
                elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation()
            ) {
    
    
                Icon(Icons.Filled.Add, "Localized description")
            }
        },
        containerColor = MaterialTheme.colorScheme.primary,
    )
}

insert image description here

AlertDialog

Note that the following code is in material1 AlertDialog, there are two AlertDialogconstructors in Material1, and there is only one AlertDialogconstructor in the material3 package.

@Composable
fun DialogExample() {
    
    
    var openDialog by remember {
    
     mutableStateOf(false) }
    Column(verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
    
    
        Button(onClick = {
    
     openDialog = true }) {
    
    
            Text(text = "show AlertDialog")
        }
    }
    if (openDialog) {
    
    
        // material3中只有一个AlertDialog,而Material中有两个AlertDialog构造函数
        AlertDialog(
            onDismissRequest = {
    
     openDialog = false }, // 当用户点击对话框以外的地方或者按下系统返回键将会执行的代码
            title = {
    
    
                Text(
                    text = "开启位置服务",
                    style = MaterialTheme.typography.h5
                )
            },
            text = {
    
    
                Text(
                    text = "这将意味着,我们会给您提供精准的位置服务,并且您将接受关于您订阅的位置信息",
                    fontSize = 16.sp
                )
            },
            buttons = {
    
    
                Row(
                    modifier = Modifier.padding(all = 8.dp),
                    horizontalArrangement = Arrangement.Center
                ) {
    
    
                    Button(
                        modifier = Modifier.fillMaxWidth(),
                        onClick = {
    
     openDialog = false }
                    ) {
    
    
                        Text("必须接受!")
                    }
                }
            }

        )

    }
}

insert image description here

AlertDialogThe following code is a constructor that uses fixed 2 button slots

 @Composable
fun DialogExample() {
    
    
    var openDialog by remember {
    
     mutableStateOf(false) }
    Column(verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
    
    
        Button(onClick = {
    
     openDialog = true }) {
    
    
            Text(text = "show AlertDialog")
        }
    }
    if (openDialog) {
    
    
        AlertDialog(
            onDismissRequest = {
    
     openDialog = false }, // 当用户点击对话框以外的地方或者按下系统返回键将会执行的代码
            title = {
    
    
                Text(
                    text = "开启位置服务",
                    style = MaterialTheme.typography.h5
                )
            },
            text = {
    
    
                Text(
                    text = "这将意味着,我们会给您提供精准的位置服务,并且您将接受关于您订阅的位置信息",
                    fontSize = 16.sp
                )
            },
            confirmButton = {
    
    
                TextButton(
                    onClick = {
    
    
                        openDialog = false
                        println("点击了确认")
                    },
                ) {
    
    
                    Text(
                        "确认",
                        style = MaterialTheme.typography.body1
                    )
                }
            },
            dismissButton = {
    
    
                TextButton(
                    onClick = {
    
    
                        openDialog = false
                        println("点击了取消")
                    }
                ) {
    
    
                    Text(
                        "取消",
                        style = MaterialTheme.typography.body1
                    )
                }
            },
            shape = RoundedCornerShape(15.dp)
        )
    }
}

insert image description here

The effect in the material3 package AlertDialogis similar to the above, but the color is slightly different. (I personally feel that the color design of material3 is not as good as material)

Dialog

DialogThere are fewer parameters, which are AlertDialogsimpler than others, and contentthe content can be filled freely

@Composable
fun DialogExample3() {
    
    
    var flag by remember{
    
     mutableStateOf(false) }
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
    
    
        Button(onClick = {
    
     flag = true }) {
    
    
            Text("show Dialog")
        }
    }
    if (flag) {
    
    
        Dialog(onDismissRequest = {
    
     flag = false }) {
    
    
            Box(
                modifier = Modifier
                    .height(150.dp).width(300.dp)
                    .background(Color.White),
                contentAlignment = Alignment.Center
            ) {
    
    
                Column {
    
    
                    LinearProgressIndicator()
                    Text("加载中 ing...")
                }
            }
        }
    }
}

insert image description here

If you want to modify Dialogthe width and height, Modifieryou only need to modify the width and height of the content area:

@Composable
fun DialogExample5() {
    
    
    var flag by remember{
    
     mutableStateOf(true) }
    val width = 300.dp
    val height = 150.dp
    if (flag) {
    
    
        Dialog(onDismissRequest = {
    
     flag = false }) {
    
    
            Box(
                modifier = Modifier
                    .size(width, height)
                    .background(Color.White),
                contentAlignment = Alignment.Center
            ) {
    
    
                Text("宽300dp高150dp的Dialog")
            }
        }
    }
}

insert image description here

DialogSome behavior controls can propertiesbe specified via parameters:

@Composable
fun DialogExample4() {
    
    
    var flag by remember{
    
     mutableStateOf(true) }
    if (flag) {
    
    
        Dialog(
            onDismissRequest = {
    
     flag = false },
            properties = DialogProperties(
                dismissOnBackPress = true, // 是否可以响应back键关闭
                dismissOnClickOutside = true, // 是否可以点击对话框以外的区域取消
                securePolicy = SecureFlagPolicy.Inherit,
                usePlatformDefaultWidth = false // 对话框是否需要被限制在平台默认的范围内
            )
        ) {
    
    
            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .background(Color.Blue),
                contentAlignment = Alignment.Center
            ) {
    
    
                Text("Dialog全屏的效果")
            }
        }
    }
}

DropdownMenu

DropdownMenuIt is generally TopAppBarused in combination and placed Scaffoldin scaffolding.

@Composable
fun ScaffoldWithDropDownMenu() {
    
    
    Scaffold(topBar = {
    
     OptionMenu() }) {
    
     padding ->
        Box(modifier = Modifier.fillMaxSize().padding(padding),
            contentAlignment = Alignment.Center
        ) {
    
    
            Text("主页界面")
        }
    }
}

@Composable
fun OptionMenu(){
    
    
    var showMenu by remember {
    
     mutableStateOf(false) }
    val context = LocalContext.current
    TopAppBar(
        title = {
    
     Text("My AppBar") },
        actions = {
    
    
            IconButton(onClick = {
    
     context.showToast("Favorite") }) {
    
    
                Icon(Icons.Default.Favorite, "")
            }
            IconButton(onClick = {
    
     showMenu = !showMenu }) {
    
    
                Icon(Icons.Default.MoreVert, "")
            }
            DropdownMenu(
                expanded = showMenu,
                onDismissRequest = {
    
     showMenu = false },
                properties = PopupProperties(
                    focusable = true,
                    dismissOnBackPress = true,
                    dismissOnClickOutside = true,
                    securePolicy = SecureFlagPolicy.SecureOn,//设置安全级别,是否可以截屏
                )
            ) {
    
    
                DropdownMenuItem(onClick = {
    
    
                        showMenu = false
                        context.showToast("Settings")
                }) {
    
    
                    Text(text = "Settings")
                }
                DropdownMenuItem(onClick = {
    
    
                    showMenu = false
                    context.showToast("Logout")
                }) {
    
    
                    Text(text = "Logout")
                }
            }
        }
    )
}

insert image description here

If used alone DropdownMenu, the expected effect may not be obtained, for example, when a button is clicked, you want to pop up under the button DropdownMenu:

@Composable
fun DropDownMenuExample() {
    
    
    var expanded by remember {
    
     mutableStateOf(false) }
    Box(
        modifier = Modifier.size(300.dp),
        contentAlignment = Alignment.Center
    ) {
    
    
        IconButton(onClick = {
    
     expanded = !expanded }) {
    
    
            Icon(imageVector = Icons.Default.Add, contentDescription = null)
        }
        DropdownMenu(
            expanded = expanded,
            onDismissRequest = {
    
    expanded = false },
            offset = DpOffset(x = 10.dp, y = 10.dp), 
        ) {
    
    
            DropdownMenuItem(onClick = {
    
     expanded = false }) {
    
     Text(text = "Menu 0") }
            DropdownMenuItem(onClick = {
    
     expanded = false }) {
    
     Text(text = "Menu 1") }
            DropdownMenuItem(onClick = {
    
     expanded = false }) {
    
     Text(text = "Menu 2") }
        }
    }
}

insert image description here

It can be seen DropdownMenuthat it is not displayed in the correct position. Unlike the native pop-up menu PopupWindow, when the native control is shown, there is an anchor parameter to specify the anchor point, but there is no way to do this PopupWindowin Compose .DropdownMenu

This means that we need to modify the parameters manually DropdownMenu, just offsetget the coordinates of the clicked position and pass it in as the DropdownMenuoffset offset. To obtain the location information of the click, it can be achieved through the API pointerInputof the modifier :detectTapGestures

@Composable
fun PersonItem(
    personName: String,
    dropdownItems: List<DropDownItem>,
    modifier: Modifier = Modifier,
    onItemClick: (DropDownItem) -> Unit
) {
    
    
    var isMenuVisible by rememberSaveable {
    
     mutableStateOf(false) }
    var pressOffset by remember {
    
     mutableStateOf(DpOffset.Zero) }
    var itemHeight by remember {
    
     mutableStateOf(0.dp) }
    val interactionSource = remember {
    
     MutableInteractionSource() }
    val density = LocalDensity.current

    Card(
        elevation = 4.dp,
        modifier = modifier.onSizeChanged {
    
    
            itemHeight = with(density) {
    
     it.height.toDp() } // 保存item的高度
        }
    ) {
    
    
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .indication(interactionSource, LocalIndication.current)
                .pointerInput(true) {
    
    
                    detectTapGestures(
                        onLongPress = {
    
    
                            isMenuVisible = true
                            pressOffset = DpOffset(it.x.toDp(), it.y.toDp()) // 获取点击位置
                        },
                        onPress = {
    
    
                            // 实现点击Item时水波纹效果
                            val press = PressInteraction.Press(it)
                            interactionSource.emit(press)
                            tryAwaitRelease()
                            interactionSource.emit(PressInteraction.Release(press))
                        }
                    )
                }
                .padding(16.dp)
        ) {
    
    
            Text(text = personName)
        }
        DropdownMenu(
            expanded = isMenuVisible,
            onDismissRequest = {
    
     isMenuVisible = false },
            offset = pressOffset.copy(y = pressOffset.y - itemHeight) // y坐标减去item的高度
        ) {
    
    
            dropdownItems.forEach {
    
    
                DropdownMenuItem(onClick = {
    
    
                    onItemClick(it)
                    isMenuVisible = false
                }) {
    
    
                    Text(text = it.text)
                }
            }
        }
    }
}
data class DropDownItem(val text: String)
@Composable
fun CustomDropdownMenuExample() {
    
    
    val context = LocalContext.current
    LazyColumn(
        modifier = Modifier.fillMaxSize().padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
    
    
        items(
            listOf("Philipp", "Carl", "Martin", "Jake", "Jake", "Jake", "Jake", "Jake", "Philipp", "Philipp")
        ) {
    
    
            PersonItem(
                personName = it,
                dropdownItems = listOf(
                    DropDownItem("Item 1"),
                    DropDownItem("Item 2"),
                    DropDownItem("Item 3"),
                ),
                onItemClick = {
    
    context.showToast(it.text) },
                modifier = Modifier.fillMaxWidth()
            )
        }
    }
} 

insert image description here

Context Menu

There is no corresponding component in the Jetpack Compose Android library, if you want to do it, use the above DropdownMenuto achieve. But if you use JetBrains' Compose-Multiplatform for multi-platform development, there is a corresponding ContextMenu API support on the desktop side. After all, context menus on the desktop side are relatively common. You can refer to the official website tutorial: Context Menu in Compose for Desktop .

Guess you like

Origin blog.csdn.net/lyabc123456/article/details/131059925