[Android] 입력이 되는 Chip 만들기

uuranus·2023년 8월 19일
1
post-thumbnail

미리 보는 완성결과

완성 결과


어떻게 만들까

  • 처음 디자이너분께 해당 디자인을 받았을 때 어떻게 구현해야 하나 막막했다.
  • Material Chip 처럼 생겼는데 Edit이 된다고??? Chip으로 그게 되나? 라는 생각이 들었다.
  • 여기서 든 생각이 Chip을 Editable하게 못 한다면 Editable한 뷰를 Chip처럼 보이게 하면 어떨까 라는 것이었다.

EditText를 Chip처럼 보이게 하기

  • 일단 추가하기 버튼은 Editable할 필요가 없기 때문에 Chip으로 만들고 그 옆에 EditText를 추가했다.
    커스텀 전 EditText
  • 누가 봐도 서로 다른 뷰다
  • EditText를 커스텀하기 전에 완성본을 보면 EditText와 Chip이 같은 라인에 존재하고 입력 중에 줄이 넘어가면 다음 라인으로 자동으로 넘어간다.
    • EditText와 Chip이 같은 layout에 있어야 하고 줄바꿈을 자동으로 해 줄 수 있어야 한다.
    • 나는 여기서 구글에서 만든 FlexLayout을 사용했다.
    • FlexLayout Github

배경 만들기

  • 일단 배경이 Chip처럼 보이도록 drawable을 만들어준다.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <corners android:radius="16dp" />

    <stroke
        android:width="1dp"
        android:color="@color/primary_color" />

</shape>

radius는 글자 크기의 절반보다 크게해서 양옆이 동그랗게 보이도록 설정해준다.

  • 원래 chip은 shapeAppearance속성으로 cornerSize 50%로 되어있지만 EditText는 material 이 아니라 shapeAppearance 속성이 없으니 그냥 적당히 큰 값을 넣어준다.

edit text 배경 설정

  • 그러면 이렇게 글씨에 딱 붙고 위쪽에 붙은 형태가 된다. 이건 Chip과의 height 와 padding의 크기가 달라서 생기는 문제이다

padding과 size 조절하기

Chip에는 다음과 같이 4종류의 padding이 존재한다.

chip 자체에 대한 paddding

chip padding

chip padding 없앤 경우
chipStartPadding을 0dp로 설정한 경우이다.
따라서, EditText도 paddingStart와 paddingEnd값을 chipStartPadding이랑 chipEndPadding과 동일하게 맞춰 줄 것이다.

Chip의 앞쪽에 붙는 icon의 padding

chip icon padding
filter chip

Filter Chip의 경우 선택했을 때 앞에 icon이 생기는데 이때의 icon padding 값이다.
EditText의 경우 icon이 없는 것도 있지만 설정된 값도 0dp이니 변경하지 않도록 한다.

Chip Text에 붙는 padding

chip text padding
chipStartPadding, textStartPadding이 0dp
아까 chipStartPadding을 0dp로 설정한 것에서 textStartPadding을 0dp으로 설정한 경우이다. 완전히 border에 딱 붙은 것을 확인할 수 있다.
EditText의 경우 text, chip padding을 따로 설정하지 않고 더해서 padding값을 주도록 하였다.

  • 즉, paddingStart는 4dp (chip) + 8dp (text) = 12dp 로 , paddingEnd는 6dp (chip) + 6dp (text) = 12dp로 설정해주었다.

close Icon에 붙는 padding

chip close icon padding

완성본에도 나와있지만 input chip은 뒤에 close icon이 같이 생성된다.
나의 경우에는 EditText에 실제 close icon을 붙이지 않고 입력이 안료되면 입력된 text를 가지는 chip을 addView하는 방식으로 구현하였다.

  • 이건 추가된 chip은 수정이 되지 않고 수정하려면 삭제하고 다시 추가하는 방식으로 기능이 정해졌기 때문에 이렇게 구현하였다. 만약 수정이 되는 거였다면
    android:drawableEnd="@drawable/icon_close" 속성을 통해서 아이콘을 추가해주면 된다.

EditText와 Chip의 height 맞추기

가로 사이즈 맞춘 후

  • 위 단계까지 했다면 다음과 같이 양쪽 패딩은 맞춰진 상태일 것이다.
  • 이제 두 뷰의 높이사이즈를 같게 해보자.
    가로 사이즈 맞춘 후 실제 뷰 크기
  • 실제 뷰의 사이즈를 보면 Chip의 경우 border까지가 뷰의 사이즈가 아니고 약간의 마진?이 있는 것을 알 수 있다.
  • height에 관련된 속성은 다음과 같이 두 개가 있다.
    chip minheight

