관리 메뉴

나만을 위한 블로그

[Android] 스피너 커스텀하는 방법 본문

Android

[Android] 스피너 커스텀하는 방법

참깨빵위에참깨빵_ 2021. 12. 22. 22:08
728x90
반응형

한 화면에서 여러 데이터 중 하나를 선택할 수 있게 하는 방법 중 하나가 스피너다.

이 스피너를 내가 원하는 모양대로 만들고 싶을 때가 있는데 이 방법이 조금 귀찮아서 기록해두려고 한다.

 

먼저 xml 2개를 만든다. 화면에서 보이는 스피너 안의 텍스트뷰, 스피너를 눌렀을 때 안의 데이터들을 보여줄 텍스트뷰 2개다. 각 파일명은 소스코드 최상단에 써놨다.

이미지뷰 안의 arrow_down은 대충 이미지 주워와서 끼워 넣거나 mipmap 안의 런처 아이콘으로 해도 상관없다.

 

<!-- spinner_outer_view.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:weightSum="1"
    android:orientation="horizontal">

    <!-- 액티비티에서 사용자에게 보여지는 스피너 안의 텍스트뷰. 선택된 상태로 취급받음 -->
    <TextView
        android:id="@+id/spinner_inner_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight=".85"
        android:gravity="center"
        android:paddingStart="13dp"
        android:textColor="#000000"
        android:textSize="15sp" />

    <ImageView
        android:layout_width="0dp"
        android:layout_height="25dp"
        android:layout_weight=".15"
        android:layout_marginStart="5dp"
        android:src="@drawable/arrow_down" />

</LinearLayout>

 

<!-- spinner_inner_view.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <!-- 클릭 시 나타나는 스피너 안의 텍스트뷰들 -->
    <TextView
        android:id="@+id/spinner_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/black"
        android:textSize="16sp" />

</LinearLayout>

 

이제 어댑터를 만든다. CustomSpinnerAdapter라는 클래스를 만들고 아래 소스코드를 복붙한다.

 

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import java.util.List;

public class CustomSpinnerAdapter extends BaseAdapter {

    private final List<String> list;
    private final LayoutInflater inflater;
    private String text;

    public CustomSpinnerAdapter(Context context, List<String> list) {
        this.list = list;
        inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public int getCount() {
        if (list != null)
            return list.size();
        else
            return 0;
    }

    @Override
    public Object getItem(int position) {
        return list.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    // 화면에 들어왔을 때 보여지는 텍스트뷰 설정
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null)
            convertView = inflater.inflate(R.layout.spinner_outer_view, parent, false);
        if (list != null) {
            text = list.get(position);
            ((TextView) convertView.findViewById(R.id.spinner_inner_text)).setText(text);
        }
        return convertView;
    }

    // 클릭 후 나타나는 텍스트뷰 설정
    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        if (convertView == null)
            convertView = inflater.inflate(R.layout.spinner_inner_view, parent, false);
        if (list != null) {
            text = list.get(position);
            ((TextView) convertView.findViewById(R.id.spinner_text)).setText(text);
        }

        return convertView;
    }

    // 스피너에서 선택된 아이템을 액티비티에서 꺼내오는 메서드
    public String getItem() {
        return text;
    }
}

 

맨 마지막의 getItem()은 무조건 구현할 필요 없는 선택사항이다. 저 방법이 아니라도 스피너에서 선택된 아이템을 가져올 방법은 있다.

 

마지막으로 메인 액티비티의 XML과 자바 파일이다.

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:id="@+id/container_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".TestActivity">

    <Spinner
        android:id="@+id/spinner"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Spinner;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import java.util.ArrayList;
import java.util.List;

public class TestActivity extends AppCompatActivity {

    private final String TAG = this.getClass().getSimpleName();

    private List<String> list = new ArrayList<>();
    private Spinner spinner;
    private CustomSpinnerAdapter adapter;
    private String selectedItem;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

        spinner = findViewById(R.id.spinner);

        // 스피너 안에 넣을 데이터 임의 생성
        for (int i = 1; i < 20; i++) {
            list.add("아이템" + i);
        }

