본문 바로가기
프로그래밍/Jetpack Compose

[Jetpack Compose] TextField 에서 사용하는 VisualTransformation 에 대해 알아보자

by dev_gyu 2024. 9. 25.
728x90

TextField 를 통한 사용자 입력을 받을 때 UI/UX 를 위해 단위 표시를 받는 부분들이 자주 생길 것이다.

하지만 이러한 Unit 을 유저에게 보여주기 위해 Input Value 에 Unit 을 집어넣는다면 코드단에서 다시 Unit 을 지워줘야하므로 불필요한 로직이 발생할 수 있으며, 때로는 버그가 발생할 수도 있다.

 

이러한 상황을 대비해서 Jetpack Compose TextField 에서는 VisualTransformation 이라는 기능을 통해 실제 값에는 영향이 가지 않는, 사용자에게 시각적으로만 표시가 되어지는 기능을 사용할 수 있다.

 

VisualTransformation 이란?

  • Jetpack Compose에서 TextField의 입력값을 시각적으로 변환하기 위해 사용하는 인터페이스
  • 사용자가 입력한 텍스트를 다른 형태로 표시하고자 할 때 활용
  • onValueChanged 의 Return 값은 VisualTransformation 이 적용되지 않은 값으로 Return 된다.
  • TextField 를 사용할 때 원, 달러나 10세, 19세 등 나이표시와 같은 단위 표시를 할 때 주로 사용

 

테스트

 

말로만 설명하면 이해가 쉽게 가지 않을 수 있으니, 테스트를 해보도록 하자.

TextField 의 visuableTransformation 은 기본적으로 VisualTransfomation.None 으로 설정되어 있기 때문에, 개발자가 따로 지정해주지 않는다면 아무런 시각 효과도 제공해주지 않는다.

그러므로 나는 PasswordVisualTransformation 을 사용하여 TextField 를 구현 해보도록 해보겠다.

@Composable
fun Test(modifier: Modifier){
    var textFieldValue by remember { mutableStateOf(TextFieldValue()) }

    TextField(
        modifier = modifier.fillMaxWidth(),
        visualTransformation = PasswordVisualTransformation(),
        value = textFieldValue,
        onValueChange = {
            Log.e("확인", "${it.text}")
            textFieldValue = it
        },
        placeholder = {
            Text(text = "Type something here")
        }
    )
}

 

위와 같이 TextField  TextFieldValue 를 설정해주고 키보드 입력이 들어올 때마다 로그와 함께 TextFieldValue 를 업데이트 하도록 설정하였다.

visualTransformation 에는 PasswordVisualTransformation() 을 설정해주었다.

 

그럼 에뮬레이터를 실행하고 텍스트필드에 숫자를 입력해보자.

 

숫자 입력 화면

 

우리가 입력한 값은 숫자임에도 암호화된 것처럼 숫자가 보이지 않고 있다.

그렇다면 Return Value 는 로그에서 어떻게 찍히고 있을까?

 

보이는 형식과는 입력값이 정상적으로 Return 되고 있다.

 

위와 같이 설정되는 이유는 무엇일까?

이유는 PasswordVisualTransformation 의 내부 코드를 보면 알 수 있다.

 

PasswordVisualTransformation  VisualTransformation Interface 를 상속받고 있으며, override 한 filter 클래스에서 입력 값인 text.text 의 길이 만큼 \u2022 문자로 변환한 값으로 Transform 하여 TextField 에 전달하기 때문이다.

class PasswordVisualTransformation(val mask: Char = '\u2022') : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
        return TransformedText(
            AnnotatedString(mask.toString().repeat(text.text.length)),
            OffsetMapping.Identity
        )
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is PasswordVisualTransformation) return false
        if (mask != other.mask) return false
        return true
    }

    override fun hashCode(): Int {
        return mask.hashCode()
    }
}

 

VisualTransformation 커스텀 방법

  1. PasswordVisualTransformation 와 동일하게 VisualTransformation 을 상속받는 커스텀 클래스를 구현해준다.
  2. Filter 함수를 Override 하여 TransformedText 를 Return 해준다.
class CustomVisualTransformation: VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
        val trimmed = if (text.text.length >= 10) text.text.substring(0..9) else text.text
        val annotatedString = AnnotatedString.Builder().apply {
            for (i in trimmed.indices) {
                append(trimmed[i])
                if (i == 2 || i == 5) append("-")
            }
        }.toAnnotatedString()

        val offsetMapping = object : OffsetMapping {
            override fun originalToTransformed(offset: Int): Int {
                if (offset <= 2) return offset
                if (offset <= 5) return offset + 1
                if (offset <= 9) return offset + 2
                return 12
            }

            override fun transformedToOriginal(offset: Int): Int {
                if (offset <= 2) return offset
                if (offset <= 6) return offset - 1
                if (offset <= 10) return offset - 2
                return 10
            }
        }

        return TransformedText(annotatedString, offsetMapping)
    }
}

 

위의 클래스에서 TransformedText 에 설정값들의 설명은 다음과 같다

  • annotatedString : 유저에게 시각적으로 보여주기 위한 Text
  • offsetMapping : 텍스트 입력 및 삭제 등 이벤트 시 커서의 위치를 설정
    • originalToTransformed : 사용자가 원본 텍스트에 커서를 놓았을 때, 변환된 텍스트로 Offset 을 변환.
    • transformToOriginal : 사용자가 변환된 텍스트에서 커서를 이동할 때, 현재 커서 위치를 원본 텍스트의 인덱스로 변환하여 원본 데이터에 반영.

 

OriginalToTransformed 예시

- 0101234567 입력 시 커서를 1에 놓는 경우 Offset 이 4이지만, 하이픈이 추가되었으므로 5를 Return.

TransformToOriginal 예시

- 010-123-5678 에서 커서를 1에 놓는 경우 Offset 이 - 까지 포함되어 5이지만, 하이픈 값을 제거하였으므로 4를 Return

728x90