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 ~~~

  1. There’s an extra underscore. That’s easy to get rid of

  2. Height is wrong

  3. 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 theTextFieldInternal 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!! 😪

rightBasicTextFieldmodified

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.

BasicTextFieldThe 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)

toBasicTextFieldIncrease 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.

BasicTextFieldYou 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