Jetpack Compose 通过 @Composable
注解标示的函数(可组合函数)来描述界面上元素,为方便起见,统一称为 "组件"。
基本组件
Text
Text 文本组件,相当于原生View 中的 TextView
@Composable
fun TextStringDemo() {
Text(
text = stringResource(id = R.string.text_content).repeat(50), // String 类型
style = TextStyle(
fontSize = 18.sp,
color = Color.Blue,
fontStyle = FontStyle.Normal,
textAlign = TextAlign.Left
),
fontWeight = FontWeight.Medium,
overflow = TextOverflow.Ellipsis,
maxLines = 5
)
}
Text 显示富文本
@Composable
fun TextAnnotatedStringDemo() {
val builder = AnnotatedString.Builder("Hello").apply {
pushStyle(
SpanStyle(
color = Color.Red,
fontSize = 24.sp,
fontStyle = FontStyle.Normal
)
)
append("World, ")
pop()
append("The new Android UI")
}
val text = builder.toAnnotatedString()
Text(
text = text, // AnnotatedString类型
fontSize = 22.sp, modifier = Modifier.clickable {
}
)
}
Button
Button 按钮组件,相当于原生View 中的 Button
@Composable
fun ButtonDemo() {
Button(
onClick = {
println("button clicked")
},
) {
Text("我是按钮 button")
}
}
@Composable
fun OutlinedButtonDemo() {
val context = LocalContext.current
OutlinedButton(
onClick = {
showToast(context, "button clicked")
},
border = BorderStroke(1.dp, Color.Red),
colors = ButtonDefaults.outlinedButtonColors(backgroundColor = Color.Transparent),
shape = RoundedCornerShape(50)
) {
Text("OutlinedButton", color = Color.Red)
}
}
@Composable
fun IconToggleButtonDemo() {
val checkState = remember { mutableStateOf(true) }
IconToggleButton(
checked = checkState.value,
onCheckedChange = {
checkState.value = it
}) {
Icon(
Icons.Filled.Favorite,
contentDescription = null,
tint = if (checkState.value) {
Color.Red
} else {
Color.Gray
}
)
}
}
Modifier
Modifier 组件修饰器,通过 Modifier 可以修改组件的大小,形状,边距,边框,点击等
@Composable
fun TextStringDemo() {
Text(
text = stringResource(id = R.string.text_content).repeat(50), // String 类型
style = TextStyle(
fontSize = 18.sp,
color = Color.Blue,
fontStyle = FontStyle.Normal,
textAlign = TextAlign.Left
),
fontWeight = FontWeight.Medium,
overflow = TextOverflow.Ellipsis,
maxLines = 5,
modifier = Modifier
.background(Color.LightGray)
.padding(20.dp)
)
}
TextField
TextField 输入框组件,相当于 原生View 中的 EditText
@Composable
fun TextFieldDemo() {
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = {
text = it
},
placeholder = {
Text("input password")
},
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
leadingIcon = {
Icon(
imageVector = Icons.Filled.Lock,
contentDescription = null
)
}
)
}
Image
Image 图片组件,相当于 原生View 中的 ImageView
@Composable
fun ImageDemo() {
val context = LocalContext.current
Image(
modifier = Modifier
.width(100.dp)
.height(100.dp)
.border(
width = 1.5.dp,
color = Color(0xFFDA8C1A),
shape = CircleShape
)
.padding(3.dp)
.clip(shape = CircleShape)
.clickable {
Toast
.makeText(context, "image clicked", Toast.LENGTH_LONG)
.show()
},
painter = painterResource(id = R.drawable.zoom),
contentScale = ContentScale.Crop,
contentDescription = null
)
}
// 网络图片需要添加依赖
@Composable
fun NetworkImageDemo() {
Image(
modifier = Modifier
.width(100.dp)
.height(100.dp),
contentScale = ContentScale.Crop,
painter = rememberGlidePainter(
request = "https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/1/9/168329d14a4d9f35~tplv-t2oaga2asx-image.image",
fadeIn = true,
previewPlaceholder = R.drawable.zoom
),
contentDescription = "network image",
)
}
RadioButton
@Composable
fun RadioButtonDemo() {
val labels = listOf("Android", "iOS", "Kotlin", "Java", "Jetpack Compose")
val indexOfChecked = remember { mutableStateOf(-1) }
Column {
labels.forEachIndexed { index, value ->
Row(
modifier = Modifier
.padding(5.dp)
.selectableGroup()
) {
RadioButton(
selected = indexOfChecked.value == index,
onClick = {
indexOfChecked.value = index
}
)
Spacer(modifier = Modifier.width(10.dp))
Text(value)
}
}
}
}
Switch
@Composable
fun SwitchDemo() {
val checkedState = remember { mutableStateOf(false) }
Row(verticalAlignment = Alignment.CenterVertically) {
Switch(
checked = checkedState.value,
onCheckedChange = {
checkedState.value = it
}
)
Text(if (checkedState.value) "开启" else "关闭")
}
}
ProgressIndicator
@Preview(showBackground = true)
@Composable
fun LinearProgressIndicatorDemo() {
LinearProgressIndicator()
}
@Preview(showBackground = true)
@Composable
fun LinearProgressIndicatorDemo2() {
var progress by remember {
mutableStateOf(0.5f)
}
val animProgress by animateFloatAsState(targetValue = progress)
Column(
modifier = Modifier.padding(10.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
LinearProgressIndicator(
progress = animProgress,
color = Color.Red,
modifier = Modifier
.width(300.dp)
.height(20.dp)
.clip(shape = RoundedCornerShape(10.dp))
)
Spacer(modifier = Modifier.height(20.dp))
Button(onClick = {
if (progress < 1.0f) {
progress += 0.1f
}
}) {
Text(text = "increase")
}
}
}
@Preview(showBackground = true)
@Composable
fun CircularProgressIndicatorDemo2() {
CircularProgressIndicator()
}
@Preview(showBackground = true)
@Composable
fun CircularProgressIndicatorDemo() {
var progress by remember {
mutableStateOf(0.5f)
}
val animProgress by animateFloatAsState(targetValue = progress)
Column(horizontalAlignment = Alignment.CenterHorizontally) {
CircularProgressIndicator(
progress = animProgress,
modifier = Modifier
.height(100.dp)
.width(100.dp),
color = Color.Red,
strokeWidth = 5.dp
)
Spacer(modifier = Modifier.height(20.dp))
Button(onClick = {
if (progress < 1f) {
progress += 0.1f
}
}) {
Text("Add")
}
}
}
Divider
@Composable
fun DividerDemo() {
Column {
Text("Hello World,".repeat(50), maxLines = 2, modifier = Modifier.padding(15.dp))
Divider(startIndent = 15.dp)
Text("Hello World,".repeat(50), maxLines = 2, modifier = Modifier.padding(15.dp))
Divider(startIndent = 15.dp)
Text("Hello World,".repeat(50), maxLines = 2, modifier = Modifier.padding(15.dp))
Divider(startIndent = 15.dp)
}
}
布局组件
Box
Box 组件,相当于 原生View 中的 FrameLayout
@Preview(showBackground = true)
@Composable
fun BoxDemo() {
Box(
modifier = Modifier
.width(200.dp)
.height(200.dp)
.background(color = Color.Red),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.width(50.dp)
.height(50.dp)
.background(color = Color.Blue)
.align(Alignment.TopStart)
)
Box(
modifier = Modifier
.width(50.dp)
.height(50.dp)
.background(color = Color.Green)
.align(Alignment.TopEnd)
)
Box(
modifier = Modifier
.width(50.dp)
.height(50.dp)
.background(color = Color.Yellow)
.align(Alignment.BottomEnd)
)
}
}
Row
Row 行组件,相当于 原生View 中的 horizontal 的 LinearLayout
@Composable
fun RowDemo() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly
) {
Box(
modifier = Modifier
.width(50.dp)
.height(50.dp)
.background(Color.Red)
)
Box(
modifier = Modifier
.width(60.dp)
.height(60.dp)
.background(Color.Green)
)
Box(
modifier = Modifier
.width(70.dp)
.height(70.dp)
.background(Color.Blue)
)
Box(
modifier = Modifier
.width(80.dp)
.height(80.dp)
.background(Color.Yellow)
)
Box(
modifier = Modifier
.width(90.dp)
.height(90.dp)
.background(Color.Cyan)
)
}
}
Column
Column 行组件,相当于 原生View 中的 vertical 的 LinearLayout
@Composable
fun ColumnDemo() {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(top = 10.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.width(50.dp)
.height(50.dp)
.background(Color.Red)
)
Box(
modifier = Modifier
.width(50.dp)
.height(50.dp)
.background(Color.Green)
)
Box(
modifier = Modifier
.width(50.dp)
.height(50.dp)
.background(Color.Blue)
)
Box(
modifier = Modifier
.width(50.dp)
.height(50.dp)
.background(Color.Green)
)
Box(
modifier = Modifier
.width(50.dp)
.height(50.dp)
.background(Color.Blue)
)
}
}
进阶组件
LazyColumn
LazyColumn 组件,适合加载多数据的列表,相当于原生View中的 RecyclerView
@Composable
fun LazyColumnDemo() {
val context = LocalContext.current
val list = listOf("Android", "iOS", "HTML5", "Linux", "Kotlin")
val onClick: (String) -> Unit = {
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
}
LazyColumn {
// 头部布局
item {
Image(
painter = painterResource(id = R.drawable.zoom),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(150.dp),
contentScale = ContentScale.Crop
)
}
// 中间列表项布局
items(list.size) { index ->
ContactsItem(list[index], onClick)
}
// 尾部布局
item {
Box(
modifier = Modifier
.fillMaxWidth()
.height(100.dp),
contentAlignment = Alignment.Center
) {
Text("加载更多")
}
}
}
}
Spacer
相当于原生 View 中的 space,显示为空白区域
@Preview
@Composable
fun SpacerDemo() {
Column {
Box(
modifier = Modifier
.background(color = Color.Red)
.size(width = 100.dp, height = 100.dp)
)
Spacer(modifier = Modifier.height(20.dp))
Box(
modifier = Modifier
.background(color = Color.Blue)
.size(width = 100.dp, height = 100.dp)
)
}
}
Card
@Composable
fun CardDemo() {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(20.dp),
elevation = 10.dp,
) {
Column {
Image(
modifier = Modifier.height(200.dp),
painter = painterResource(id = R.drawable.bmw),
contentScale = ContentScale.Crop,
contentDescription = null
)
Text(
"New BMW 3",
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(15.dp)
)
}
}
}
AnimatedVisibility
@Composable
fun AnimatedVisibilityDemo() {
val show = remember {
mutableStateOf(true)
}
Card(
modifier = Modifier
.padding(10.dp)
.clickable {
show.value = !show.value
},
shape = RoundedCornerShape(6.dp),
elevation = 5.dp
) {
Column(modifier = Modifier.padding(10.dp)) {
Text(
text = stringResource(R.string.text_content),
style = TextStyle(fontSize = 14.sp)
)
Spacer(modifier = Modifier.height(10.dp))
AnimatedVisibility(visible = show.value) {
Image(
painter = painterResource(id = R.drawable.zoom),
contentDescription = null,
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.Crop
)
}
}
}
}
Dialog
@Preview(showBackground = true)
@Composable
fun AlertDialogDemo() {
val dialogState: MutableState<Boolean> = remember { mutableStateOf(false) }
Button(
onClick = {
dialogState.value = true
},
modifier = Modifier
.width(200.dp)
.wrapContentHeight()
) {
Text(text = "open dialog")
}
if (dialogState.value) {
ShowAlertDialog({
dialogState.value = false
}, {
dialogState.value = false
})
}
}
@Composable
fun ShowAlertDialog(confirm: () -> Unit, dismiss: () -> Unit) {
AlertDialog(
onDismissRequest = dismiss, // Executes when the user tries to dismiss the Dialog by clicking outside or pressing the back button. This is not called when the dismiss button is clicked
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Filled.Notifications,
contentDescription = null
)
Text(text = "我是标题")
}
},
text = {
Text("我是内容")
},
confirmButton = {
TextButton(onClick = confirm) {
Text(text = "确定")
}
},
dismissButton = {
TextButton(onClick = confirm) {
Text("取消")
}
},
properties = DialogProperties(
dismissOnBackPress = false,
dismissOnClickOutside = false,
securePolicy = SecureFlagPolicy.SecureOn
)
)
}
TabRow / ScrollableTabRow
TabRow 相当于原生View中的 TabLayout
TabRow: 包含一行 Tab, 其中的 Tab 均匀分布,每一个 Tab 占用相等的宽度
ScrollableTabRow: 可以滚动的 TabRow
@Composable
fun TabRowDemo() {
val state = remember { mutableStateOf(0) }
val titles = listOf<String>("推荐", "体育新闻", "Android软件工程师")
Column {
TabRow(selectedTabIndex = state.value) {
titles.forEachIndexed { index, value ->
Tab(
text = { Text(value) },
selected = state.value == index,
onClick = {
state.value = index
}
)
}
}
Spacer(modifier = Modifier.height(20.dp))
Text(
modifier = Modifier.align(Alignment.CenterHorizontally),
text = "第${state.value}个Tab, ${titles[state.value]}",
style = TextStyle(fontSize = 20.sp)
)
}
}
@Preview(showBackground = true)
@Composable
fun ScrollableTabRowDemo() {
val state = remember { mutableStateOf(0) }
val titles = listOf<String>("推荐", "Kotlin 入门到精通", "Android软件工程师", "Web前端工程师")
Column {
ScrollableTabRow(
selectedTabIndex = state.value,
modifier = Modifier
.fillMaxWidth(),
edgePadding = 6.dp
) {
titles.forEachIndexed { index, value ->
Tab(
text = {
Text(
value,
fontSize = if (state.value == index) 18.sp else 14.sp
)
},
selected = state.value == index,
onClick = {
state.value = index
}
)
}
}
Spacer(modifier = Modifier.height(20.dp))
Text(
modifier = Modifier.align(Alignment.CenterHorizontally),
text = "第${state.value}个Tab, ${titles[state.value]}",
style = TextStyle(fontSize = 20.sp)
)
}
}
HorizontalPager / VerticalPager
HorizontalPager 组件相当于原生View中的 ViewPager
@Preview(showBackground = true)
@ExperimentalPagerApi
@Composable
fun HorizontalPagerDemo2() {
val pagerState = rememberPagerState(pageCount = 10)
val images = listOf(
R.drawable.zoom,
R.drawable.link_co,
R.drawable.honda,
R.drawable.bmw,
R.drawable.maserati
)
HorizontalPager(state = pagerState) { page ->
Image(
painter = painterResource(id = images[page % images.size]),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(200.dp),
contentScale = ContentScale.Crop
)
}
}
HorizontalPager 与 ScrollableTabRow 结合使用
@Preview
@ExperimentalPagerApi
@Composable
fun HorizontalPagerWithScrollableRow() {
val datas = listOf(
TabItem("马自达", R.drawable.zoom),
TabItem("领克", R.drawable.link_co),
TabItem("本田", R.drawable.honda),
TabItem("宝马", R.drawable.bmw),
TabItem("玛莎拉蒂", R.drawable.maserati)
)
val pagerState = rememberPagerState(pageCount = datas.size)
val coroutineScope = rememberCoroutineScope()
Column {
ScrollableTabRow(
selectedTabIndex = pagerState.currentPage,
edgePadding = 5.dp,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
modifier = Modifier
.pagerTabIndicatorOffset(
pagerState = pagerState,
tabPositions = tabPositions
)
.width(20.dp)
)
}
) {
datas.forEachIndexed { index, item ->
Tab(
selected = index == pagerState.currentPage,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
},
modifier = Modifier
.height(40.dp)
.wrapContentWidth()
) {
Text(item.name)
}
}
}
HorizontalPager(state = pagerState) { page ->
Image(
painter = painterResource(id = datas[page % datas.size].resId),
contentDescription = null,
modifier = Modifier
.height(200.dp)
.fillMaxWidth(),
contentScale = ContentScale.Crop
)
}
}
}
DropdownMenu
DropdownMenu 相当于原生View中的 PopupWIndow
@Preview
@Composable
fun DropdownMenuDemo() {
val expand = remember {
mutableStateOf(false)
}
val context = LocalContext.current
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
) {
IconButton(onClick = { expand.value = true }) {
Icon(Icons.Filled.Add, contentDescription = "")
}
}
DropdownMenu(
expanded = expand.value,
onDismissRequest = { expand.value = false },
modifier = Modifier
.width(100.dp)
.wrapContentHeight()
.shadow(
elevation = 1.dp,
clip = false
),
offset = DpOffset(200.dp, 0.dp)
) {
DropdownMenuItem(onClick = { showToast(context, "分享") }) {
Row {
Icon(Icons.Filled.Share, contentDescription = null)
Spacer(modifier = Modifier.width(6.dp))
Text("分享")
}
}
DropdownMenuItem(onClick = { showToast(context, "收藏") }) {
Row {
Icon(Icons.Filled.Favorite, contentDescription = null)
Spacer(modifier = Modifier.width(6.dp))
Text("收藏")
}
}
DropdownMenuItem(onClick = { showToast(context, "导出") }) {
Row {
Icon(Icons.Filled.ExitToApp, contentDescription = null)
Spacer(modifier = Modifier.width(6.dp))
Text("导出")
}
}
}
}
Slider
@Preview(showBackground = true)
@Composable
fun SliderDemo() {
var slideValue by remember { mutableStateOf(0f) }
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = slideValue.toString())
Slider(
value = slideValue,
onValueChange = { slideValue = it },
valueRange = 0f..5f,
steps = 4 // 4步阶段,共 5 段
)
}
}
ModalBottomSheetLayout
@Preview(showBackground = true)
@ExperimentalMaterialApi
@Composable
fun ModalBottomSheetLayoutDemo() {
val sheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
val coroutineScope = rememberCoroutineScope()
ModalBottomSheetLayout(sheetState = sheetState,
sheetShape = RoundedCornerShape(10.dp),
sheetContent = {
Column(
modifier = Modifier
.height(600.dp)
.fillMaxWidth()
) {
Box(
modifier = Modifier
.height(50.dp)
.fillMaxWidth()
.padding(horizontal = 15.dp)
) {
Text(
text = "评论",
fontSize = 18.sp,
modifier = Modifier.align(Alignment.Center)
)
Icon(
Icons.Filled.Close,
contentDescription = "close",
modifier = Modifier
.align(Alignment.CenterEnd)
.clickable {
coroutineScope.launch {
sheetState.hide()
}
}
)
}
LazyColumn {
items(100) { index ->
ListItem {
Text("sheet content $index")
}
}
}
}
}) {
Column {
Text("title")
Spacer(modifier = Modifier.height(20.dp))
Button(onClick = {
coroutineScope.launch {
sheetState.show()
}
}) {
Text(text = "show")
}
}
}
}
Canvas
Canvas 组件常用于自定组件。
@Preview(showBackground = true)
@Composable
fun CanvasDemo1() {
Canvas(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
) {
val canvasWidth = size.width
val canvasHeight = size.height
drawLine(
brush = Brush.linearGradient(colors = listOf(Color.Red, Color.Blue, Color.Green)),
start = Offset.Zero,
end = Offset(canvasWidth, canvasHeight),
strokeWidth = 10f
)
}
}
自定义 Layout
自定义横向瀑布流
@Composable
fun HorizontalStaggeredGrid(
modifier: Modifier = Modifier,
row: Int,
horizontalSpace: Dp = 15.dp,
verticalSpace: Dp = 12.dp,
content: @Composable () -> Unit
) {
Layout(modifier = modifier, content = content) { measurables, constraints ->
// 每行的宽度,初始都是 0
val rowWidths = IntArray(row) { 0 }
// 每行的高度,初始都是 0
val rowHeights = IntArray(row) { 0 }
// 遍历每个孩子,List<Measurable> 转换成 List<Placeable>
val placeableList = measurables.mapIndexed { index, measurable ->
// 测量每个孩子组件
val placeable = measurable.measure(constraints)
// 当前孩子所在行的索引
val rowIndex = index % row
// 当前行宽度累加
rowWidths[rowIndex] += placeable.width + horizontalSpace.roundToPx()
// 当前行高度取 当前高度和此孩子高度的最大值
rowHeights[rowIndex] = max(rowHeights[rowIndex], placeable.height)
// lambda 返回值
placeable
}
// 此自定义组件的宽度
val width = rowWidths.maxOrNull()
?.coerceIn(constraints.minWidth.rangeTo(constraints.maxWidth)) // 限制在minWidth和maxWidth之间
?: constraints.minWidth // 如果rowWidths 为null,则设置为minWidth
// 此自定义组件的高度
val height = rowHeights.sumOf { it + verticalSpace.roundToPx() }
.minus(verticalSpace.roundToPx()) // 减去最后一行的 verticalSpace
.coerceIn(constraints.minHeight.rangeTo(constraints.maxHeight))
// 记录每行顶部所在位置
val rowY = IntArray(row) { 0 }
// 第0行为 0, 从第一行开始,每行的顶部位置为 上一行顶部位置 + 上一行的高度
for (rowIndex in 1 until row) {
rowY[rowIndex] = rowY[rowIndex - 1] + rowHeights[rowIndex - 1] + verticalSpace.roundToPx()
}
layout(width, height) {
// 记录每行当前横向位置x
val rowX = IntArray(row) { 0 }
placeableList.forEachIndexed { index, placeable ->
// 所在行索引
val rowIndex = index % row
placeable.placeRelative(x = rowX[rowIndex], y = rowY[rowIndex])
rowX[rowIndex] += placeable.width + horizontalSpace.roundToPx()
}
}
}
}
调用示例
data class Item(val text: String, val height: Dp)
@Composable
fun HorizontalStaggeredGridSample() {
val list = listOf(
Item("TextView", 10.dp),
Item("Button", 20.dp),
Item("GridView", 30.dp),
Item("ListView", 60.dp),
Item("JetpackCompose", 20.dp),
Item("AndroidStudio", 60.dp),
Item("Compose-Desktop", 20.dp)
)
HorizontalStaggeredGrid(
modifier = Modifier
.wrapContentHeight()
.horizontalScroll(rememberScrollState())
.background(color = Color(0xffcccccc)),
row = 5
) {
for (index in 0..50) {
val item = list[index % list.size]
val color = when (index % list.size) {
0 -> Color(0xffa23f12)
1 -> Color(0xffff3456)
2 -> Color(0xff12ff12)
3 -> Color(0xff1a34ff)
4 -> Color(0xffefac1f)
5 -> Color(0xffa2040f)
else -> Color(0xfff9af6f)
}
Text(
modifier = Modifier
.background(color = color)
.height(item.height),
text = item.text
)
}
}
}
常见问题
Clickable 禁用 Ripple 波纹
inline fun Modifier.noRippleClickable(crossinline onClick: () -> Unit): Modifier = composed {
clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }) {
onClick()
}
}
TextField 修改 height 导致显示不全
自定义 BasicTextField
@Composable
fun CustomTextField(
modifier: Modifier = Modifier,
leadingIcon: (@Composable () -> Unit)? = null,
trailingIcon: (@Composable () -> Unit)? = null,
placeholderText: String = "",
fontSize: TextUnit = MaterialTheme.typography.body2.fontSize
) {
var text by rememberSaveable { mutableStateOf("") }
BasicTextField(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 6.dp),
value = text,
onValueChange = {
text = it
},
singleLine = true,
cursorBrush = SolidColor(MaterialTheme.colors.primary),
textStyle = LocalTextStyle.current.copy(
color = MaterialTheme.colors.onSurface,
fontSize = fontSize
),
decorationBox = { innerTextField ->
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically
) {
if (leadingIcon != null) leadingIcon()
Spacer(modifier = Modifier.width(5.dp))
Box(
modifier = Modifier
.weight(1f)
.fillMaxHeight(),
contentAlignment = Alignment.CenterStart
) {
if (text.isEmpty())
Text(
placeholderText,
style = LocalTextStyle.current.copy(
color = MaterialTheme.colors.onSurface.copy(alpha = 0.3f),
fontSize = fontSize
)
)
innerTextField()
}
if (trailingIcon != null) trailingIcon()
}
}
)
}
调用用例:
CustomTextField(
leadingIcon = {
Icon(
imageVector = Icons.Filled.Search,
contentDescription = null,
modifier = Modifier.size(20.dp),
tint = LocalContentColor.current.copy(alpha = 0.3f)
)
},
trailingIcon = null,
modifier = Modifier
.clip(RoundedCornerShape(5.dp))
.background(
color = Color.Red,
)
.width(200.dp)
.height(40.dp),
fontSize = 14.sp,
placeholderText = "Search"
)