【Android】Composeの罠：デザインの指定が思い通りにならない

## Paddingが思い通りにならない
今回はJetpack Composeの罠について。
UIを作成するときにComposeを使って書いているのですが、そのときにpaddingの設定がうまくいかず困ったケースを挙げてみます。

## TextFeid
ユーザーに文字を入力させたいとき、手っ取り早いのはTextFeildを使うことだと思います。

```kotlin
@Composable
private fun EditTextField() {
    var text by remember { mutableStateOf("") }
    
    TextField(
        value = text,
        onValueChange = { newText -> text = newText },
        placeholder = { Text(text = "ここに入力してください", color = Color.Gray) },
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    )
}
```

|||
|:---:|:---:|
|<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3887743/f91da0e7-6d66-52e9-a679-f3f813632a9f.png" width = "300" />|<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3887743/ba9afb51-dc31-0959-4c99-2c567cc494ee.png" width = "300" />|

しかし、TextFieldを使うとTextFeildの外枠と実際に入力された文字が入るエリアのpaddingが固定されてしまっていて、TextFieldの外側から操作することができません。

例えばデザイナーさんから、TextFeildの外枠の位置から文字が入るようにしてほしいという指示があった場合は、このままでは対応できません。

そんな時は独自で用意するしかありません。
[こちらの記事](https://qiita.com/kilalabu/items/b193a41b69b5747d1a0f)でも説明されているように、TextFieldの実装の中身を踏襲しつつ、paddingを外側から調整できるようにします。
ついでにデフォルトのフォントサイズなども扱いやすいように決めてしまいましょう。

```kotlin
// 内部のPaddingを調整できるようにcontentPaddingを引数に用意
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomTextField(
    modifier: Modifier = Modifier,
    value: String,
    onValueChange: (String) -> Unit,
    enabled: Boolean = true,
    readOnly: Boolean = false,
    textStyle: TextStyle = TextStyle(
        color = Color.Black,
        fontSize = 14.fontDp
    ),
    cursorColor: Brush = SolidColor(MaterialTheme.colors.secondaryVariant),
    label: @Composable (() -> Unit)? = null,
    placeholder: @Composable (() -> Unit)? = null,
    leadingIcon: @Composable (() -> Unit)? = null,
    trailingIcon: @Composable (() -> Unit)? = null,
    prefix: @Composable (() -> Unit)? = null,
    suffix: @Composable (() -> Unit)? = null,
    supportingText: @Composable (() -> Unit)? = null,
    isError: Boolean = false,
    visualTransformation: VisualTransformation = VisualTransformation.None,
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
    keyboardActions: KeyboardActions = KeyboardActions.Default,
    singleLine: Boolean = true,
    maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
    minLines: Int = 1,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    contentPadding: PaddingValues = TextFieldDefaults.contentPaddingWithLabel(
        0.dp,
        0.dp,
        0.dp,
        0.dp
    ),
    shape: Shape = TextFieldDefaults.shape,
    colors: TextFieldColors = TextFieldDefaults.colors(
        unfocusedContainerColor = Color.Transparent,
        focusedContainerColor = Color.Transparent,
        focusedIndicatorColor = MaterialTheme.colors.secondaryVariant,
        unfocusedIndicatorColor = MaterialTheme.colors.secondaryVariant,
    ),
) {
    BasicTextField(
        value = value,
        modifier = modifier
            .fillMaxWidth()
            .defaultMinSize(minHeight = 0.dp),
        onValueChange = { onValueChange(it) },
        enabled = enabled,
        readOnly = readOnly,
        textStyle = textStyle,
        cursorBrush = cursorColor,
        visualTransformation = visualTransformation,
        keyboardOptions = keyboardOptions,
        keyboardActions = keyboardActions,
        interactionSource = interactionSource,
        singleLine = singleLine,
        maxLines = maxLines,
        minLines = minLines,
        decorationBox = @Composable { innerTextField ->
            TextFieldDefaults.DecorationBox(
                value = value,
                visualTransformation = visualTransformation,
                innerTextField = innerTextField,
                placeholder = placeholder,
                label = label,
                leadingIcon = leadingIcon,
                trailingIcon = trailingIcon,
                prefix = prefix,
                suffix = suffix,
                supportingText = supportingText,
                shape = shape,
                singleLine = singleLine,
                enabled = enabled,
                isError = isError,
                interactionSource = interactionSource,
                colors = colors,
                contentPadding = contentPadding, // 内部のpadding
            )
        }
    )
}
```

```kotlin
@Composable
private fun EditTextField() {
    var text by remember { mutableStateOf("") }
    
    CustomEditTextField(
        value = text,
        onValueChange = { newText -> text = newText },
        placeholder = { Text(text = "ここに入力してください", color = Color.Gray) },
        contentPadding = PaddingValues(0.dp,10.dp,0.dp,10.dp),
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
    )
}
```

|||
|:---:|:---:|
|<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3887743/db622518-3484-7ae7-517c-6c3f86171044.png" width = "300" />|<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3887743/4f9de404-aaf0-eb28-2ad4-c8021d6e2141.png" width = "300" />|


これで内部のpaddingが調整できるようになりました。
内部paddingのtop, bottom, start, end を個別で指定することもできるようになって使い勝手が上がったと思います（元々この形で実装していてほしい...）。