AutoSize TextField 만들기

post-thumbnail

(https://pub.dev/packages/auto_size_text)

TextField를 구현할때, 위와같이 자동으로 줄어드는 TextField를 구현하려면 어떻게 해야할까요?

우리는 두가지 목표를 향할겁니다.
1. 지정된 높이보다 크면 TextField의 폰트가 줄어든다.
2. maxLine보다 크면 TextField의 폰트가 줄어든다.

1. 처음에 생각한 계획(실패한 코드)

코드를 구현할때 가장 중요한것은 어떻게 구현할지 계획 해보는겁니다.
자동으로 줄어드는 화면을 구현하기 위해서는 아래와 같은 구현이 필요합니다.

  1. TextField의 고정높이 알아내기
  2. TextField의 라인카운트 알아내기
  3. onTextLayout에서 해당 폰트 처리

BasicTextField의 onTextLayout에서 현재 입력된 단락을 알수있습니다.

여기서 MultiParagraph를 통해 이전에 계산된 텍스트의 Height를 얻을 수 있습니다.

이것을 토대로 TextField를 만들어보면 아래와 같이 만들 수 있습니다.

@Composable
fun AutoSizeTextField(
    value: String,
    onValueChange: (String) -> Unit,
    modifier: Modifier = Modifier,
    fontSize: TextUnit = 32.sp,
    maxLines: Int = Int.MAX_VALUE,
    minFontSize: TextUnit,
    scaleFactor: Float = 0.9f,

) {
    val density = LocalDensity.current
    BoxWithConstraints(
        modifier = modifier,
    ) {
        var resizedStyle by remember { mutableStateOf(fontSize) }

        // https://developer.android.com/jetpack/compose/text
        BasicTextField(
            value = value,
            maxLines = 2,
            onValueChange = onValueChange,
            textStyle = TextStyle.Default.copy(fontSize = resizedStyle),
            onTextLayout = { result ->
                //크기가 height에 벗어나려고 할때()  or  lineCount가 벗어날때 
                if (with(density) { result.multiParagraph.height > minHeight.toPx() } || result.lineCount > maxLines+1) {
                    Log.d("multiParagraph", result.multiParagraph.height.toString())
                    Log.d("didExceedMaxLines", result.multiParagraph.didExceedMaxLines.toString())
                    Log.d("minheight", minHeight.dpToPx(density).toString())
                    // 폰트사이즈를 감소시킨다.
                    resizedStyle *= scaleFactor
                }
            },
        )

lineCount에서 벗어날때, 잘 줄어들지만 하나의 문제가 있습니다.
바로 글자를 제거했을때 폰트사이즈가 다시 늘어나야하는것입니다.

사이즈는 언제 늘어날 수 있을까요?
작은 사이즈를 다시 키우기엔 분기를 정하는게 어렵습니다.
만약 하고자 한다면, text가 줄어들었을때 size를 한번 키워보고, lineCount에 벗어나면 안키운다. 혹은 Text가 변경될때마다 기존 사이즈에서 감소된값을 넣는것도 좋을듯 합니다.

이런 문제를 해결하려면, 기존에 입력하기전에 단락의 크기정보를 알아야합니다.
하지만 onTextLayout에서는 입력된 단락의 height만 알수있는데, TextField에 입력하기 전에 알수있는 방법이 없을까요?

Paragraph()라면 가능합니다.

요점은 후행이 아닌 사전에 처리를 하는것입니다.
onTextLayout에서 받는게 아닌, Paragraph() 생성자를 호출하고, 미리 해당 높이를 아는것이죠.

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AutoSizableOutLinedTextField(
    value: String,
    onValueChange: (String) -> Unit,
    modifier: Modifier = Modifier,
    fontSize: TextUnit = 32.sp,
    maxLines: Int = Int.MAX_VALUE,
    minFontSize: TextUnit,
    scaleFactor: Float = 0.9f,
) {
    val density = LocalDensity.current

    BoxWithConstraints(
        modifier = modifier,
    ) {
        // 폰트사이즈가 알아서 변경되어야함.
        var nFontSize = fontSize
        
        //Density때문에 컴포저블 어노테이션이 붙었음
        // OutLineTextField가 기본적으로 제공하는 패딩 때문에 16*2 가 붙었음
        val calculateParagraph = @Composable{
            Paragraph(
                text = value,
                style = TextStyle(fontSize = nFontSize),
                constraints = Constraints(maxWidth = ceil(with<Density, Float>(LocalDensity.current) { (maxWidth - 32.dp).toPx() }).toInt()),
                density = LocalDensity.current,
                fontFamilyResolver = LocalFontFamilyResolver.current,
                spanStyles = listOf(),
                placeholders = listOf(),
                maxLines = maxLines,
                ellipsis = false,
            )
        }


        var intrinsics = calculateParagraph()

        //입력할때마다 축소된값으로 변경
        with(LocalDensity.current) {
            while ((intrinsics.height.toDp() > maxHeight || intrinsics.didExceedMaxLines) && nFontSize >= minFontSize) {
                Log.d("intrinsics", intrinsics.height.pxToDp(density).toString())
                Log.d("maxHeight", maxHeight.dpToPx(density).toString())
                Log.d("minHeight", minHeight.dpToPx(density).toString())
                Log.d("didExceedMaxLines", intrinsics.didExceedMaxLines.toString())
                nFontSize *= scaleFactor
                //Log.d
                intrinsics = calculateParagraph()
            }
        }

        OutlinedTextField(
            value = value,
            onValueChange = onValueChange,
            modifier = Modifier.fillMaxSize(),
            shape = RoundedCornerShape(8.dp),
            maxLines = maxLines,
            textStyle = TextStyle(fontSize = nFontSize),

        )
    }
}

기존과 다르게 사이즈가 remember되어있지 않기때문에 Text가 줄어들면 줄어든 대로 Size를 갖게 됩니다.

감사합니다!

참고

Android Jetpack Compose의 AutoSize TextField

profile
쉽게 가르칠수 있도록 노력하자

0개의 댓글