For a good reading experience, you need to have a basic understanding of Compose or Flutter (too similar) before reading this article
Compose has been out for almost a month. But the actual use of the project should be very few. With a bit of previous experience with Flutter, I recently decided to test the waters and use it in my project. Next, we will most likely update some of the problems we encounter with Compose
Start with a small goal
A search function like the one below should be a common requirement in daily development, so let’s implement it
Simple use of TextField
TextField provides a lot of parameter usage, we first refer to the basic usage of Google development documents to try to complete the following UI style.
For those of you who know a little bit about this, it’s easy to implement: Row + icon + TextField
Let me post some simple code. Look at the TextField section
var text by remember { mutableStateOf("") }
Row(
Modifier
.fillMaxWidth()
.padding(end = 20.dp, start = 10.(dp). The background Color. White), verticalAlignment = Alignment. CenterVertically) {... ellipsis... TextField (value = text, onValueChange = { text = it onValueChange.invoke(it) }, singleLine =true,
placeholder = { Text(value, fontSize = 16f.sp, color = colorResource(id = R.color.color_BFBFBF)) },
leadingIcon = { BuildImageIcon(R.drawable.icon_search_black, 24.dp) },
trailingIcon = {
BuildImageIcon(R.drawable.icon_edit_clean, 24.dp) {
text = ""
}
},
textStyle = TextStyle(color = colorResource(id = R.color.color_262626), fontSize = 16.sp),
modifier = Modifier
.fillMaxSize()
.padding(start = 20.dp)
.background(colorResource(id = R.color.colorF5F5F5))
.defaultMinSize(minHeight = 40.dp),
shape = RoundedCornerShape(8.dp),
colors = TextFieldDefaults.textFieldColors(
backgroundColor = colorResource(id = R.color.colorF5F5F5),
),
)
}
}
Copy the code
With my years of experience, I think I’m ready to start paddling. 🎉
However… After running, it looks like this ~~~
-
There’s an extra underscore. That’s easy to get rid of
-
Height is wrong
-
Text display is incomplete.
So let’s fix these problems.
TextField details modified
Start with the simplest underline:
Just change the colors of the TextField:
colors = TextFieldDefaults.textFieldColors(
backgroundColor = colorResource(id = R.color.colorF5F5F5),
+ focusedIndicatorColor = Color.Transparent,
+ unfocusedIndicatorColor = Color.Transparent
),
Copy the code
Wrong height:
This direct modification Modifier is good.
Modifier.fillMaxSize().height(40.dp).padding(start = 20.dp),
Copy the code
Next comes the crucial missing text:
Modify theTextField
Internal margin
TextField
I tried a lot of properties, but I couldn’t just cancel the TextField built-in padding
Look at the source code. TextField is based on BasicTextField, with the default height written internally:
How about changing the default height? I tried, but it didn’t work
…
Unfortunately, I tried a lot of things, Google and StackOverflow searched for hours, but nothing worked.
TextField can only do this, complete the text!! 😪
rightBasicTextField
modified
TextField can see is on the androidx.com pose. The material under the package of a class that can be seen as a conform to the material input box Design, want to let it completely accords with the Design of domestic can be a bit difficult. Fortunately, we see BasicTextField, which we can implement directly using BasicTextField.
BasicTextField
The use of
BasicTextField is not very different from TextField:
BasicTextField does not provide leading and trailing attributes. We use Row + icon + BasicTextField + icon to implement our own
Post the code:
BuildImageIcon is a self-encapsulated Image of a local drawable
Be sure to give Row verticalAlignment instead of textStyle. Because it can only control the position of text horizontally
var text by remember { mutableStateOf("") }
Row(
Modifier
.fillMaxWidth()
.background(
colorResource(id = R.color.colorF5F5F5),
shape = RoundedCornerShape(8.dp)
)
.height(40.dp)
.padding(start = 10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
BuildImageIcon(R.drawable.icon_search_black, 24.dp)
BasicTextField(
value = text,
onValueChange = {
text = it
onValueChange.invoke(it)
},
singleLine = true,
modifier = Modifier
.weight(1f)
.padding(start = 10.dp),
textStyle = TextStyle(
color = colorResource(id = R.color.color_262626),
fontSize = 16.sp, ), keyboardActions = KeyboardActions(onSearch = { onSearch? .invoke(text) }), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search) ) BuildImageIcon(R.drawable.icon_edit_clean,24.dp) {
text = ""}}Copy the code
After running, the effect is very close to the design:
There’s no problem typing the text to test it:
Think this is the end of it? No, no, there’s one more obvious problem, the input box doesn’t hint (or placeholder)
toBasicTextField
Increase the placeholder
This problem is easy to solve. Take a quick look at the BasicTextField source code. There is a decorationBox property, which is clearly stated in the comment:
Composable lambda that allows to add decorations around text field, suc as icon, placeholder, helper messages or similar, and automatically increase the hit target area of the text field. To allow you to control the placement of the inner text field relative to your decorations, the text field implementation will pass in a framework-controlled composable parameter “innerTextField” to the decorationBox lambda you provide. You must call innerTextField exactly once.
Basically, it allows you to add decorations around text fields, such as ICONS, placeholders, help messages, or the like, and automatically increase the target area of the text field
And TextField’s functionality is also implemented through this
Here’s the code for the decorationBox section:
decorationBox = { innerTextField ->
if (text.isEmpty()) {
Box(
modifier = Modifier
.padding(start = 5.dp)
.fillMaxSize(), contentAlignment = Alignment.CenterStart
) {
Text(
"Here's the placeholder.", fontSize = 16f.sp,
modifier = Modifier
.fillMaxWidth(),
style = TextStyle(
color = colorResource(id = R.color.color_BFBFBF),
fontSize = 16.sp,
),
)
}
} else
innerTextField()
},
Copy the code
The innerTextField is the text input part of the BasicTextField itself, so let’s see if there’s any text input based on the text above, and if there isn’t, we’ll show our placeholder text.
Run to see the effect:
It’s almost the same as the renderings. It’s perfect.
This is really the time to call it a day. 😚
But !!!! The cursor is not displayed after the input box is clicked. The experience is really bad.
If there are more than one input box, the user doesn’t know which one to click on.
And now we’ve got rid of the placeholder that we’ve set, and then we hit the placeholder and it’s fine. But placeholder removed with the design map is not consistent, certainly can not be removed.
This question really bothered me for a long time.
BasicTextField
You can display the cursor with the placeholder
This problem I also did not find a good way, if which big guy has a way to hope that can freely give advice.
After half a day of fruitless speculation, I finally resorted to the ultimate solution:
Use Box in the decorationBox to bring the two controls together
The effect after running
After this treatment, it does meet the requirements, but I feel that the writing is not very elegant. Wish the comments section had a more elegant way of writing
Finally, send the packaged BasicTextField, which is more suitable for actual project use:
@Composable
fun CustomTextField(
modifier: Modifier = Modifier,
hint: String? = null,
showCleanIcon: Boolean = false,
onTextChange: String. () - >Unit = {},
leadingIcon: @Composable(() - >Unit)? = null,
trailingIcon: @Composable(() - >Unit)? = null,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: String. () - >Unit = {},
textFieldStyle: TextStyle = defaultTextStyle,
hintTextStyle: TextStyle = defaultHintTextStyle,
) {
var text by remember { mutableStateOf("") } Row( modifier, verticalAlignment = Alignment.CenterVertically, ) { leadingIcon? .invoke() BasicTextField( value = text, onValueChange = { text = it onTextChange.invoke(it) }, cursorBrush = SolidColor(colorResource(id = R.color.color_currency)), singleLine =true,
modifier = Modifier
.weight(1f)
.padding(start = 10.dp),
textStyle = textFieldStyle,
decorationBox = { innerTextField ->
if(text.isBlank() && hint.isNotNullEmpty()) Box( modifier = Modifier .fillMaxHeight(), contentAlignment = Alignment.CenterStart ) { innerTextField() CustomText(hint ? :"".16f.sp, colorResource(id = R.color.color_BFBFBF)) Text( hint ? :"",
modifier = Modifier
.fillMaxWidth(),
style = hintTextStyle,
)
} elseinnerTextField() }, keyboardActions = KeyboardActions { keyboardActions(text) }, keyboardOptions = keyboardOptions ) trailingIcon? .invoke()if (showCleanIcon)
ImageIcon(R.drawable.icon_edit_clean, 24.dp) {
text = ""}}}Copy the code