Jetpack Compose中的基本组件及组件提取

前言

除了布局组件外,Jetpack Compose 还提供了一系列其他常用的 UI 组件。

https://developer.android.google.cn/jetpack/compose/components?hl=zh-cn

https://developer.android.google.cn/courses/pathways/compose?hl=zh-cn

占位

1
Spacer(modifier = Modifier.height(16.dp))

分割线

1
2
3
4
Divider(
color = Color.Black,
modifier = Modifier.fillMaxHeight().width(1.dp)
)

文本

Text(文本):

1
Text("Hello, World!")

文本居中

1
2
3
4
5
6
7
8
Text(
text = "A",
modifier = Modifier
.background(Color.Red)
.width(30.dp).height(30.dp)
.wrapContentSize(Alignment.Center),
textAlign = TextAlign.Center,
)

设置行数

1
2
3
4
5
6
7
8
9
Text(
text = "ABC",
modifier = Modifier
.background(Color.Red)
.width(30.dp).height(30.dp)
.wrapContentSize(Alignment.Center),
textAlign = TextAlign.Center,
maxLines = 1
)

按钮

基本使用

1
2
3
4
5
6
7
8
9
10
11
Button(
modifier = Modifier.width(100.dp).height(40.dp),
shape = MaterialTheme.shapes.medium,
onClick = { /* 按钮点击事件 */ }
) {
Text(
text = "点击",
fontSize = 16.sp,
color = Color.White
)
}

自定义按钮

效果

image-20240321233209257

组件定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

@Composable
fun ZBtnView(text:String){
val mShape = RoundedCornerShape(6.dp)
Box(
modifier = Modifier
.fillMaxSize()
.clip(mShape)
) {
Box(
modifier = Modifier
.padding(0.dp,6.dp,0.dp,0.dp)
.fillMaxSize()
.background(
Color(0xff2B89E0),
shape =mShape
)
.clip(mShape)
)
Box(
modifier = Modifier
.padding(0.dp,0.dp,0.dp,2.5.dp)
.fillMaxSize()
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color(0xff8AEFFD),
Color(0xff3BD9FD),
Color(0xff32C8FD),
Color(0xff36B3FE),
Color(0xff50B8FF),
), // 定义垂直渐变色
startY = 0f,
),
shape = mShape
)
.clip(mShape)
)
Text(
text = text,
color = Color.White,
style = TextStyle(fontSize = 14.sp),
textAlign = TextAlign.Center,
modifier = Modifier
.align(Alignment.Center)
.wrapContentSize(Alignment.Center),
)
}
}

使用

1
2
3
4
5
6
7
8
Box(
modifier = Modifier
.align(Alignment.Bottom)
.size(72.dp, 28.dp)
.clip(RoundedCornerShape(6.dp))
.clickable { }) {
ZBtnView("安装")
}

单选按钮

1
2
3
4
5
6
7
8
@Composable
fun MyRadioBtn(onSelectChange: (Boolean) -> Unit) {
val isSelected = remember {mutableStateOf(false)}
RadioButton(selected = isSelected.value, onClick = {
isSelected.value = !isSelected.value
onSelectChange(isSelected.value)
})
}

Switch

1
2
3
4
5
6
7
8
9
10
@Composable
fun SwitchMinimalExample() {
var checked by remember { mutableStateOf(true) }
Switch(
checked = checked,
onCheckedChange = {
checked = it
}
)
}

文本输入

BasicTextField

1
2
3
4
BasicTextField(
value = "",
onValueChange = {},
)

TextField

1
2
3
4
5
6
7
8
9
10
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SimpleFilledTextFieldSample() {
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("姓名") }
)
}

输入类型为密码

