Button
default style
Button
Any component lambda
can be passed in the block Composable
, but generally one is placed Text
inside
Button(onClick = {
println("确认onClick") }) {
Text("默认样式")
}
The width and height of the button
If you want to be wider or taller Button
, you can Modifier
modify the width and height, for example, Column
you can Modifier.fillMaxWidth()
specify to fill up the parent control in , and you can also modify the rounded arc by shape
parametersButton
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Button(
onClick = {
println("确认onClick") },
modifier = Modifier.fillMaxWidth().padding(all = 5.dp),
shape = RoundedCornerShape(15.dp)
) {
Text("指定圆角弧度")
}
}
button border
Specify the border of the button via the parameter Button
ofborder
Button(
onClick = {
println("click the button") },
border = BorderStroke(1.dp, Color.Red)
) {
Text(text = "按钮的边框")
}
disabled state of the button
Specify the disabled state of the button via the parameter Button
ofenabled
Button(
onClick = {
println("click the button") },
enabled = false
) {
Text(text = "禁用的按钮")
}
button content
Since Button
the internal use of a Row
component is used for packaging, multiple components lambda
can 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("喜欢")
}
Customize the background of the button pressed state
An object can interactionSource
be 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 valueMutableInteractionSource
remember
collectIsPressedAsState()
// 创建 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)
}
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 = "文本按钮")
}
border button
OutlinedButton(onClick = {
println("click the OutlinedButton") }) {
Text(text = "边框按钮")
}
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)
}
}
Cancel IconButton's ripple
IconButton
In the source code, the parameter of Box
in is actually set to ripple, we just need to copy the source code and add it to our own project, and set it toModifier.clickable
Indication
indication
null
@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)
}
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)
)
ElevatedButton
Note that this is in androidx.compose.material3
the package. elevation
The 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")
}
You can also not specify elevation
, it has a default value, the default effect is as follows
Divider
Divider
Is 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")
}
}
Icon
Icon
The main parameters of:
ImageVector
: vector graphics object, which can display icons in SVG formatImageBitmap
: Bitmap object, which can display icons in JPG, PNG and other formatstint
: the color of the iconPainter
: Represents a custom brush, which can be used toCanvas
directly draw icons on . In addition to directly passing in specific types of instances, we can alsores/
set icons through image resources under .
ImageVector
and ImageBitmap
both provide the corresponding Drawable
method of loading resources, vectorResource
which are used to load a vector XML
, imageResource
used to load jpg
or png
a picture. painterResource
Both of the above two types Drawable
are 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
)
Icon
Loading resource image shows black image not loaded?
- Don't panic, because the default
tint
mode isLocalContentColor.current
, we need to remove its default coloring mode andtint
set the property toColor.Unspecified
Icon(
bitmap = ImageBitmap.imageResource(id = R.drawable.ic_head3),
contentDescription = "图片资源",
tint = Color.Unspecified,
modifier = Modifier.size(100.dp).clip(CircleShape),
)
Icon
Supports 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),
)
Image
Image
When a component loads a local resource image, it Icon
is similar to the use of , and the resource image is loaded by painter
specifying parameterspainterResource
R.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)
)
}
}
}
Among them, contentScale
the parameter is the image scaling type, and the value is in ContentScale
the companion object. The meaning refers to the native scaling type ImageView
of 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 Surface
the component to wrap it, and then specify Surface
the shape
as CircleShape
, and the other is to use Modifier.clip(CircleShape)
to directly act on Image
the 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,
This is because the default parameter Image
of the source code in contentScale
is 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.Crop
also maintain the aspect ratio, but try to make the width or height completely full. So we contentScale
set ContentScale.Crop
to solve this problem.
Compose's built-in Image
can 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 Coil
load 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
SubcomposeAsyncImage
The 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 SubcomposeAsyncImage
constraint information needs to be obtained in advance, and Subcomposelayout
the constraint information of the parent component or the constraint information of other components can be obtained before the subcomponent is synthesized. SubcomposeAsyncImage
It 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.Subcomposelayout
content
SubcomposeAsyncImage
@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
ProgressIndicator
The progress bar is also divided into two types in Compose, horizontal LinearProgressIndicator
and 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)),
)
}
}
Slider
Slider
Equivalent 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)
},
)
}
}
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
)
}
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
)
}
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 }
})
}
}
}
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")
}
}
Chip
The 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}
)
}
}
}
}
FilterChip
FilterChip
It 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")
}
}
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
)
}
}
In addition to textAlign
setting Text
the alignment position in the parent component, you can also set the Text
alignment 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,
)
}
}
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()
)
}
}
BasicTextField
TextField
They are all designed according to Material Design, so some spacing inside is fixed. If you want to customize the TextField
height 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("关闭") }
}
}
}
)
}
}
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()
)
}
}
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()
)
}
}
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")
}
}
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)
}
}
BoxScope
There are two Box
child-specific Modifier
attributes in the : align
andmatchParentSize
@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))
}
}
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()
}
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)
}
}
Surface
Surface
You can quickly set the shape, shadow, border, color, etc. of the interface, which can reduce Modifier
the 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")
}
}
}
}
@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(),
)
}
}
}
}
@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(),
)
}
}
}
}
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() }
}
)
}
Scaffold
Scaffold
It 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()
}
}
}
Let be embedded in the middle of the bottom floatingActionButton
as :Docked
bottomBar
@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("主页界面")
}
}
}
Change the shape of floatingActionButton
the 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("主页界面")
}
}
}
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")
}
)
}
}
}
)
}
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)
)
}
}
}
}
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
)
}
}
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
)
}
}
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 FancyAnimatedIndicator
and FancyIndicator
are 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)))
}
FancyTab
It 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:
Without animation:
TopAppBar
TopAppBar
It is in the Material3 package and is generally Scaffold
used 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)
)
}
}
}
)
}
fixed top bar
Use the specified TopAppBar
parameter scrollBehavior
to 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)
)
}
}
}
)
}
collapse top bar
Through the specified TopAppBar
parameter scrollBehavior
to 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)
)
}
}
}
)
}
TopAppBarDefaults
There are other scrollBehavior
such as in exitUntilCollapsedScrollBehavior()
, the effect is similar, you can choose according to your needs.
CenterAlignedTopAppBar
This is TopAppBar
not 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)
)
}
}
}
)
}
MediumTopAppBar
One size bigger TopAppBar
, MediumTopAppBar
with a title
slot, 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)
)
}
}
}
)
}
LargeTopAppBar
One size larger TopAppBar
, but the height LargeTopAppBar
of title
the 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)
)
}
}
}
)
}
BottomAppBar
BottomAppBar
Usually Scaffold
used in conjunction with Scaffolding, placed in the slot Scaffold
of bottomBar
the , 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,
)
}
AlertDialog
Note that the following code is in material1 AlertDialog
, there are two AlertDialog
constructors in Material1, and there is only one AlertDialog
constructor 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("必须接受!")
}
}
}
)
}
}
AlertDialog
The 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)
)
}
}
The effect in the material3 package AlertDialog
is 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
Dialog
There are fewer parameters, which are AlertDialog
simpler than others, and content
the 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...")
}
}
}
}
}
If you want to modify Dialog
the width and height, Modifier
you 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")
}
}
}
}
Dialog
Some behavior controls can properties
be 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
DropdownMenu
It is generally TopAppBar
used in combination and placed Scaffold
in 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")
}
}
}
)
}
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") }
}
}
}
It can be seen DropdownMenu
that 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 PopupWindow
in Compose .DropdownMenu
This means that we need to modify the parameters manually DropdownMenu
, just offset
get the coordinates of the clicked position and pass it in as the DropdownMenu
offset offset
. To obtain the location information of the click, it can be achieved through the API pointerInput
of 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()
)
}
}
}
Context Menu
There is no corresponding component in the Jetpack Compose Android library, if you want to do it, use the above DropdownMenu
to 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 .