minHeight는 말 그대로 해당 뷰의 최소 뷰 높이를 설정하는 것이다. 만약 0dp 로 설정한다면
chipMinHeight을 0dp로
다음과 같이 text에 딱 맞춰 작아진 것을 볼 수 있다.
근데 여기서 minHeight를 0으로 줄였어도 뷰의 마진?은 그대로 있는 것을 알 수 있는데 이게 바로 chipMinTouchTargetSize 때문이다.

chipMinTouchTargetSize

chipMinTouchTargetSize는 chip이 터치될 수 있는 최소사이즈를 말한다. ensureMinTouchTargetSize가 true일 때 설정이 된다. 만약 이를 false로 변경하면
ensureMinTouchTargetSize가 false일 때
다음과 같이 마진이 없어지고 뷰 사이즈와 stroke된 사이즈가 동일해진 것을 볼 수 있다.

그럼 EditText를 어떻게 변경해줘야 할까?

  • 목표는 EditText를 Chip처럼 설정해주는 것이기 때문에 Chip의 chipMinHeight는 그대로 두고 chipMinTouchTargetSize는 조금 줄여서 40dp로 해주었다. 그리고 EditText의 minHeight를 32dp, marginTop,Bottom을 4dp씩 설정해주었다.

이렇게 해서 나온 최종 결과물은

가로세로 사이즈를 맞춘 결과물
다음과 같이 높이가 똑같아졌다!!


최종 style xml

<style name="Widget.App.EditText.IngredientAdd" parent="Widget.AppCompat.EditText">
        <item name="android:background">@drawable/background_edit_chip</item>
        <item name="android:textColor">@color/primary_color</item>
        <item name="android:paddingStart">12dp</item>
        <item name="android:layout_marginEnd">4dp</item>
        <item name="android:layout_marginTop">4dp</item>
        <item name="android:layout_marginBottom">4dp</item>
        <item name="android:paddingEnd">12dp</item>
        <item name="android:minHeight">32dp</item>
        <item name="maxLine">1</item>
        <item name="android:inputType">text</item>
        <item name="android:imeOptions">actionDone</item>
        <item name="android:textAppearance">@style/TextAppearance.App.Regular14</item>
    </style>
  • maxLine~imeOptions는 엔터를 쳤을 때 줄바꿈이 되는 것을 막기 위함이며 marginEnd는 추가하기 버튼과의 간격을 주기 위함이다.

리스너 달기

  • 추가하기 버튼을 누르면 빈 입력칸이 나오고 추가하기를 다시 누르거나 빈 공간을 누르면 x 버튼이 있는 chip으로 추가가 된다.
  • 이를 구현한 방법은 간단하게 코드만 추가해놓는다.
  1. 추가하기 버튼을 눌렀을 때 빈 입력칸이 보여야 한다.
  • 기존 입력칸에 텍스트가 있을 경우 chip으로 추가한 후 text를 clear해준다.
  1. 빈 공간을 선택해 EditText가 focus를 잃었을 때 텍스트가 있는 경우 chip으로 추가한 후 text를 clear하고 보이지 않게 바뀐다.
addIngredientEditText.setOnFocusChangeListener { v, hasFocus ->
                if (hasFocus.not()) {
                    val oldText = viewModel.getIngredientInputText()
                    if (oldText.isNotEmpty()) {
                        ingredientGroup.addIngredient(oldText)
                        addIngredientEditText.text?.clear()
                    }
                    addIngredientEditText.isVisible = false
                }
            }
  • 텍스트가 있었으면 chip으로 추가하고 텍스트를 clear하는 부분이 추가하기를 눌렀을 때와 포커스를 잃었을 때와 동일하다.
  • 따라서, 포커스 리스너 코드에 다음과 같이 구현해놓고
addIngredientButton.setOnClickListener {
                addIngredientEditText.clearFocus()
                addIngredientEditText.isVisible = true
            }

추가하기 버튼 클릭 리스터는 다음과 같이 구현했다.

참고

  • focusChangeListener를 사용하려면 EditText가 focusable="true"이고 focusableInTouchMode="true"여야 하고 빈 공간을 눌렀을 때 포커스가 풀릴 려면 빈 공간도 포커스가 가능해야 한다. (그래야 포커스가 빈 공간으로 가면서 EditText의 포커스가 풀린다.) 그래서 root ConstraintLayout도 포커스가 가능하게 설정해줬다.

세줄 요약

  1. EditText를 Chip처럼 보이게 하고 입력이 완료된 후에는 Chip으로 addView를 하여 Chip이 Editable한 것처럼 보이도록 한다.
  2. Chip의 attribute 속성을 분석해 EditText의 width, height, padding, margin값을 설정해준다.
  3. focusChangeListener를 통해서 EditText가 포커스를 잃은 경우 지금까지 입력된 값을 Chip으로 추가해준다.
profile
Android Developer

1개의 댓글

comment-user-thumbnail
2023년 8월 19일

훌륭한 글 감사드립니다.

답글 달기