개발 공부/안드로이드

[Compose] 컴포즈 공부하기 9 - TextField

yong_DD 2023. 9. 20. 16:22

xml에 EditText가 있다면 compose에는 TextField가 있다!

 

아래와 같이 간단하게 사용할 수 있다.

var text by remember { mutableStateOf("안녕하세요!") }
TextField(value = text, onValueChange = { text = it})

 

 

그렇다면 TextField에는 어떤 값들이 있을지 자세히 알아보자!

@Composable
fun TextField(
    value: String,
    onValueChange: (String) -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    readOnly: Boolean = false,
    textStyle: TextStyle = LocalTextStyle.current,
    label: @Composable (() -> Unit)? = null,
    placeholder: @Composable (() -> Unit)? = null,
    leadingIcon: @Composable (() -> Unit)? = null,
    trailingIcon: @Composable (() -> Unit)? = null,
    isError: Boolean = false,
    visualTransformation: VisualTransformation = VisualTransformation.None,
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
    keyboardActions: KeyboardActions = KeyboardActions(),
    singleLine: Boolean = false,
    maxLines: Int = Int.MAX_VALUE,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    shape: Shape =
        MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
    colors: TextFieldColors = TextFieldDefaults.textFieldColors()
)

 

1. value, onValueChange

var text by remember { mutableStateOf("안녕하세요!") }
TextField(value = text, onValueChange = { text = it})

상단의 코드를 보면 알 수 있듯이 작성한 값과 변화한 값이다.

onValueChange는 String으로 받는다.

 

onValueChanged에서 text를 바꾸고 text이 value를 바꾸게된다.

그렇다면 onValueChange에서 text를 바꾸지 않는다면??

 

text가 변동이 안되기 때문에 아무리 입력해도 입력이 되거나 삭제가 되지 않는다😅

value가 값을 계속 유지하고 있기때문이다!

 

2. enabled, readOnly

enabled는 활성화 가능한 상태, readOnly는 편집 가능한 상태를 의미한다.

default 값은 당연히 enabled는 true (활성화 가능), readOnly는 false(편집 가능) 이다.

 

그렇다면 둘의 차이가 뭘까?

enabled가 false가 되면 선택할 수도 focus할 수도 없게 된다.

터치했을 때 아무런 반응이 없게된다는 것이다.

그에 반해 readOnly가 true가 되면 (이미 value가 채워진 상태였을 때) text를 편집할 수는 없지만, focus해서 해당 글자를 복사할 수 있는 상태가 된다. (+ 키보드가 올라오진 않는다)

 

var text by remember { mutableStateOf("안녕하세요!") }
Column(modifier = Modifier.padding(16.dp)) {
    TextField(value = text, onValueChange = {text = it}, readOnly = false)
    TextField(value = text, onValueChange = {text = it}, readOnly = true)
}

위의 코드를 실행하면 아래와 같이 동작한다.

 

3. TextStyle

@Immutable
class TextStyle(
    val color: Color = Color.Unspecified,
    val fontSize: TextUnit = TextUnit.Unspecified,
    val fontWeight: FontWeight? = null,
    val fontStyle: FontStyle? = null,
    val fontSynthesis: FontSynthesis? = null,
    val fontFamily: FontFamily? = null,
    val fontFeatureSettings: String? = null,
    val letterSpacing: TextUnit = TextUnit.Unspecified,
    val baselineShift: BaselineShift? = null,
    val textGeometricTransform: TextGeometricTransform? = null,
    val localeList: LocaleList? = null,
    val background: Color = Color.Unspecified,
    val textDecoration: TextDecoration? = null,
    val shadow: Shadow? = null,
    val textAlign: TextAlign? = null,
    val textDirection: TextDirection? = null,
    val lineHeight: TextUnit = TextUnit.Unspecified,
    val textIndent: TextIndent? = null
)

TextStyle은 위와 같이 구성되어 있다.

text에 관련된 Style을 자유롭게 넣을 수 있다.

       TextField(
            value = text, 
            onValueChange = {text = it}, 
            textStyle = TextStyle(color = Color.Red, fontWeight = FontWeight.Bold)
        )

 

