본문 바로가기

Android/Basic

[Android: Basic] Spinner Dropdown

DropDown Spinner

아래의 이미지 처럼 터치했을 때, 값을 사용자가 선택할 수 있도록 목록 형태로 펼쳐지는 것을 드롭다운(Drowdown) 이라고 한다.

안드로이드에서 드롭다운은 스피너(Spinner) 를 이용해 구현할 수 있다.

이번 포스팅에서는 스피너를 구현하는 방법과 커스텀하는 방법에 대해 알아보겠다.

ViewBinding 환경을 고려하고 작성된 글이다.


완성본

아래 과정을 통해 완성될 결과물을 먼저 살펴보자.

간단하게 드롭다운에서 아이템을 선택하면 그에 따라 텍스트가 바뀌는 예제다.
코드 전문은 아래 깃허브 저장소에서 확인 가능하다.
깃허브 링크


💻 Code

‣ Layout resource

<Spinner
    android:id="@+id/my_spinner"
    style="@style/Widget.AppCompat.Spinner"
    android:layout_width="100dp"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:background="@drawable/border_round_5_e5e5e5_arrow"
    android:dropDownWidth="match_parent"
    android:dropDownVerticalOffset="35dp"
    android:popupBackground="@android:color/white" />

 

dropDownWidthdropDownVertivalOffset에 대해서 알아보겠다.


dropDownWidth

  • 스피너의 드롭다운 목록의 너비를 지정
  • 값이 match_parent로 설정되면 드롭다운 목록의 너비는 스피너의 너비와 동일하게 된다.

dropDownVerticalOffset

  • 스피너의 드롭다운 목록이 표시되는 수직 위치를 지정하는 오프셋 값
  • 기본적으로 드롭다운 목록은 스피너 바로 아래에 표시된다.
  • 하지만 dropDownVerticalOffset속성을 사용하여 드롭다운 목록의 시작 위치를 조절할 수 있다.
  • 예를 들어, 위 코드처럼 dropDownVerticalOffset을 35dp로 설정하면 드롭다운 목록은 스피너 바로 아래에서부터 35dp 아래로 내려간 위치에서 시작된다.

개인적인 팁으로, minWidthminHeight를 사용해 일정 크기를 보장해 두면 View가 뭉그러지는 것을 일부 방지할 수 있다.

‣ 테두리 drawable resource

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <corners android:radius="5dp" />
            <solid android:color="@color/white" />
            <stroke
                android:width="1dp"
                android:color="#e5e5e5" />
        </shape>
    </item>
    <item android:right="16dp" android:gravity="center_vertical|end">
        <bitmap android:src="@drawable/btn_arrow_down_black"/>
    </item>
</layer-list>

 

btn_arrow_down_black은 아래 방향을 가르키는 png 이미지 아무거나 넣은 것이다.

대충 이런 모양새를 만들기 위한 코드다.

‣ Adapter 코드

필자는 하단 코드를 onCreate()에 작성했다.

val spinnerAdapter = ArrayAdapter(this, R.layout.simple_spinner_item, genderType)
    spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
    binding.mySpinner.adapter = spinnerAdapter

 

스피너는 데이터를 효율적으로 관리하고 표시하기 위해 어뎁터를 사용한다.

스피너 어뎁터의 주요 역할은 다음과 같다.

데이터 표시:

  • 어뎁터는 데이터 셋에서 개별 데이터 항목을 가져와 스피너에 표시한다.
  • 이를 통해 동적인 데이터 변경에 유연하게 대응할 수 있다.
  • 다양한 데이터 소스 지원:
  • 배열, DB, 구조체 등, 다양한 데이터 소스를 가져와 표시할 수 있다.
  • 위의 예시 코드처럼, ArrayAdapter를 사용하거나 CursorAdapter등을 사용하여 이를 쉽게 구현 가능하다.

‣ 리스너 설정

binding.mySpinner.onItemSelectedListener =
      object : AdapterView.OnItemSelectedListener {
          override fun onItemSelected(
              parent: AdapterView<*>?,
              view: View?,
              position: Int,
              id: Long
          ) {
              val selectedItem = parent?.getItemAtPosition(position) as Gender
              binding.textGender.text = selectedItem.sex
          }

          override fun onNothingSelected(parent: AdapterView<*>?) {
              // empty here
          }
      }

 

스피너 위젯에 아이템 선택 리스너를 설정하는 코드이다.
하나씩 살펴보자.


onItemSelectedListener

  • 스피너에서 아이템이 선택될 때 호출되는 리스너를 설정한다.

object: AdapterView.OnItemSelectedListener

  • 인터페이스를 구현하는 익명 객체를 생성한다.
  • 이 인터페이스는 아이템이 선택되거나 선택이 해제될 때 호출되는 콜백 메서드를 정의한다.

onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Log)

  • 스피너에서 사용자가 아이템을 선택하면 호출되는 메서드
  • parent: 선택된 아이템을 포함하는 AdapterView로, 여기서는 Spinner다.
  • view: 선택된 아이템의 뷰
  • position: 선택된 아이템의 위치(0부터 시작하는 인덱스)
  • id: 선택된 아이템의 ID(일반적으로 데이터의 행 ID를 의미한다)

val selectedItem = parent?.getItemAtPosition(position)

  • 선택된 아이템을 가져온다.

getItemAtPosition(position) 메서드는 주어진 위치의 아이템을 반환한다.

  • 본인은 as T(명시적 형변환)를 통해 본인이 작성한 데이터 클래스인 Gender 타입으로 형 변환을 해줬다.

onNothingSelected(parent: AdapterView<*>?)

  • 선택이 초기화되었 때 호출되는 메서드지만, Spinner에서 사용자가 직접 현재 선택을 초기화하는 UI 옵션이 되지는 않는다.
  • 때문에 사실상 아무런 동작을 하지 않는 코드이다.
  • 그럼에도 불구하고, onNothingSelected()메서드는AdapterView.OnItemSelectedListener 인터페이스의 일부로 정의되어 있기 때문에, 해당 인터페이스를 구현할 때는 이 메서드를 오버라이드해야 한다.
  • 아무튼, 보통은 비워둔다.

마무리

기회가 된다면 Compose로 Spinner를 만드는 방법에 대한 글도 작성하겠다.

그거 만들어보겠다고 반나절 날렸던게 벌써 5개월 전의 일이라니..