1
2
3
4
5
6
7
8
9
10
11
12
@Composable
fun PasswordTextField() {
var password by rememberSaveable { mutableStateOf("") }

TextField(
value = password,
onValueChange = { password = it },
label = { Text("Enter password") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
)
}

彩虹色文字

1
2
3
4
5
6
7
8
9
10
11
12
13
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SimpleFilledTextFieldSample() {
var text by remember { mutableStateOf("") }
val brush = remember {
Brush.linearGradient(
colors = listOf<Color>(Color.Red, Color.Green, Color.Blue)
)
}
TextField(
value = text, onValueChange = { text = it }, textStyle = TextStyle(brush = brush)
)
}

OutlinedTextField

1
2
3
4
5
6
7
8
9
10
@Composable
fun SimpleOutlinedTextFieldSample() {
var text by remember { mutableStateOf("") }

OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") }
)
}

图片

适配模式

1
2
3
4
5
6
7
8
9
Image(
modifier = Modifier
.padding(10.dp)
.size(200.dp, 150.dp)
.background(color = Color.Yellow),
painter = painterResource(id = R.drawable.logo),
contentDescription = null,
contentScale = ContentScale.Crop
)

contentScale:

  • ContentScale.Fit 等比缩放 保证图片完全显示 默认的方式
  • ContentScale.Crop 等比缩放后居中剪裁
  • ContentScale.Inside 等比缩放保证图片完全显示
  • ContentScale.FillBounds 拉伸填充
  • ContentScale.FillHeight 等比缩放 填充高度
  • ContentScale.FillWidth 等比缩放 填充宽度
  • ContentScale.None 不缩放后居中剪裁

着色

非透明的PNG图片的区域添加着色。

1
2
3
4
5
6
7
8
Image(
modifier = Modifier
.padding(10.dp)
.size(200.dp, 150.dp),
painter = painterResource(id = R.drawable.logo),
contentDescription = null,
colorFilter = ColorFilter.tint(color = Color.Green, BlendMode.SrcAtop)
)

圆角

圆形

1
2
3
4
5
6
7
8
9
10
Image(
modifier = Modifier
.padding(10.dp)
.size(200.dp)
.background(color = Color.Yellow)
.clip(shape = CircleShape),
painter = painterResource(id = R.drawable.logo),
contentDescription = null,
contentScale = ContentScale.Crop,
)

圆角

1
2
3
4
5
6
7
8
9
10
Image(
modifier = Modifier
.padding(10.dp)
.size(200.dp)
.background(color = Color.Yellow)
.clip(shape = RoundedCornerShape(20.dp)),
painter = painterResource(id = R.drawable.logo),
contentDescription = null,
contentScale = ContentScale.Crop,
)

背景剪裁

图片的背景是不会被剪裁的,我们可以在外面套一个Box做剪裁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Box(
modifier = Modifier
.padding(10.dp)
.size(200.dp)
.clip(shape = RoundedCornerShape(20.dp)),
) {
Image(
painter = painterResource(id = R.drawable.logo),
contentDescription = null,
modifier = Modifier
.background(color = Color.Yellow)
.fillMaxSize(),
contentScale = ContentScale.Crop
)
}

加载本地图片

1
2
3
4
Image(
painter = painterResource(R.drawable.my_image),
contentDescription = "My Image"
)

图片角标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Image(
painter = painterResource(id = R.drawable.logo),
modifier = Modifier
.padding(10.dp)
.size(46.dp)
.drawWithContent {
drawContent()
drawCircle(
Color.Red,
5.dp.toPx(),
Offset(size.width - 1.dp.toPx(), 1.dp.toPx())
)

},
contentDescription = "头像"
)

加载网络图片

添加依赖

1
implementation("io.coil-kt:coil-compose:2.6.0")

添加网络权限

1
<uses-permission android:name="android.permission.INTERNET" />

使用

1
2
3
4
5
6
7
@Composable
fun LoadWebImage(url:String){
AsyncImage(
model = url,
contentDescription = null,
)
}

调用

1
LoadWebImage(url = "https://www.psvmc.cn/head.jpg")

设置占位图