4. label, placeholder, leadingIcon, trailingIcon

예시를 보면 더 쉽게 이해할 수 있다.

var text by remember { mutableStateOf("") }
TextField(
    value = text,
    onValueChange = {text = it},
    label = { Text("this is label", color = Color.Blue) },
    placeholder = {
        Text( text = "placeholder", color = Color.Green)
    },
    leadingIcon = {
        Icon(
            imageVector = Icons.Filled.Add,
            contentDescription = "add"
        )
    },
    trailingIcon = {
        Icon(
            imageVector = Icons.Filled.Send,
            contentDescription = "send"
        )
    }

)

label과 placeHolder는 이미 알고 있는 경우가 많을 것이다.

leadingIcon은 왼쪽 시작지점, trailingIcon은 오른쪽 끝나는 지점에 위치하는 Icon이다.

label, placeholder, leadingIcon, trailingIcon 전체 컴포저블로 넣어주면 된다.

각각의 색상은 한 번에 지정해 줄 수도 있는데 9번을 참고해주면 된다😁

 

+ brush  사용하기

android developer의 예시를 보면 brush를 사용하여 무지개로도 보여주는 예시가 있다.

현재는 실험적인 api이고 material 3를 사용하면 사용해 볼 수 있다.

// build.gradle
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
val colors = listOf(Color.Red, Color.Yellow, Color.Green, Color.Blue)
val brush = remember {
    Brush.linearGradient(
        colors = colors
    )
}
TextField(
    value = text,
    onValueChange = {text = it},
    textStyle = TextStyle(brush = brush)
)

기본색으로 이렇게 자연스럽게 잘나오게 된다!

색을 잘 넣으면 굉장히 이쁠 것 같다😁

(이거 테스트하려고 material3로 바꿨다가 터지는 오류 덕에 조큼 고생했다...🤣)

 

더 관심이 있다면 여기를 클릭

 

 

5. isError

TextField(
    value = text,
    onValueChange = {text = it},
    label = { Text("this is label", color = Color.Blue) },
    placeholder = {
        Text( text = "placeholder", color = Color.Green)
    },
    leadingIcon = {
        Icon(
            imageVector = Icons.Filled.Add,
            contentDescription = "add"
        )
    },
    trailingIcon = {
        Icon(
            imageVector = Icons.Filled.Send,
            contentDescription = "send"
        )
    },
    isError = true
)

TextField의 현재 값이 오류인지 여부를 나타내는 것으로 예를 들어 password 확인 textField인데 기존에 작성한 것과 일치하지 않았다던지 할 때 편리하게 사용할 수 있다.

 

isError가 true일 때는 error 컬러를 보여주게 된다.

이 컬러값은 9번을 참고해주면 된다!

6.  visualTransformation

보여지는 값을 어떻게 보여줄지에 대한 것으로 예를 들어 비밀번호 입력시 **** 처럼 나오게 할 수 있다.

PasswordVisualTransforamtion을 제공하고 있어서 이것을 적용해보면,

    var text by remember { mutableStateOf("비밀번호") }
    TextField(
        value = text,
        onValueChange = {text = it},
        visualTransformation = PasswordVisualTransformation()
    )

위와같이 보여지게 된다.

(VisualTransformation 사용 예시는 여기를 참고)

7. keyboardOptions, keyboardActions

@Immutable
class KeyboardOptions constructor(
    val capitalization: KeyboardCapitalization = KeyboardCapitalization.None,
    val autoCorrect: Boolean = true, // 자동 수정 기능
    val keyboardType: KeyboardType = KeyboardType.Text,
    val imeAction: ImeAction = ImeAction.Default
)

inline class KeyboardCapitalization internal constructor(internal val value: Int) {
   //...
    companion object {
        val None = KeyboardCapitalization(0)
        val Characters = KeyboardCapitalization(1)
        val Words = KeyboardCapitalization(2)
        val Sentences = KeyboardCapitalization(3)
    }
}

