Jetpack Compose の基本コンポーネント

ボタン

デフォルトのスタイル

Button任意のコンポーネントをlambdablock 内に渡すことができますComposableが、通常は 1 つがText内部に配置されます。

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

ここに画像の説明を挿入

ボタンの幅と高さ

幅を広くしたい場合や高さを高くしたい場合はButton、幅と高さを変更できますModifier。たとえば、で親コントロールを埋めるように指定したり、Columnパラメータ丸みを帯びた円弧を変更したりすることもできます。Modifier.fillMaxWidth()shapeButton

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指定でき、そのオブジェクトを使用して、オブジェクトのメソッドを通じてボタンのインタラクティブな状態を収集し、状態の値に応じて異なる背景またはテキストの色を作成できます。MutableInteractionSourceremembercollectIsPressedAsState()

// 创建 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)
    } 
}

ここに画像の説明を挿入

アイコンボタンの波紋をキャンセル

IconButtonBoxソース コードでは、 inModifier.clickableのパラメーターは実際にはIndicationripple に設定されています。ソース コードをコピーして独自のプロジェクトに追加し、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 種類の両方がサポートされており、アイコンを描画するためにさまざまな種類のリソースに応じて対応するブラシが内部的に作成されます。DrawablevectorResourceXMLimageResourcejpgpngpainterResourceDrawable

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必要があります。tintColor.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 に指定する方法SurfaceshapeもうCircleShape1 つは を使用してコンポーネント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 }
        )
    }
}

進行状況インジケーター

ProgressIndicatorCompose ではプログレスバーも水平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)),
        )
    }
}

ここに画像の説明を挿入

スライダー

SliderAndroidのオリジナルと同等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設定するだけでなく、次の方法でコンポーネント内のテキストの配置位置を設定することもできます。TextText

@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()
        )
    }
}

ここに画像の説明を挿入

基本テキストフィールド

TextFieldTextFieldこれらはすべてマテリアル デザインに従ってデザインされているため、内部の間隔は固定されています。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属性があります。alignmatchParentSize

@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ますDockedbottomBar

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

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

ここに画像の説明を挿入

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パラメータを使用して、固定トップバー効果を実現しますscrollBehaviorTopAppBarDefaults.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)
                    )
                }
            }
        }
    )
}

ここに画像の説明を挿入

TopAppBarDefaultsscrollBehavior他にも などがあります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 と組み合わせて使用​​しますが、単独で使用することもできます。ScaffoldbottomBar

@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 つのコンストラクターがありAlertDialogAlertDialogマテリアル 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のカラーデザインがマテリアルに及ばないように感じます)

ダイアログ

DialogAlertDialog他に比べてパラメータが少なくシンプルで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、アンカーポイントを指定するアンカーパラメータがありますが、 PopupWindowComposeではDropdownMenuこれを行う方法がありません。 。

これは、パラメータを手動で変更する必要があることを意味しますDropdownMenuoffsetクリックされた位置の座標を取得して、それをDropdownMenuオフセットとして渡すだけですoffsetクリックの位置情報を取得するには、pointerInputモディファイアのdetectTapGesturesAPI を使用して取得できます。

@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 のメニュー

おすすめ

転載: blog.csdn.net/lyabc123456/article/details/131059925