Logo
Published on
·6 min read

Spinner 이용하여 Dropdown(select box) 구현(안드로이드)

개요

sosone 프로젝트의 스마트 탁상 시계(안드로이드)에는 폰트를 선택하는 기능이 있습니다.
폰트는 Dropdown으로 선택할 수 있고, 선택된 항목은 색상을 달리해 강조되도록 했습니다.

서비스 바로가기

스마트 탁상 시계

화면 미리보기

Fragment 화면을 이용했으며,
font 클릭 시 Dialog 형태로 Spinner(Dropdown)가 표시됩니다.

Spinner(Dropdown) 동작 화면은 아래 이미지와 같습니다.

Spinner(Dropdown) 동작 화면

작업 간략 소개

Layout 부분

  • layout 에 <Spinner ... />추가
  • Spinner 각 항목에 대한 layout 추가 (res/layout/spinner_list_item.xml)
  • 선택된 항목을 강조 표시하기 위한 drawable 추가 (res/drawable/selector_spinner_background.xml)

Kotlin 코드 부분

  • Spinner 에 Adapter 연결
    • (선택) Spinner에 들어갈 배열은 Enum으로 정의함 (EnumFont.kt)
  • 이전에 선택된 항목이 Default로 선택되도록 setSelection
  • 항목 선택 시 작업 Listener 설정

layout

fragment_setting.xml

  • font Spinner 목록이 있는 설정 화면 레이아웃입니다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ...생략...>
    <ScrollView
        android:id="@+id/view_setting"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <!-- 생략 -->

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <com.google.android.material.textview.MaterialTextView
                android:id="@+id/font_hint"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="font"
                />
            <Spinner
                android:id="@+id/spinner_font_select"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:spinnerMode="dialog"
                />

        </LinearLayout>
    </ScrollView>
</LinearLayout>
[ 코드 간략 설명 ]
  • font 선택용을 표시할 TextView- 다른 설정들을 material 컴포넌트를 사용했기 때문에 동일하게 MaterialTextView를 사용했습니다.

  • Spinner 설정- android:spinnerMode="dialog" 또는 "dropdown"을 선택할 수 있습니다.

spinner_list_item.xml

Spinner의 각 항목(폰트)에 대한 layout입니다.

Spinner의 Adapter에 연결됩니다. (SettingFragment.kt 참고)

android:backgroundselector를 사용하여 선택된 항목을 강조 표시할 수 있도록 했습니다.
(selector_spinner_background.xml 부분 참고)

<?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:padding="8dp"
    android:ellipsize="marquee"
    android:maxLines="1"
    android:textAppearance="?attr/textAppearanceSubtitle1"
    android:background="@drawable/selector_spinner_background"
    />

selector_spinner_background.xml

선택된 항목을 강조 표시하기 위한 부분입니다.
state_activated 상태일 때 해당 색상으로 표시됩니다.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/teal_700" android:state_activated="true" />
</selector>

Kotlin Source (Fragment)

SettingFragment.kt

코드 내 주석을 참고해 주세요.

class SettingFragment : Fragment() {
	private var _binding : FragmentSettingBinding? = null
    private val binding get() = _binding!!

    // 설정한 Data는 viewMode에서 관리
    private val viewModel: ClockViewModel by activityViewModels{
        ClockViewModelFactory(
            (activity?.application as ClockApplication).database.settingDao()
        )
    }

    // ...생략...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    	super.onViewCreated(view, savedInstanceState)

        // font는 사용자에게 보여는 것과, 실제 로드해야하는 이름이 달라 enum으로 정의
        val fontItems = enumValues<EnumFont>()
        // Spinner에 로드할 ArrayAdapter 지정
        val arrayAdapter = ArrayAdapter(requireContext(), R.layout.spinner_list_item, fontItems)
        binding.spinnerFontSelect.adapter = arrayAdapter

        // 선택된 폰트 정보가 있을 경우 setSelection으로 지정
        if(viewModel.font.value != null && viewModel.font.value!!.isNotEmpty()){
            binding.spinnerFontSelect.setSelection(
                arrayAdapter.getPosition(
                    EnumFont.valueOf(
                        viewModel.font.value!!
                    )
                )
            )
        }

        // 폰트 선택 시 Listener 정의
        binding.spinnerFontSelect.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
            override fun onNothingSelected(parent: AdapterView<*>?) {
                println("onNothingSelected")
            }

            override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
                println("onItemSelected")

                val selectedFont = parent?.getItemAtPosition(position) as EnumFont
                viewModel.changeFont(selectedFont.name)
            }
        }

        // font TextView 클릭시에 font Spinner 열리도록 추가
        binding.fontHint.setOnClickListener { binding.spinnerFontSelect.performClick() }

    }
}

EnumFont.kt

font는 사용자에게 보여는 것과, 실제 로드해야 하는 이름이 달라 enum으로 정의했습니다.

enum class EnumFont(val fontName: String) {
    DEFAULT("default"),
    BLACK_HAN_SANS("black_han_sans"),
    CUTE_FONT("cute_font"),
    DONGLE("dongle_regular"),
    DONGLE_BOLD("dongle_bold"),
    GEO("geo"),
    GUGI("gugi"),
    MONTSERRAT("montserrat_bold"),
    NANUM_GOTHIC("nanum_gothic_bold"),
    // 생략
}

덧붙이는 내용

처음에는 아래와 같이 material AutoCompleteTextView를 이용했습니다.

<com.google.android.material.textfield.TextInputLayout ... 생략 ...>
	<AutoCompleteTextView ... 생략 ... />
</com.google.android.material.textfield.TextInputLayout>

이유는 Spinner 보다 디자인, Dropdown 시작 위치 조정, 높이 조정 등 더 좋아 보였습니다.
하지만 Spinner로 변경한 이유는 저의 기술 부족 문제로 아래의 문제들을 해결할 수 없었기 때문입니다.

  • setText로 선택된 항목이 표시된 상태에서, 회전이 되면 기존 Dropdown 목록이 없어지고 선택된 항목만 남는 버그가 발견됐습니다.
  • 선택된 항목을 강조 표시하기 위해 따로 상태값을 지정해줘야 하는 번거로움이 있었습니다.