inline class KeyboardType internal constructor(@Suppress("unused") private val value: Int) {
	//...
    companion object {
        val Text: KeyboardType = KeyboardType(1)
        val Ascii: KeyboardType = KeyboardType(2)
        val Number: KeyboardType = KeyboardType(3)
        val Phone: KeyboardType = KeyboardType(4)
        val Uri: KeyboardType = KeyboardType(5)
        val Email: KeyboardType = KeyboardType(6)
        val Password: KeyboardType = KeyboardType(7)
        val NumberPassword: KeyboardType = KeyboardType(8)
    }
}

inline class ImeAction internal constructor(@Suppress("unused") private val value: Int) {
	//...
    companion object {
        val Default: ImeAction = ImeAction(1)
        val None: ImeAction = ImeAction(0)
        val Go: ImeAction = ImeAction(2)
        val Search: ImeAction = ImeAction(3)
        val Send: ImeAction = ImeAction(4)
        val Previous: ImeAction = ImeAction(5)
        val Next: ImeAction = ImeAction(6)
        val Done: ImeAction = ImeAction(7)
    }
}

우선 keyboardOption은 위와 같이 구성되어 있다.

대문자를 어떤 단위로 해줄 것인지(KeyboardCapitalization), 키보드 타입은 어떤 것인지(KeyboardType), 다음 동작 버튼은 어떤 것인지(ImeAction) 등을 지정할 수 있다.

xml에 있는 내용과 거의 유사하기 때문에 쉽게 이해할 수 있을 것이라고 본다!

 

class KeyboardActions(
    val onDone: (KeyboardActionScope.() -> Unit)? = null,
    val onGo: (KeyboardActionScope.() -> Unit)? = null,
    val onNext: (KeyboardActionScope.() -> Unit)? = null,
    val onPrevious: (KeyboardActionScope.() -> Unit)? = null,
    val onSearch: (KeyboardActionScope.() -> Unit)? = null,
    val onSend: (KeyboardActionScope.() -> Unit)? = null
) {
    companion object {
        val Default: KeyboardActions = KeyboardActions()
    }
}

키보드 액션은 ImeAction으로 지정된 버튼을 클릭했을 때 어떻게 동작할지에 대해 처리해주는 것이다.

 

keyboardOptions 과 keyboardActions를 사용한 예시는 아래와 같다.

var text by remember { mutableStateOf("") }
val context = LocalContext.current
Column(modifier = Modifier.padding(16.dp)) {
    TextField(
        value = text,
        onValueChange = {text = it},
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Go),
        keyboardActions = KeyboardActions(onGo = {
            Toast.makeText(context,"Go!!", Toast.LENGTH_SHORT).show()
        })
    )
}

8. singleLine, maxLines

이 2개도 xml에서 많이 사용해봤기 때문에 잘 알 것이라고 생각한다!

singleLine은 말 그대로 한 줄만 보여줄 것인지 여부 true/false (default = false)

maxLines도 최대 몇 줄까지 할 것인가 정하는 것이다  (default = Int.MAX_VALUE)

TextField(
    value = text,
    onValueChange = {text = it},
    singleLine = true,
    maxLines = 3
)

 

 

9. shape, colors

TextFieldDefaults는 많은 값이 있어서 가져오질 못했고 (직접 봐보는게 좋을 듯하다!), 기본적인 shpae과 TextFieldColors는 아래와 같다.

