이번 프로젝트를 하며 EditText 앞에 Chip이 추가되는 방식의 화면을 구현해야 했다. 기존의 방식은 ChipGroup과 EditText를 사용해서 입력을 하면 ChipGroup에 추가되는 방식이었지만, 마지막 Chip 바로 옆에 입력창이 있었으면 했다. 이러한 방식으로는 도저히 생각나지 않아 다른 방법을 찾아본 결과 FlexboxLayout를 활용한 방법을 알게 되었다.
FlexboxLayout은 웹에서 사용되던 CSS Flexible Box Layout Module을 안드로이드에 접목하여 개발한 라이브러리이다. 구글에서 만든 라이브러리인데 왜 이제야 알았을까. 뷰를 동적으로 추가해야 한다고 했을 때 자연스러운 모션을 보여줘야 한다면 FlexboxLayout이 좋은 선택지가 될 수 있다.
FlexboxLayout에 대한 자세한 설명은 github.com/google/flexbox-layout에 나와있다.
이번에 구현해야 할 화면은 다음과 같다. 네이버 메일 어플에서 받는 사람에 이메일을 추가하는 방식과 같다.
1. dependency
FlexboxLayout과 Material Chip을 사용할 것이므로 아래 두 개의 의존성을 추가한다.
implementation "com.google.android:flexbox:2.0.1"
implementation 'com.google.android.material:material:1.3.0-alpha03'
2. view_chip.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.chip.Chip xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:theme="@style/Widget.MaterialComponents.Chip.Action"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
app:closeIconEnabled="true" />
3. activity_main.xml
FlexboxLayout 안에 자식으로 EditText를 추가한다. 아래 Activity 코드에서 나오겠지만, Chip은 FlexboxLayout의 자식으로 추가하게 된다.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/flex_box_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:flexWrap="wrap"
app:alignItems="stretch"
app:alignContent="stretch" >
<EditText
android:id="@+id/edit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="입력"
android:imeOptions="actionDone"
android:maxLines="1"
android:singleLine="true"
android:inputType="text|textCapWords"
app:layout_flexGrow="1"/>
</com.google.android.flexbox.FlexboxLayout>
</FrameLayout>
4. Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
edit_text.setOnKeyListener { v, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
val et = v as EditText
val name = et.text.toString()
flex_box_layout.addChip(name)
et.text = null
}
return@setOnKeyListener false
}
}
private fun FlexboxLayout.addChip(text: String) {
val chip = LayoutInflater.from(context).inflate(R.layout.view_chip, null) as Chip
chip.text = text
val layoutParams = ViewGroup.MarginLayoutParams(ViewGroup.MarginLayoutParams.WRAP_CONTENT, ViewGroup.MarginLayoutParams.WRAP_CONTENT)
layoutParams.rightMargin = dpToPx(4)
chip.setOnCloseIconClickListener {
removeView(chip as View)
}
addView(chip, childCount - 1, layoutParams)
}
private fun FlexboxLayout.getAllChips(): List<Chip> {
return (0 until childCount).mapNotNull { index ->
getChildAt(index) as? Chip
}
}
private fun FlexboxLayout.clearChips() {
val chipViews = (0 until childCount).mapNotNull { index ->
val view = getChildAt(index)
if (view is Chip) view else null
}
chipViews.forEach { removeView(it) }
}
}
fun Context.dpToPx(dp: Int): Int
= TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), resources.displayMetrics).roundToInt()
예제 코드라서 최대한 간단하게 만들었다. 핵심은 addChip()이다. inflate를 통해 Chip 객체를 생성한 뒤, FlexboxLayout에 addview 해준다. 이때 주의할 점은, FlexboxLayout의 자식 중에 EditText를 포함하고 있기 때문에 addView 할 때 index를 childCount - 1로 설정해준다.
이번에 FlexboxLayout을 처음 알게 되고 사용해봤는데 굉장히 재미있는 레이아웃인 것 같다. 잘만 사용한다면 기존의 자주 사용하던 ConstraintLayout이나 LinearLayout으로 구현하기 힘든 레이아웃들도 쉽게 구현할 수 있을 듯하다.
'Android' 카테고리의 다른 글
[Android] parent의 영역을 벗어나서 UI를 그려보자 - clipChildren (2) | 2020.12.06 |
---|---|
[Android] Custom Calendar 만들기 - Infinite ViewPager 구현 (2) | 2020.11.27 |
[Android] CollapsingToolbarLayout 응용하기 - Google Calendar App 클론 코딩 (0) | 2020.09.19 |
[Android] ViewTreeObserver란? - View가 그려지는 시점 알아내기 (0) | 2020.09.19 |
[Android] ImageView에 색상 넣기 (ColorFilter vs Tint) (2) | 2020.09.11 |