0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Jetpack ComposeのTextFieldの余白をなくす方法 〜 Material3の内部実装から原因を調査してみた

0
Posted at

背景

Jetpack Composeでコンパクトな検索バーを作ろうとしたところ、TextFieldに謎の余白があり、高さを小さくできませんでした。

そこで、Material3の内部実装を確認しながら原因を追っていきます。

動作確認環境

  • Kotlin 2.4.20
  • Jetpack Compose BOM 2026.09.00
  • Material3(Compose BOM経由)

実現したいこと

検索バーなどで利用できる、縦方向をできるだけコンパクトにしたTextFieldを表示したいです。

標準のTextFieldと比べると、高さを抑えつつMaterial3らしい見た目は維持したい、というケースを想定しています。

上がMaterial3標準のTextField、
下が今回作成するコンパクトなTextFieldです。

0 作りたいもの.png

Material3のTextFieldは、Material Designに基づき、アクセシビリティや可読性を考慮した余白がデフォルトで設定されています。

そのため、通常のフォーム入力欄では、Material3のデフォルト設定をそのまま利用することが一般的です。

一方で、検索バーのようにコンパクトなUIを作りたいケースでは、この余白を調整したいことがあります。

今回は、この余白がどこで設定されているのかを確認し、コンパクトな入力欄を実現する方法を探っていきます。

普通にTextFieldを配置してみる

var text by remember { mutableStateOf("初期値") }

TextField(
    modifier = Modifier.fillMaxWidth(),
    value = text,
    onValueChange = { text = it },
)

1 普通に作ってみた.png

上下に大きな余白があります。

Modifier.padding(0.dp)を指定してみる

TextField(
    modifier = Modifier
        .fillMaxWidth()
        .padding(0.dp),
    value = text,
    onValueChange = { text = it },
)

2 paddingを0dpに設定してみた.png

変わりません。

Modifier.padding()はTextFieldの外側の余白を変更するものなので、内部の余白には影響しません。

高さを小さくしてみる

TextField(
    modifier = Modifier
        .fillMaxWidth()
        .height(50.dp),
    value = text,
    onValueChange = { text = it },
)

3 高さを設定してみた.png

文字が見切れてしまいました。

高さだけ変更しても内部レイアウトは変わらないようです。

原因を調査する

なぜModifier.padding()では変更できないのか気になったので、
Material3のTextFieldの実装を追ってみます。

今回確認したMaterial3のソースコードはこちらです。

BasicTextField(
    ...
    decorationBox = { innerTextField ->
        TextFieldDefaults.DecorationBox(...)
    }
)

DecorationBoxを確認すると、

contentPadding =
    if (label == null) {
        contentPaddingWithoutLabel()
    } else {
        contentPaddingWithLabel()
    }

となっており、デフォルトでcontentPaddingが設定されています。

次に、実際に何dpの余白が設定されているのか確認してみます。

contentPaddingWithoutLabel()とcontentPaddingWithLabel()の実装を見ると、以下のようになっていました。

fun contentPaddingWithLabel(
    start: Dp = TextFieldPadding,
    end: Dp = TextFieldPadding,
    top: Dp = TextFieldWithLabelVerticalPadding,
    bottom: Dp = TextFieldWithLabelVerticalPadding
): PaddingValues = PaddingValues(start, top, end, bottom)

fun contentPaddingWithoutLabel(
    start: Dp = TextFieldPadding,
    top: Dp = TextFieldPadding,
    end: Dp = TextFieldPadding,
    bottom: Dp = TextFieldPadding
): PaddingValues = PaddingValues(start, top, end, bottom)

TextFieldPaddingとTextFieldWithLabelVerticalPaddingを確認すると、

internal val TextFieldPadding = 16.dp
internal val TextFieldWithLabelVerticalPadding = 8.dp

となっていました。

今回はラベルを指定していないため、
contentPaddingWithoutLabel()が使用され、
上下左右すべて16.dpの余白が設定されます。

最終的にはこの値がCommonDecorationBoxへ渡されます。

CommonDecorationBox(
    ...
    contentPadding = contentPadding
)

DecorationBoxで設定されたcontentPaddingは、そのままCommonDecorationBoxへ渡され、最終的なレイアウトに反映されます。

つまり、TextFieldは次のような構造になっています。

TextField
 ↓
BasicTextField
 ↓
DecorationBox
 ↓
CommonDecorationBox
 ↓
contentPadding

そのため、Modifier.padding()ではなく、
DecorationBoxのcontentPaddingを書き換える必要があります。

ただし、BasicTextFieldだけを利用すると、Material3の見た目は失われてしまいます。

例えば、以下のような装飾や補助的なUIはBasicTextField単体では提供されません。

  • プレースホルダー
  • ラベル
  • エラー表示
  • 下線(インジケーター)
  • Leading / Trailing Icon

そのため今回は、入力部分はBasicTextFieldを利用しつつ、Material3の見た目はTextFieldDefaults.DecorationBoxを利用し、contentPaddingだけを変更します。

解決方法

原因が分かったので、BasicTextFieldと
TextFieldDefaults.DecorationBoxを利用し、
contentPaddingを指定して、内部余白を変更します。

val interactionSource = remember { MutableInteractionSource() }

BasicTextField(
    value = text,
    onValueChange = { text = it },
    modifier = Modifier.fillMaxWidth(),
    interactionSource = interactionSource,
    decorationBox = { innerTextField ->
        TextFieldDefaults.DecorationBox(
            value = text,
            innerTextField = innerTextField,
            enabled = true,
            singleLine = true,
            visualTransformation = VisualTransformation.None,
            interactionSource = interactionSource,
            // 内部余白だけを変更
            contentPadding = PaddingValues(0.dp)
        )
    }
)

4 完成.png

余白をなくしたTextFieldを表示できました。

この方法を使うケース

contentPadding = PaddingValues(0.dp)を指定することでコンパクトな入力欄を実現できますが、Material3が想定する余白は失われます。

そのため、通常のフォーム入力欄ではデフォルト値を利用し、検索バーなどのコンパクトさが求められるUIで利用するケースが考えられます。

  • 検索バー
  • ツールバー内の入力欄
  • 一覧画面のフィルター
  • コンパクトなチャット入力欄

まとめ

Modifier.padding()では内部余白は変更できません。

また、height()だけ変更すると文字が見切れる場合があります。

原因はTextFieldDefaults.DecorationBoxでデフォルト設定されているcontentPadding(ラベルなしの場合は16.dp)でした。

コンパクトな入力欄を作りたい場合は、

  • BasicTextField
  • TextFieldDefaults.DecorationBox
  • contentPadding = PaddingValues(0.dp)

を指定することで、内部余白を調整できます。

今回のように、Composeでは公開APIだけを見ても原因が分からないことがあります。

そのような場合はAndroidXのソースコードを追うことで、「どこで値が決まっているのか」「どのレイヤーでレイアウトされているのか」が分かり、今回のような問題も解決しやすくなります。

Composeの内部実装を読む習慣を付けると、同様の問題に遭遇した際の調査力も身につくと思います。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?