안드로이드 리스트뷰 다중 선택 처리하기. (Android ListView Multi Choice)

2016. 4. 25. 13:19


1. ListView 아이템 선택 기능

일반적으로 ListView의 아이템은 텍스트 또는 이미지 등으로 구성된 내용을 표시할 때 많이 사용하지만, 아이템 추가, 수정, 삭제 기능을 통해 데이터의 내용이나 개수를 조절하는 경우에도 자주 사용됩니다. 이 때 ListView의 어떤 아이템이 수정 또는 삭제될 것인지 판단하기 위한 방법이 필요한데, 이를 위해 ListView 아이템에는 "선택 기능"이 사용됩니다.


ListView의 아이템이 "선택 기능"을 가지도록 만들기 위해서는 ListView의 "choiceMode" 속성을 사용합니다. "choiceMode" 속성에 어떤 값을 지정하느냐에 따라 단일 선택 또는 다중 선택 모드가 결정되는데, 단일 선택 모드를 사용할 때는 "singleChoice", 다중 선택 모드를 사용할 때는 "multipleChoice" 값을 지정합니다.
("singleChoice"에 대한 간단한 예제는 [안드로이드 리스트뷰 아이템 추가,수정,삭제] 내용을 참고하세요.)


단일 선택 모드와 다중 선택 모드에서의 구현 작업은 크게 차이나지 않습니다. 하나의 선택 아이템을 다루는지, 여러 선택 아이템을 루프를 돌며 처리하는지 정도의 차이가 있을 뿐입니다.


오히려 구현 과정에서의 복잡함은 선택 기능을 지원하는 Custom ListView를 만드는 데 있을 수 있습니다. 이와 관련된 내용은 별도의 주제로 다루겠습니다.


그럼 지금부터 다중 선택을 지원하는 ListView를 만드는 방법에 대해 알아보겠습니다.


2. 기본적인 아이템 다중 선택 기능 구현 방법

ListView의 "choiceMode" 속성을 사용한 아이템 다중 선택 기능 구현 방법을 예제를 통해 알아보도록 하겠습니다.


예제에서는 다중 선택이 가능한 ListView 및 아이템 추가를 위한 Button과 선택된 아이템을 삭제하기 위한 Button, 그리고 전체 아이템을 선택하기 위한 Button으로 구성됩니다.



안드로이드 다중 선택 리스트뷰 아이템 레이아웃


2.1 ListView 및 Button 추가.

늘 그렇듯이 ListView와 Button들이 추가된 Layout 리소스 XML을 작성합니다. ListView는 다중 선택이 가능해야 하므로, "choiceMode" 속성에 "multipleChoice" 값을 지정합니다.

[STEP-1] "activity_main.xml" - MainActivity에 ListView와 Button 추가.
<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.example.madwin.listviewitemcontrolexample1.MainActivity"
    tools:showIn="@layout/activity_main"
    android:orientation="vertical">

    <ListView
        android:id="@+id/listview1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:choiceMode="multipleChoice" />

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

        <Button
            android:id="@+id/add"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Add" />

        <Button
            android:id="@+id/delete"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Delete" />

        <Button
            android:id="@+id/selectAll"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Select All" />

    </LinearLayout>

</LinearLayout>

2.2 ListView, ArrayList 및 Adapter 생성

MainActivity의 onCreate() 함수에서 ListView, ArrayList, Adapter를 생성합니다.

[STEP-2] "MainActivity.java" - onCreate() 함수에서 ListView, ArrayList, Adapter 생성.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ... 코드 계속

        // 빈 데이터 리스트 생성.
        final ArrayList<String> items = new ArrayList<String>() ;
        // ArrayAdapter 생성. 아이템 View를 선택(multiple choice)가능하도록 만듦.
        final ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_multiple_choice, items) ;

        // listview 생성 및 adapter 지정.
        final ListView listview = (ListView) findViewById(R.id.listview1) ;
        listview.setAdapter(adapter) ;

        // 코드 계속 ...
    }
}

위의 코드에서 Adatper에 전달되는 Layout으로 "android.R.layout.simple_list_item_multiple_choice"가 사용된 것을 주목하세요. 앞에서 잠깐 언급했듯이 본 예제에서는 ListView 아이템 Layout을 만들지 않습니다. 대신 안드로이드 SDK에서 제공되는 Layout을 사용하는거죠. 즉, "android.R.layout.simple_list_item_multiple_choice"가 안드로이드 SDK에서 기본적으로 제공되는 Layout입니다.


"android.R.layout.simple_list_item_multiple_choice"는 TextView와 CheckBox 위젯이 배치되어 있는 Layout입니다. [안드로이드 리스트뷰 아이템 추가,수정,삭제]에서 단일 선택 모드 예제를 작성하면서 사용했던 "android.R.layout.simple_list_item_single_choice"는 TextView와 RadioButton으로 만들어져 있었죠. 둘 다 "선택" 기능을 위해 만들어졌지만 다중 선택 가능 여부에 따라 CheckBox를 사용하는지, RadioButton을 사용하는지가 차이가 나는 것입니다.


2.3 "Add" Button 클릭 시 아이템 추가.

예제에 따라 "Add" Button을 클릭하면, 새로운 아이템을 추가하도록 구현합니다.

