ボタン
デフォルトのスタイル
Button
任意のコンポーネントをlambda
block 内に渡すことができますComposable
が、通常は 1 つがText
内部に配置されます。
Button(onClick = {
println("确认onClick") }) {
Text("默认样式")
}
ボタンの幅と高さ
幅を広くしたい場合や高さを高くしたい場合はButton
、幅と高さを変更できますModifier
。たとえば、で親コントロールを埋めるように指定したり、Column
パラメータで丸みを帯びた円弧を変更したりすることもできます。Modifier.fillMaxWidth()
shape
Button
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Button(
onClick = {
println("确认onClick") },
modifier = Modifier.fillMaxWidth().padding(all = 5.dp),
shape = RoundedCornerShape(15.dp)
) {
Text("指定圆角弧度")
}
}
ボタンの境界線
Button
のパラメータでborder
ボタンの境界線を指定します。
Button(
onClick = {
println("click the button") },
border = BorderStroke(1.dp, Color.Red)
) {
Text(text = "按钮的边框")
}
ボタンの無効状態
Button
のパラメータを介してenabled
ボタンの無効状態を指定します。
Button(
onClick = {
println("click the button") },
enabled = false
) {
Text(text = "禁用的按钮")
}
ボタンの内容
Button
コンポーネントの内部使用はRow
パッケージングに使用されるため、実際には複数のコンポーネントをlambda
ブロック内に渡すことができ、それらは横一列に配置されます。Composable
Button(onClick = {
println("喜欢onClick") }) {
Icon(
// Material 库中的图标,Icons.Filled下面有很多自带的图标
Icons.Filled.Favorite,
contentDescription = null,
modifier = Modifier.size(ButtonDefaults.IconSize)
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("喜欢")
}
ボタンを押した状態の背景をカスタマイズする
オブジェクトはパラメータを通じてinteractionSource
指定でき、そのオブジェクトを使用して、オブジェクトのメソッドを通じてボタンのインタラクティブな状態を収集し、状態の値に応じて異なる背景またはテキストの色を作成できます。MutableInteractionSource
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)
}
テキストボタン
通常の状態では単なるテキストですが、クリックすることができ、クリックすると波紋効果が発生します。
TextButton(onClick = {
println("click the TextButton") },) {
Text(text = "文本按钮")
}
境界線ボタン
OutlinedButton(onClick = {
println("click the OutlinedButton") }) {
Text(text = "边框按钮")
}
アイコンボタン
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)
}
}
アイコンボタンの波紋をキャンセル
IconButton
Box
ソース コードでは、 inModifier.clickable
のパラメーターは実際にはIndication
ripple に設定されています。ソース コードをコピーして独自のプロジェクトに追加し、次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()
}
}
使用:
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(
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)
)
エレベーテッドボタン
androidx.compose.material3
こちらはパッケージに入っておりますのでご了承ください。elevation
ボタンの高さ効果はパラメータで指定できます
ElevatedButton(
onClick = {
},
elevation = ButtonDefaults.buttonElevation(defaultElevation = 5.dp, pressedElevation = 10.dp)
) {
Text(text = "ElevatedButton")
}
を指定することもできませんelevation
。デフォルト値があり、デフォルトの効果は次のとおりです。
ディバイダー
Divider
分割線です。色や太さなどを指定できます。
@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
主なパラメータは次のとおりです。
ImageVector
: SVG 形式でアイコンを表示できるベクター グラフィックス オブジェクトImageBitmap
: JPG、PNG、その他の形式でアイコンを表示できるビットマップオブジェクトtint
: アイコンの色Painter
: にアイコンを直接描画するために使用できるカスタム ブラシを表しますCanvas
。特定の種類のインスタンスを直接渡すことに加えて、res/
の画像リソースを通じてアイコンを設定することもできます。
ImageVector
どちらも、リソースをロードするImageBitmap
対応するメソッドを提供します。これらのメソッドは、ベクトルのロードに使用され、ピクチャのロードに使用されます。上記 2 種類の両方がサポートされており、アイコンを描画するためにさまざまな種類のリソースに応じて対応するブラシが内部的に作成されます。Drawable
vectorResource
XML
imageResource
jpg
png
painterResource
Drawable
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
リソース画像をロードすると黒い画像が表示され、ロードされていませんか?
- パニックにならないでください。デフォルトの
tint
モードは であるため、デフォルトのカラーリング モードを削除し、プロパティを次のように設定するLocalContentColor.current
必要があります。tint
Color.Unspecified
Icon(
bitmap = ImageBitmap.imageResource(id = R.drawable.ic_head3),
contentDescription = "图片资源",
tint = Color.Unspecified,
modifier = Modifier.size(100.dp).clip(CircleShape),
)
Icon
あらゆるタイプの画像リソースをサポートし、画像コンポーネントとして使用できます
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
コンポーネントがローカル リソース イメージをロードするときは、Icon
の使用と同様であり、リソース イメージはpainter
パラメータを指定してpainterResource
ロードされます。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)
)
}
}
}
このうちcontentScale
パラメータは画像のスケーリングタイプ、値はContentScale
コンパニオンオブジェクトにあり、意味はAndroidImageView
のネイティブスケーリングタイプを指しておりscaleType
、ほぼ同様です。
また、上記コードのように円形画像を設定するには 2 つの方法があり、1 つはSurface
コンポーネントを使用してラップし、 を as に指定する方法Surface
、shape
もうCircleShape
1 つは を使用してコンポーネントModifier.clip(CircleShape)
に直接作用する方法です。Image
ただし、どちらの方法でも、幅と高さを一定の等しい値に設定する必要があります。そうしないと、完全な円になりません。
円に設定すると画像の上下が切れてしまう場合がありますが、
これは、Image
ソース コードのデフォルト パラメータが であるcontentScale
ためですContentScale.Fit
。つまり、画像のアスペクト比を維持し、画像全体が完全に表示されるまで縮小します。またContentScale.Crop
、アスペクト比を維持しますが、幅または高さを完全に埋めるようにしてください。そこで私たちはこの問題を解決することにcontentScale
しました。ContentScale.Crop
Compose の組み込みでは、Image
リソース マネージャーにイメージ ファイルのみをロードできます。ネットワーク イメージや他のローカル パスにあるファイルをロードしたい場合は、https Coil
: //coil-kt.github.io/coil/を使用してネットワーク イメージをロードできます。作曲する /
@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)
)
}
}
サブコンポーズAsyncImage
SubcomposeAsyncImage
画像の最終的なサイズは、コンポーネントの制約空間に従って決定されます。つまり、画像をロードする前に、制約情報をSubcomposeAsyncImage
事前に取得する必要があり、Subcomposelayout
親コンポーネントの制約情報またはコンポーネントの制約情報を取得する必要があります。他のコンポーネントは、サブコンポーネントが合成される前に取得できます。SubcomposeAsyncImage
これは能力に
依存すること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")
}
}
}
}
コイルはネットワーク SVG イメージをロードします
@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
Compose ではプログレスバーも水平LinearProgressIndicator
と円形の 2 種類に分かれておりCircularProgressIndicator
、プログレス値を指定しない場合は無限アニメーションのプログレスバーとなります。
@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
Androidのオリジナルと同等SeekBar
@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)
},
)
}
}
レンジスライダー
範囲選択用の SeekBar は現在実験的な 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
)
}
スペーサー
プレースホルダーコンポーネント
@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
)
}
スイッチ、チェックボックス、ラジオボタン
使用例
@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 }
})
}
}
}
チップ
ラベルに似たコンポーネントでは、境界線や色などを設定したり、クリック イベントに応答したりできます。
@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
デフォルトでは選択されていませんが、クリック時に背景色を変更することで選択できます。たとえば、次のようになります。
@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
選択状態 がありChip
、選択状態では背景色、コンテンツ色、選択アイコンを設定できます。
@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")
}
}
文章
@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
)
}
}
親コンポーネントで配置位置をtextAlign
設定するだけでなく、次の方法でコンポーネント内のテキストの配置位置を設定することもできます。Text
Text
@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,
)
}
}
ガウスぼかし (Android 12 以降のみ)
// 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)
)
}
}
テキストフィールド
@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()
)
}
}
基本テキストフィールド
TextField
TextField
これらはすべてマテリアル デザインに従ってデザインされているため、内部の間隔は固定されています。aやその他のカスタム エフェクトの高さをカスタマイズしたい場合は、次を使用する必要があります。BasicTextField
// 可自定义的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("关闭") }
}
}
}
)
}
}
Bilibili アプリに似た検索ボックス:
@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()
)
}
}
アウトライン化されたテキストフィールド
枠線付きの入力ボックス
@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()
)
}
}
カード
カードコンポーネント
@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")
}
}
箱
順番に積み重ねると、次のようになりますFrameLayout
@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
とには 2 つの子Box
固有のModifier
属性があります。align
matchParentSize
@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))
}
}
制約のあるボックス
サンプルコード 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()
}
サンプルコード 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
インターフェイスの形状、影、境界線、色などを素早く設定できるため、Modifier
使用量を削減できます。
@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(),
)
}
}
}
}
モーダルボトムシートレイアウト
通常、下部のポップアップ メニューに使用されます
@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
これはマテリアル デザインのレイアウト構造を構成するための足場であり、構成用のデフォルトのピットを提供します。
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()
}
}
}
底部の中央に次のように埋め込みfloatingActionButton
ます。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("主页界面")
}
}
}
floatingActionButton
埋め込まれた底部の形状を変更します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("主页界面")
}
}
}
背景足場
@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")
}
)
}
}
}
)
}
ボトムシート足場
@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)
)
}
}
}
}
タブ行
@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
)
}
}
スクロール可能なタブ行
@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
)
}
}
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
)
}
}
ここでFancyAnimatedIndicator
、 と はFancyIndicator
次のように定義されます。
@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
次のように定義されます。
@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)
)
}
}
}
アニメーション付き:
アニメーションなし:
トップアプリバー
TopAppBar
これはマテリアル 3 パッケージに含まれており、通常はScaffold
スキャフォールディングで使用されます。
@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)
)
}
}
}
)
}
固定トップバー
指定されたTopAppBar
パラメータを使用して、固定トップバー効果を実現しますscrollBehavior
。TopAppBarDefaults.pinnedScrollBehavior()
コンテンツがスクロールされると動的に変化する色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)
)
}
}
}
)
}
トップバーを折りたたむ
指定されたTopAppBar
パラメータを使用して、上部のバーを折りたたむ効果を実現しscrollBehavior
ますTopAppBarDefaults.enterAlwaysScrollBehavior()
@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
scrollBehavior
他にも などがありますexitUntilCollapsedScrollBehavior()
が、効果は同様なので、ニーズに応じて選択できます。
CenterAlignedTopAppBar
これは とTopAppBar
あまり変わりません。つまり、タイトルは中央に配置されますが、スクロール イベントには応答しません。
@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)
)
}
}
}
)
}
中トップアプリバー
1 サイズ大きくTopAppBar
、スロットMediumTopAppBar
がありtitle
、デフォルトは展開されています。これにより、トップ バーが折りたたまれたときにタイトルが折りたたまれたり展開されたりする効果を実現できます。
@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)
)
}
}
}
)
}
ラージトップアプリバー
1 サイズ大きくなりますTopAppBar
が、領域LargeTopAppBar
の高さtitle
は固定されており変更できないため、2 行のタイトルに適しています。
@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
通常はのスロットにScaffold
配置して Scaffolding と組み合わせて使用しますが、単独で使用することもできます。Scaffold
bottomBar
@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,
)
}
アラートダイアログ
次のコードはマテリアル 1 にありAlertDialog
、マテリアル 1 には 2 つのコンストラクターがありAlertDialog
、AlertDialog
マテリアル 3 パッケージにはコンストラクターが 1 つだけあることに注意してください。
@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("必须接受!")
}
}
}
)
}
}
次のコードは、固定 2 つのボタン スロットを使用するAlertDialog
コンストラクターです。
@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)
)
}
}
マテリアル 3 パッケージのエフェクトAlertDialog
は上記と似ていますが、色が若干異なります。(個人的にはマテリアル3のカラーデザインがマテリアルに及ばないように感じます)
ダイアログ
Dialog
AlertDialog
他に比べてパラメータが少なくシンプルでcontent
内容を自由に記入できます。
@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...")
}
}
}
}
}
Dialog
幅と高さを変更したい場合は、Modifier
コンテンツ領域の幅と高さを変更するだけです。
@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
一部の動作制御はproperties
パラメータを介して指定できます。
@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
一般的TopAppBar
には組み合わせて足場に設置して使用されますScaffold
。
@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")
}
}
}
)
}
単独で使用するDropdownMenu
と期待した効果が得られない場合があります。たとえば、ボタンがクリックされたときにボタンの下にポップアップを表示したい場合があります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") }
}
}
}
DropdownMenu
ネイティブのポップアップメニューとは異なり、ネイティブコントロールを表示する場合PopupWindow
、アンカーポイントを指定するアンカーパラメータがありますが、 PopupWindow
ComposeではDropdownMenu
これを行う方法がありません。 。
これは、パラメータを手動で変更する必要があることを意味しますDropdownMenu
。offset
クリックされた位置の座標を取得して、それをDropdownMenu
オフセットとして渡すだけですoffset
。クリックの位置情報を取得するには、pointerInput
モディファイアのdetectTapGestures
API を使用して取得できます。
@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()
)
}
}
}
コンテキストメニュー
Jetpack Compose Android ライブラリには対応するコンポーネントがありません。これを実行したい場合は、上記の方法を使用してDropdownMenu
実現します。ただし、マルチプラットフォーム開発に JetBrains の Compose-Multiplatform を使用する場合、デスクトップ側に対応する ContextMenu API サポートがあります。結局のところ、デスクトップ側のコンテキスト メニューは比較的一般的です。公式 Web サイトのチュートリアルを参照してください: Context Compose for Desktop のメニュー。