        // 스피너에 붙일 어댑터 초기화
        adapter = new CustomSpinnerAdapter(this, list);
        spinner.setAdapter(adapter);

        // 스피너 클릭 리스너
        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                // 어댑터에서 정의한 메서드를 통해 스피너에서 선택한 아이템의 이름을 받아온다
                selectedItem = adapter.getItem();
                Toast.makeText(TestActivity.this, "선택한 아이템 : " + selectedItem, Toast.LENGTH_SHORT).show();
                // 어댑터에서 정의하는 게 귀찮다면 아래처럼 구할 수도 있다
                // getItemAtPosition()의 리턴형은 Object이므로 String 캐스팅이 필요하다
                String otherItem = (String) spinner.getItemAtPosition(position);
                Log.e(TAG, "getItemAtPosition() - 선택한 아이템 : " + otherItem);
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
                //
            }
        });

    }
}

 

이러고 실행하면 아래처럼 동작한다. 로그도 출력되니 에뮬레이터와 로그캣을 같이 보면 된다.

 

 

잘 동작한다. 스피너 오른쪽에 화살표가 2개인 것만 빼고 말이다.

이제 저 기본 화살표를 없애고 내가 넣은 아이콘을 스피너 우측에 보여줄 것이다. 그러기 위해 res/drawable 폴더에 XML 하나를 만들어준다.

 

<!-- spinner_border_layout.xml -->
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners
        android:bottomLeftRadius="20dp"
        android:bottomRightRadius="20dp"
        android:topLeftRadius="20dp"
        android:topRightRadius="20dp" />

    <stroke
        android:width="1dp"
        android:color="#000"/>
</shape>

 

그리고 메인 액티비티의 XML에서 스피너의 속성으로 background 값을 추가한다.

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:id="@+id/container_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".TestActivity">

    <Spinner
        android:id="@+id/spinner"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/spinner_border_layout"/>

</LinearLayout>

 

이대로 빌드하면 끝인가? 아니다. 이 상태로 빌드하면 화면에서 스피너가 안 이쁜 모습으로 보인다.

 

 

글자 위아래로 패딩이 좀 있으면 볼 만해질 것 같다. 그럼 어떤 파일의 어디를 고치면 좋을까?

스피너를 클릭하지 않은 상태에서 보이는 뷰를 고쳐야 하니까 spinner_outer_view 파일을 고치면 될 것이다.

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:weightSum="1"
    android:padding="16dp"
    android:orientation="horizontal">

    <!-- 액티비티에서 사용자에게 보여지는 스피너 안의 텍스트뷰. 선택된 상태로 취급받음 -->
    <TextView
        android:id="@+id/spinner_inner_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight=".85"
        android:gravity="center"
        android:paddingStart="13dp"
        android:textColor="#000000"
        android:textSize="15sp" />

    <ImageView
        android:layout_width="0dp"
        android:layout_height="25dp"
        android:layout_weight=".15"
        android:layout_marginStart="5dp"
        android:src="@drawable/arrow_down" />

</LinearLayout>

 

부모 레이아웃에서 전체 패딩을 16dp만큼 먹였다. 이렇게 하면 아래처럼 보인다.

 

 

이제 이 예제를 바탕으로 본인의 입맛에 맞게 모양, 안에 들어갈 데이터를 바꿔서 적절하게 사용하면 된다.

주의할 것은 내가 임의로 커스텀한 드로어블을 스피너에 적용한 것이기 때문에 스피너를 클릭해도 화살표 주변에서 리플 효과(클릭 시 회색 물결 같은 애니메이션이 발생하는 것)가 발생하지 않는다. 이 현상의 수정 방법까지는 찾아보지 않아서 모른다.

 

 

참고한 사이트)

 

https://onedaycodeing.tistory.com/66

 

안드로이드 스피너 커스텀해보기 (내가 원하는 디자인의 스피너)

저는 스피너 화살표의 간격을 조절하고싶어서 커스텀을 해보게되었습니다. 일단 레이아웃 먼저 만들어 주고 시작할게요. -spinner_getview- <?xml version="1.0" encoding="utf-8"?> -spinner_custom- <?xml versi..

onedaycodeing.tistory.com

 

반응형
Comments