[STEP-3] "MainActivity.java" - "Add" Button에 대한 핸들러 작성.
        // add button에 대한 이벤트 처리.
        Button addButton = (Button)findViewById(R.id.add) ;
        addButton.setOnClickListener(new Button.OnClickListener() {
            public void onClick(View v) {
                int count;
                count = adapter.getCount();

                // 아이템 추가.
                items.add("LIST" + Integer.toString(count + 1));

                // listview 갱신
                adapter.notifyDataSetChanged();
            }
        }) ;

2.4 "Delete" Button 클릭 시 선택 아이템 삭제.

"Delete" Button을 누르면 아이템을 삭제하도록 구현합니다. 단일 선택 모드를 사용한 [안드로이드 리스트뷰 아이템 추가,수정,삭제]에서 아이템을 삭제할 때, ListView로부터 선택(Checked)된 아이템 위치를 getCheckedItemPosition() 함수를 사용하여 가져왔는데, 다중 선택 모드에서는 조금 다른 방법을 사용합니다.

[STEP-4] "MainActivity.java" - "Delete" Button 클릭 시 선택(Checked)된 아이템 삭제.
        // delete button에 대한 이벤트 처리.
        Button deleteButton = (Button)findViewById(R.id.delete) ;
        deleteButton.setOnClickListener(new Button.OnClickListener() {
            public void onClick(View v) {
                SparseBooleanArray checkedItems = listview.getCheckedItemPositions();
                int count = adapter.getCount() ;

                for (int i = count-1; i >= 0; i--) {
                    if (checkedItems.get(i)) {
                        items.remove(i) ;
                    }
                }

                // 모든 선택 상태 초기화.
                listview.clearChoices() ;

                adapter.notifyDataSetChanged();
            }
        }) ;

먼저 현재 선택된 아이템들을 가져오기 위해 getCheckedItemPositions() 함수를 사용합니다. 단일 선택일 때 사용했던 getCheckedItemPosition() 함수 이름에 "s"가 더해져있죠. 물론 함수의 리턴 타입도 다릅니다.


getCheckedItemPositions()에서 리턴되는 SparseBooleanArray는 정수 값들을 boolean 값들로 매핑시키는 기능을 하는 클래스입니다. 여기서 정수 값은 ListView 아이템의 위치(position)로 사용하고, boolean 값은 선택(Checked) 여부로 사용됩니다.
SparseBooleanArray의 get() 함수를 사용하면 인자로 입력되는 position의 상태가 Checked인지 확인할 수 있습니다.


그리고 선택된 아이템을 remove() 함수를 사용하여 삭제한 다음에 clearChoices() 함수를 호출한 것을 주의하세요. 보통 Adapter로 입력된 데이터를 삭제하고 notifyDataSetChanged()함수를 호출하면 선택(Checked) 상태까지 초기화될 것이라 생각되어질 수 있습니다. 하지만 그렇지 않죠. (위의 코드에서 "listview.clearChoices() ;" 문장을 주석처리하면 그 결과를 확인할 수 있습니다.)


아이템의 데이터와 화면에 표시되는 View를 연결해주는 역할을 해주는 것이 분명 Adapter의 역할이긴 하지만, 선택(Checked) 상태는 ListView 자체가 유지하고 있습니다. 그러므로 아이템을 삭제하고 Adapter의 notifyDataSetChanged()를 호출한다고 해도 유효한 position의 선택(Checked) 상태를 그대로 유지하고 있는 것입니다.


마지막으로 위의 코드에서 아이템을 삭제하기 위해 for 루프를 (count-1) 부터 감소시켜가며 실행한 것을 주의하세요. remove() 함수를 호출하면 아이템이 삭제되면서 삭제 위치의 뒤에 있던 아이템들의 position이 앞으로 이동하게 되고 아이템의 갯수가 줄어듭니다. 그러면 당연히 의도하지 않은 아이템이 삭제되거나, for 루프가 줄어든 아이템 갯수보다 많이 실행되는 문제가 발생합니다. 리스트 자료 구조에서 데이터를 삭제할 땐 "마지막 아이템부터" 루프를 실행해야 한다는 것을 꼭 명심하시기 바랍니다.

2.5 "Select All" Button 클릭 시 모든 아이템 선택.

"Select All" Button을 클릭하면 ListView의 모든 아이템을 선택합니다. 아이템 선택을 위해 호출하는 함수는 ListView의 setItemChecked() 함수입니다. setItemChecked() 함수는 입력받은 position에 대해서만 선택 여부를 설정하므로, 현재 Adapter에 지정된 모든 아이템에 대해 루프를 돌며 호출해줘야 합니다.

[STEP-5] "MainActivity.java" - "Select All" Button 클릭 시 모든 아이템 선택.
    // selectAll button에 대한 이벤트 처리.
    Button selectAllButton = (Button)findViewById(R.id.selectAll) ;
    selectAllButton.setOnClickListener(new Button.OnClickListener() {
        public void onClick(View v) {
            int count = 0 ;
            count = adapter.getCount() ;

            for (int i=0; i<count; i++) {
                listview.setItemChecked(i, true) ;
            }
        }
    }) ;

2.5 예제 실행 화면.

예제 코드를 모두 작성하고 실행하면 아래와 같은 실행 화면을 확인할 수 있습니다.


안드로이드 다중 선택 리스트뷰 예제 실행 화면


각 버튼 클릭 시 동작은 다음과 같습니다.


3. 참고.

.END.


ANDROID 프로그래밍/LISTVIEW