@Immutable
interface Shape {
    /**
     * Creates [Outline] of this shape for the given [size].
     *
     * @param size the size of the shape boundary.
     * @param layoutDirection the current layout direction.
     * @param density the current density of the screen.
     *
     * @return [Outline] of this shape for the given [size].
     */
    fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline
}
class TextFieldColors internal constructor(
    private val textColor: Color,
    private val disabledTextColor: Color,
    private val containerColor: Color,
    private val cursorColor: Color,
    private val errorCursorColor: Color,
    private val textSelectionColors: TextSelectionColors,
    private val focusedIndicatorColor: Color,
    private val unfocusedIndicatorColor: Color,
    private val errorIndicatorColor: Color,
    private val disabledIndicatorColor: Color,
    private val focusedLeadingIconColor: Color,
    private val unfocusedLeadingIconColor: Color,
    private val disabledLeadingIconColor: Color,
    private val errorLeadingIconColor: Color,
    private val focusedTrailingIconColor: Color,
    private val unfocusedTrailingIconColor: Color,
    private val disabledTrailingIconColor: Color,
    private val errorTrailingIconColor: Color,
    private val focusedLabelColor: Color,
    private val unfocusedLabelColor: Color,
    private val disabledLabelColor: Color,
    private val errorLabelColor: Color,
    private val placeholderColor: Color,
    private val disabledPlaceholderColor: Color,
    private val focusedSupportingTextColor: Color,
    private val unfocusedSupportingTextColor: Color,
    private val disabledSupportingTextColor: Color,
    private val errorSupportingTextColor: Color,
    )

색이 굉장히 많기 때문에 원하는 대로 가져다 사용하면 될 것 같다.

 

예시를 봐보자!

TextField(
    value = text,
    onValueChange = {text = it},
    label = { Text("this is label") },
    placeholder = {
        Text( text = "placeholder")
    },
    leadingIcon = {
        Icon(
            imageVector = Icons.Filled.Add,
            contentDescription = "add"
        )
    },
    trailingIcon = {
        Icon(
            imageVector = Icons.Filled.Send,
            contentDescription = "send"
        )
    },
    isError = true,
    shape = RoundedCornerShape(10.dp),
    colors = TextFieldDefaults.textFieldColors(
        errorLabelColor = Color.Magenta,
        errorIndicatorColor = Color.Magenta,
        errorLeadingIconColor = Color.Blue,
        errorTrailingIconColor = Color.Green,
        placeholderColor = Color.Yellow
    )
)

errorColor를 하나씩 커스텀 할 수 있기 때문에 다 다르게설정해 줄 수 있다.

shape은 Round만 주었더니 양 끝이 Indicator 과 좀 떨어지게 되었다.

Indicator를 제외한 부분만 변경되는 것으로 생각해주면 될 것 같다😁

 

+ OutLinedtextField

구성은 textField와 동일하다.

보여주는 shape이 outline으로 변경된 것이다.

하단의 코드를 보면 기본값이 shape과 color에 차이가 있다!

fun OutlinedTextField(
   //...
   shape: Shape = TextFieldDefaults.outlinedShape,
   colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors()
)

fun TextField(
   //...
   shape: Shape = TextFieldDefaults.filledShape,
   colors: TextFieldColors = TextFieldDefaults.textFieldColors()
)
OutlinedTextField(
    value = text,
    onValueChange = {text = it},
    label = { Text("this is label") },
    placeholder = {
        Text( text = "placeholder")
    }
)

 

 

++ InteractionSource

component가 내보내는 이벤트에 해당하는 Interactions의 스트림을 나타낸다.
interactions는 누르거나 dragged 할 때 등 여러 상태에서 구성요소가 나타나는 방식을 변경할 수 있다.

클릭했을 때 효과 (ripple 등) 등을 컨트롤할 때 사용한다.

이 부분은 좀 더 공부하고 나중에 추가하도록 하겠다😂

우선 아래 링크를 보고 참고해주면 좋겠다!

 

MutableInteractionSource  |  Android Developers

androidx.appsearch.builtintypes.properties

developer.android.com

 


 

[참고 및 출처]

 

Handle user input  |  Jetpack Compose  |  Android Developers

Handle user input Stay organized with collections Save and categorize content based on your preferences. TextField allows users to enter and modify text. This page describes how you can implement TextField, style TextField input, and configure other TextFi

developer.android.com

 

 

VisualTransformation  |  Android Developers

androidx.appsearch.builtintypes.properties

developer.android.com

 

https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui-text/samples/src/main/java/androidx/compose/ui/text/samples/VisualTransformationSamples.kt

 

cs.android.com