[안드로이드] 머티리얼 디자인 - Menu(DropDown)

이번엔 DropDown에 대해 알아보겠습니다~
이번 예제에서는 두 종류의 DropDown을 사용해볼 예정입니다.
프로젝트 전체 layout 입니다.
<?xml version="1.0" encoding="utf-8"?> | |
<layout | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
xmlns:tools="http://schemas.android.com/tools"> | |
<androidx.constraintlayout.widget.ConstraintLayout | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
tools:context=".skill.MaterialDesign3Activity"> | |
<com.google.android.material.navigationrail.NavigationRailView | |
android:id="@+id/navigation_rail" | |
android:layout_width="wrap_content" | |
android:layout_height="0dp" | |
android:background="@color/white" | |
app:itemIconTint="@color/select_navi_rail_icon_color" | |
app:itemTextColor="@color/select_navi_rail_text_color" | |
app:itemRippleColor="@color/bg_gray" | |
app:menu="@menu/menu_navigation_rail" | |
app:headerLayout="@layout/layout_rail_header" | |
app:labelVisibilityMode="unlabeled" | |
app:menuGravity="bottom" | |
app:layout_constraintTop_toTopOf="parent" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintStart_toStartOf="parent"/> | |
<androidx.appcompat.widget.AppCompatButton | |
android:id="@+id/btnDropDown" | |
android:layout_width="0dp" | |
android:layout_height="wrap_content" | |
android:text="DropDown 메뉴" | |
android:layout_marginTop="24dp" | |
app:layout_constraintWidth_percent="0.5" | |
app:layout_constraintStart_toEndOf="@+id/navigation_rail" | |
app:layout_constraintTop_toTopOf="parent" | |
app:layout_constraintEnd_toEndOf="parent"/> | |
<com.google.android.material.textfield.TextInputLayout | |
android:id="@+id/textInputLayout" | |
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu" | |
android:layout_width="0dp" | |
android:layout_height="wrap_content" | |
android:hint="Hint" | |
app:helperText="HelperText" | |
android:layout_marginTop="24dp" | |
app:layout_constraintTop_toBottomOf="@+id/btnDropDown" | |
app:layout_constraintStart_toStartOf="@+id/btnDropDown" | |
app:layout_constraintEnd_toEndOf="@+id/btnDropDown"> | |
<AutoCompleteTextView | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:inputType="none" /> | |
</com.google.android.material.textfield.TextInputLayout> | |
</androidx.constraintlayout.widget.ConstraintLayout> | |
</layout> |
먼저, res > menu 디렉토리에 xml 파일을 생성하고, 그 menu xml을 inflate하는 방식입니다.
그리고 다음 코드를 작성합니다.
@SuppressLint("RestrictedApi") | |
private fun showDropDownMenu(v: View){ | |
val popup = PopupMenu(this, v) | |
popup.menuInflater.inflate(R.menu.menu_drop_down, popup.menu) | |
if (popup.menu is MenuBuilder) { | |
val menuBuilder = popup.menu as MenuBuilder | |
menuBuilder.setOptionalIconsVisible(true) | |
menuBuilder.visibleItems.forEach { item -> | |
val iconMarginPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, resources.displayMetrics).toInt() | |
item.icon?.let { | |
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { | |
item.icon = InsetDrawable(it, iconMarginPx, 0, iconMarginPx, 0) | |
} else { | |
item.icon = object: InsetDrawable(it, iconMarginPx, 0, iconMarginPx, 0) { | |
override fun getIntrinsicWidth(): Int { | |
return it.intrinsicWidth + iconMarginPx + iconMarginPx | |
} | |
} | |
} | |
} | |
} | |
} | |
popup.show() | |
popup.setOnMenuItemClickListener { | |
when(it.itemId) { | |
R.id.dropDown1 -> { | |
Toast.makeText(this, "DropDown1 Clicked",Toast.LENGTH_SHORT).show() | |
} | |
R.id.dropDown2 -> { | |
Toast.makeText(this, "DropDown2 Clicked",Toast.LENGTH_SHORT).show() | |
} | |
R.id.dropDown3 -> { | |
Toast.makeText(this, "DropDown3 Clicked",Toast.LENGTH_SHORT).show() | |
} | |
R.id.dropDown4 -> { | |
Toast.makeText(this, "DropDown4 Clicked",Toast.LENGTH_SHORT).show() | |
} | |
} | |
false | |
} | |
popup.setOnDismissListener { | |
Snackbar.make(binding!!.btnDropDown, "DropDown Dismissed!!", Snackbar.LENGTH_SHORT).show() | |
} | |
} |
showDropDownMenu(v: View)의 파라미터 v는 해당 드랍다운 메뉴가 발생하는 뷰의 입니다. 이 예제에서는 버튼 클릭 시 드랍다운 메뉴가 버튼 아래로 나타나게 됩니다. menu를 inflate한 후에 아이콘의 margin을 설정해주는 코드를 작성했습니다.(공식 홈페이지 참고했습니다.)
각 메뉴마다 이벤트를 처리할 수 있습니다.
popup.setOnMenuItemClickListener { | |
when(it.itemId) { | |
R.id.dropDown1 -> { | |
Toast.makeText(this, "DropDown1 Clicked",Toast.LENGTH_SHORT).show() | |
} | |
R.id.dropDown2 -> { | |
Toast.makeText(this, "DropDown2 Clicked",Toast.LENGTH_SHORT).show() | |
} | |
R.id.dropDown3 -> { | |
Toast.makeText(this, "DropDown3 Clicked",Toast.LENGTH_SHORT).show() | |
} | |
R.id.dropDown4 -> { | |
Toast.makeText(this, "DropDown4 Clicked",Toast.LENGTH_SHORT).show() | |
} | |
} | |
false | |
} |
드랍다운 메뉴가 dismiss 됐을때의 이벤트를 처리할 수 있습니다.
popup.setOnDismissListener { | |
Snackbar.make(binding!!.btnDropDown, "DropDown Dismissed!!", Snackbar.LENGTH_SHORT).show() | |
} |
버튼 이벤트에서 (btnDropDown) 함수를 실행합니다.
override fun onClick(v: View?) { | |
when(v?.id) { | |
R.id.btnReturn -> onBackPressed() | |
R.id.layoutDraw -> Toast.makeText(this, "Draw Clicked", Toast.LENGTH_SHORT).show() | |
R.id.btnDraw -> Toast.makeText(this, "Draw Clicked", Toast.LENGTH_SHORT).show() | |
R.id.btnDropDown -> showDropDownMenu(v) | |
} | |
} |
여기까지 첫번째 DropDown을 만들어 봤습니다.
다음은 ExposedDropDownMenu를 만들어 보겠습니다.
<com.google.android.material.textfield.TextInputLayout | |
android:id="@+id/textInputLayout" | |
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu" | |
android:layout_width="0dp" | |
android:layout_height="wrap_content" | |
android:hint="Hint" | |
app:helperText="HelperText" | |
android:layout_marginTop="24dp" | |
app:layout_constraintTop_toBottomOf="@+id/btnDropDown" | |
app:layout_constraintStart_toStartOf="@+id/btnDropDown" | |
app:layout_constraintEnd_toEndOf="@+id/btnDropDown"> | |
<AutoCompleteTextView | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:inputType="none" /> | |
</com.google.android.material.textfield.TextInputLayout> |
private fun showExposedDropDownMenu() { | |
val items = listOf("items 1","items 2","items 3","items 4") | |
val adapter = ArrayAdapter(this, R.layout.list_item, items) | |
(binding!!.textInputLayout.editText as? AutoCompleteTextView)?.setAdapter(adapter) | |
(binding!!.textInputLayout.editText as? AutoCompleteTextView)?.setOnItemClickListener { parent, view, position, id -> | |
when(position) { | |
0 -> Toast.makeText(this, "ExposedDropDown 1 Clicked",Toast.LENGTH_SHORT).show() | |
1 -> Toast.makeText(this, "ExposedDropDown 2 Clicked",Toast.LENGTH_SHORT).show() | |
2 -> Toast.makeText(this, "ExposedDropDown 3 Clicked",Toast.LENGTH_SHORT).show() | |
3 -> Toast.makeText(this, "ExposedDropDown 4 Clicked",Toast.LENGTH_SHORT).show() | |
} | |
} | |
(binding!!.textInputLayout.editText as? AutoCompleteTextView)?.setOnDismissListener { | |
Snackbar.make(binding!!.btnDropDown, "ExposedDropDownMenu Dismissed!!", Snackbar.LENGTH_SHORT).show() | |
} | |
} |
<?xml version="1.0" encoding="utf-8"?> | |
<TextView xmlns:android="http://schemas.android.com/apk/res/android" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:ellipsize="end" | |
android:maxLines="1" | |
android:padding="10dp" | |
android:textSize="16dp" | |
android:textAppearance="?attr/textAppearanceBodyLarge" /> |
위와 같이 list를 생성하고 ArrayAdapter를 생성한 후에 textInputLayout안에 AutoCompleteTextView의 adapter를 설정합니다. 그리고 showExposedDropDownMenu() 함수를 필요한 시기에 호출합니다. 저의 경우는 onCreate에서 호출했습니다.
각 아이템의 클릭 이벤트를 처리할 수 있습니다.
(binding!!.textInputLayout.editText as? AutoCompleteTextView)?.setOnItemClickListener { parent, view, position, id -> | |
when(position) { | |
0 -> Toast.makeText(this, "ExposedDropDown 1 Clicked",Toast.LENGTH_SHORT).show() | |
1 -> Toast.makeText(this, "ExposedDropDown 2 Clicked",Toast.LENGTH_SHORT).show() | |
2 -> Toast.makeText(this, "ExposedDropDown 3 Clicked",Toast.LENGTH_SHORT).show() | |
3 -> Toast.makeText(this, "ExposedDropDown 4 Clicked",Toast.LENGTH_SHORT).show() | |
} | |
} |
ExposedDropDown 메뉴가 dismiss 됐을때 이벤를 처리 할 수 있습니다.
(binding!!.textInputLayout.editText as? AutoCompleteTextView)?.setOnDismissListener { | |
Snackbar.make(binding!!.btnDropDown, "ExposedDropDownMenu Dismissed!!", Snackbar.LENGTH_SHORT).show() | |
} |
TextInputLayout의 style이 현재는 OutlineBox 스타일로 되어있는데, 다른 스타일의 형태도 있으니 참고하시면 되겠습니다.
Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu | |
Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu | |
Widget.MaterialComponents.TextInputLayout.FilledBox.Dense.ExposedDropdownMenu | |
Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense.ExposedDropdownMenu |