Logo
Published on
·7 min read

Implementing Dropdown (Select Box) Using Spinner in Android

I apologize in advance for any awkward expressions in English.

English is not my native language, and I have relied on ChatGPT's assistance to proceed with the translation.

Overview

The smart desk clock in the sosone project (Android) includes a font selection feature. Fonts can be selected via a dropdown, and the selected item is highlighted with a different color.

Service Shortcut

Smart Desk Clock

Screen Preview

Fragment screens are used, and when a font is clicked, a Spinner (Dropdown) is displayed in the form of a dialog.

The Spinner (Dropdown) operation screen looks like the image below.

Spinner (Dropdown) Operation Screen

Brief Introduction to the Work

Layout Section

  • Added <Spinner ... /> to the layout
  • Added layouts for each item in the Spinner (res/layout/spinner_list_item.xml)
  • Added a drawable to highlight the selected item (res/drawable/selector_spinner_background.xml)

Kotlin Code Section

  • Connected an Adapter to the Spinner
  • (Optional) Defined the array for the Spinner using Enum (EnumFont.kt)
  • Set the previously selected item as the default using setSelection
  • Set up operations when an item is selected with a Listener

layout

fragment_setting.xml

  • This is the layout for the settings screen with a font Spinner list.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ...Omitted...>
    <ScrollView
        android:id="@+id/view_setting"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <!-- Omitted -->

        <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>
[ Brief Code Explanation ]
  • TextView for displaying font selection: We used MaterialTextView to maintain consistency with other material components used in the settings.

  • Spinner configuration: You can choose between android:spinnerMode="dialog" or android:spinnerMode="dropdown".

spinner_list_item.xml

This is the layout for each item (font) in the Spinner.

It is connected to the Spinner's Adapter (refer to SettingFragment.kt).

We use selector in the android:background to highlight the selected item (refer to 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

This section is used to highlight the selected item. It is displayed with the specified color when in the state_activated state.

<?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

Please refer to the comments in the code.

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

    // The configured data is managed in the view model.
    private val viewModel: ClockViewModel by activityViewModels{
        ClockViewModelFactory(
            (activity?.application as ClockApplication).database.settingDao()
        )
    }

    // ...omitted...

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

        // Fonts are defined as an enum since they have different names to display to the user and load in reality.
        val fontItems = enumValues<EnumFont>()
        // Specify the ArrayAdapter to load into the Spinner.
        val arrayAdapter = ArrayAdapter(requireContext(), R.layout.spinner_list_item, fontItems)
        binding.spinnerFontSelect.adapter = arrayAdapter

        // If there is selected font information, set it with setSelection.
        if(viewModel.font.value != null && viewModel.font.value!!.isNotEmpty()){
            binding.spinnerFontSelect.setSelection(
                arrayAdapter.getPosition(
                    EnumFont.valueOf(
                        viewModel.font.value!!
                    )
                )
            )
        }

        // Define a Listener for font selection.
        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)
            }
        }

        // Added functionality to open the font Spinner when the font TextView is clicked.
        binding.fontHint.setOnClickListener { binding.spinnerFontSelect.performClick() }

    }
}

EnumFont.kt

Font names are different for the user interface and the actual font loading, so they are defined using an 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"),
    // omitted
}

Additional Information

Initially, I used the material AutoCompleteTextView as follows:

<com.google.android.material.textfield.TextInputLayout ... omitted ...>
    <AutoCompleteTextView ... omitted ... />
</com.google.android.material.textfield.TextInputLayout>

The reason was that it seemed more appealing than Spinner due to better design, adjustment of the Dropdown's starting position, and height control. However, I switched to Spinner because I couldn't resolve the following issues due to my limited technical knowledge:

  • When a selection was set using setText, the previous Dropdown list disappeared upon rotation, leaving only the selected item.
  • It was cumbersome to specify a separate state to highlight the selected item.