1
2
3
4
5
6
7
8
9
10
11
12
13
@Composable
fun LoadWebImage(url:String){
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(url)
.crossfade(true)
.build(),
placeholder = painterResource(R.drawable.ic_launcher_foreground),
contentDescription = stringResource(R.string.app_name),
contentScale = ContentScale.Crop,
modifier = Modifier.clip(CircleShape).size(60.dp)
)
}

加载中动画

1
CircularProgressIndicator(modifier = Modifier.size(30.dp))

消息框

Toast

组件化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import android.annotation.SuppressLint
import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

@SuppressLint("CoroutineCreationDuringComposition")
@Composable
fun ShowToast(message: String) {
val context = LocalContext.current
// 启动一个协程来显示 Toast
CoroutineScope(Dispatchers.Main).launch {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}

调用

1
2
3
4
5
var showToast by remember { mutableStateOf(false) }

if (showToast) {
ShowToast("哈哈");
}

组件内方法

1
2
3
4
5
6
7
8
val context = LocalContext.current

// 显示 Toast 的函数
fun showToast(message: String) {
CoroutineScope(Dispatchers.Main).launch {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}

Snackbar

基本示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Composable
fun SnackbarExample() {
var snackbarVisible by remember { mutableStateOf(false) }

Box {
Button(onClick = { snackbarVisible = true }) {
Text("显示 Snackbar")
}
if (snackbarVisible) {
Snackbar(
content = { Text(text = "文本") },
action = {
Button(onClick = { snackbarVisible = false }) {
Text(text = "关闭")
}
}
)
}
}
}

使用snackbarHost

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
floatingActionButton = {

}
) { contentPadding ->
Box {
Button(
modifier = Modifier.padding(10.dp).width(100.dp).height(40.dp),
shape = MaterialTheme.shapes.medium,
onClick = {
scope.launch {
snackbarHostState.showSnackbar("我是提示消息")
}
}
){
Text(
text = "点击",
fontSize = 16.sp,
color = Color.White
)
}
}
}

对话框

1
2
3
4
5
6
7
8
9
Dialog(onDismissRequest = { /* 关闭对话框事件 */ }) {
Surface(
modifier = Modifier.size(300.dp),
shape = RoundedCornerShape(16.dp),
color = MaterialTheme.colorScheme.background
) {
Text("文字")
}
}

隐藏键盘

1
2
3
4
5
6
val keyboardController = LocalSoftwareKeyboardController.current

// 在需要隐藏键盘的地方调用这个函数
fun hideKeyboard() {
keyboardController?.hide()
}

加载网页

1
2
3
4
5
6
7
8
9
10
11
12
13
@Composable
fun WebViewContainer(url: String) {
AndroidView(factory = { context ->
WebView(context).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
webViewClient = WebViewClient()
loadUrl(url)
}
})
}

视频播放器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.compose.ui.viewinterop.AndroidView
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.ui.PlayerView
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util

@Composable
fun VideoPlayer(videoUrl: String) {
val context = LocalContext.current
val exoPlayer = SimpleExoPlayer.Builder(context).build().apply {
val dataSourceFactory = DefaultDataSourceFactory(
context,
Util.getUserAgent(context, context.getString(R.string.app_name))
)
val videoSource = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(Uri.parse(videoUrl))
prepare(videoSource)
}
AndroidView(factory = { ctx ->
PlayerView(ctx).apply {
player = exoPlayer
useController = true
}
}, update = { playerView ->
playerView.player = exoPlayer
})
}

实战-登录页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LoginScreen(onLoginClicked: (String, String) -> Unit) {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
Log.i("LoginScreen", "Username: $username, Password: $password")
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
OutlinedTextField(
value = username,
onValueChange = { username = it },
label = { Text("用户名") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("密码") },
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
Log.i("登录点击", "Username: $username, Password: $password")
onLoginClicked(username, password)
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = "登录")
}
}
}

调用

1
2
3
LoginScreen { name, pwd ->
Log.i("LoginScreen", "Username: $name, Password: $pwd")
};

组件提取

因为Compose组件本质都是方法,所以可以使用提取方法来提取组件

快捷键:Ctrl+Alt+M