안드로이드 리스트뷰 아이템 정렬하기 2. (Android ListView Item Sorting 2) [Example]

2016. 9. 19. 15:08


1. ListView 아이템 다루기

초보 개발자들이, 처음 안드로이드 ListView를 다룰 때 어려워하는 것 중 하나가 바로, Adapter의 개념 및 사용 방법에 대한 이해입니다. TextView, ImageView, Button 등을 사용할 때 경험할 수 있었던 "구현의 단순함"이나, "실행 결과의 직관성" 같은 것들이, ListView의 Adapter를 접하는 순간, "익숙하지 않은 생소함" 또는 "구조의 복잡성"으로 바뀌어 버리니까요.


하지만 대부분의 개발 관련 지식과 기술이 그러하듯, Adapter 또한, 지속적으로 학습하고 반복적으로 사용하는 과정을 거치면, 금방 능숙하게 사용할 수 있게 됩니다. 게다가 Adapter를 사용함으로써 누릴 수 있는 여러 장점들은, 개발에 소요되는 노력과 시간을 상당 부분 줄일 수 있게 만들어주죠.


그러한 Adapter의 장점을 크게 누릴 수 있는 경우가 바로, ListView의 아이템을 정렬하는 기능을 구현하는 경우입니다.
Adapter를 중심으로, 내부적으로 관리되는 데이터와 화면에 표시되는 View 영역이 분리됨으로써, 아이템 정렬에 필요한 코드의 양이나, 고려되어야 할 예외처리들이 줄어드는 효과를 볼 수 있게 되는 것이죠.


이러한 Adapter의 장점에 더하여 [안드로이드 리스트뷰 아이템 정렬하기 1]에서 살펴본 것과 같이, Comparator 인터페이스와 Collections 클래스의 조합으로 구현할 수 있는 리스트 정렬 기능은 ListView 아이템을 정렬하는 과정을 한층 더 간편하게 만들어 줍니다. 데이터 정렬을 위한 루프 실행이나 복잡한 알고리즘에 대한 고민없이, 데이터 순서의 기준이 되는 조건을 기술하는 것만으로 데이터가 정렬된 리스트를 획득할 수 있으니까요.


이제, [안드로이드 리스트뷰 아이템 정렬하기 1]에서 설명한 내용을 바탕으로, ListView의 아이템을 정렬하는 예제를 작성해보도록 하겠습니다.

2. ListView 아이템 정렬하기.

예제의 Layout은 두 개의 TextView로 구성되는 Custom ListView와 각 TextView를 오름차순, 내림차순으로 정렬하는 네 개의 Button들을 포함합니다.

리스트뷰 정렬 예제 구성도


2.1 워크플로우

리스트뷰 아이템 정렬 워크플로우


2.2 MainActivity의 Layout 구성.

앞서 설계한 화면 구성 내용에 맞게 MainActivity의 Layout XML을 "activity_main.xml" 파일(또는 "content_main.xml")에 작성합니다.

[STEP-1] "activity_main.xml" - MainActivity의 Layout 구성.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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.recipes4dev.examples.listviewitemsorting.MainActivity"
    tools:showIn="@layout/activity_main">

    <ListView
        android:id="@+id/listview1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true">

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/buttonNoAsc"
            android:text="No ASC"/>

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/buttonNoDesc"
            android:text="No DESC"/>

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/buttonTextAsc"
            android:text="Text ASC"/>

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/buttonTextDesc"
            android:text="Text DESC"/>

    </LinearLayout>

</RelativeLayout>

2.3 ListView 아이템에 대한 Layout 구성.

다음으로, Custom ListView를 만들 때 필요한, ListView 아이템의 Layout 구성 작업을 수행합니다. 아이템의 Layout은 "listview_item.xml" 이라는 이름으로 작성합니다.

[STEP-2] "listview_item.xml" - ListView 아이템 Layout 구성.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:textSize="24sp"
        android:gravity="center"
        android:layout_weight="1"
        android:id="@+id/textViewNo"/>

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:textSize="24sp"
        android:layout_weight="4"
        android:id="@+id/textViewText"/>

</LinearLayout>

2.4 ListView 아이템 데이터 클래스 정의.

ListView 아이템을 위한 클래스를 정의합니다. 아이템은 "No"와 "Text"라는 두 개의 TextView로 구성되어 있지만, "No"는 숫자 값을 사용하여 표현할 것이므로 클래스에는 int와 String 변수 하나씩 정의합니다.

[STEP-3] "ListViewItem.java" - ListView 아이템 데이터 클래스 정의.
public class ListViewItem {
    private int no ;
    private String text ;

    public void setNo(int no) {
        this.no = no ;
    }
    public void setText(String text) {
        this.text = text ;
    }

    public int getNo() {
        return no ;
    }
    public String getText() {
        return text ;
    }
}

2.5 ListView Adapter 구현.

이전에 작성했던 Custom ListView 예제들과 마찬가지로, 위에서 정의한 아이템 데이터와 View를 연결하는 Adapter를 구현합니다.

[STEP-4] "ListViewAdapter.java" - Custom Adapter 구현.
public class ListViewAdapter extends BaseAdapter {
    // Adapter에 추가된 데이터를 저장하기 위한 ArrayList
    private ArrayList<ListViewItem> listViewItemList ;

    // ListViewAdapter의 생성자
    public ListViewAdapter(ArrayList<ListViewItem> itemList) {
        if (itemList == null) {
            listViewItemList = new ArrayList<ListViewItem>() ;
        } else {
            listViewItemList = itemList ;
        }
    }

    // Adapter에 사용되는 데이터의 개수를 리턴. : 필수 구현
    @Override
    public int getCount() {
        return listViewItemList.size() ;
    }

    // position에 위치한 데이터를 화면에 출력하는데 사용될 View를 리턴. : 필수 구현
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final int pos = position;
        final Context context = parent.getContext();

        // "listview_item" Layout을 inflate하여 convertView 참조 획득.
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.listview_item, parent, false);
        }

        // 화면에 표시될 View(Layout이 inflate된)으로부터 위젯에 대한 참조 획득
        TextView noTextView = (TextView) convertView.findViewById(R.id.textViewNo) ;
        TextView textTextView = (TextView) convertView.findViewById(R.id.textViewText) ;

        // Data Set(listViewItemList)에서 position에 위치한 데이터 참조 획득
        ListViewItem listViewItem = listViewItemList.get(position);

        // 아이템 내 각 위젯에 데이터 반영
        noTextView.setText(Integer.toString(listViewItem.getNo()));
        textTextView.setText(listViewItem.getText());

        return convertView;
    }

    // 지정한 위치(position)에 있는 데이터와 관계된 아이템(row)의 ID를 리턴. : 필수 구현
    @Override
    public long getItemId(int position) {
        return position ;
    }

    // 지정한 위치(position)에 있는 데이터 리턴 : 필수 구현
    @Override
    public Object getItem(int position) {
        return listViewItemList.get(position) ;
    }

    // 아이템 데이터 추가를 위한 함수. 개발자가 원하는대로 작성 가능.
    public void addItem(int no, String text) {
        ListViewItem item = new ListViewItem();

        item.setNo(no);
        item.setText(text);

        listViewItemList.add(item);
    }

    public ArrayList<ListViewItem> getItemList() {
        return listViewItemList ;
    }
}

2.6 Adapter 생성 후 ListView에 연결.

Adapter를 구현했으니, Adapter 객체를 하나 생성하고 ListView에 지정하는 코드를 작성합니다.

[STEP-5] "MainActivity.java" - onCreate() 함수에서 Adapter 생성 후 ListView에 연결.
public class MainActivity extends AppCompatActivity {
    ListView listview ;
    ListViewAdapter adapter;

    ArrayList<ListViewItem> itemList = new ArrayList<ListViewItem>() ;

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

        // Adapter 생성
        adapter = new ListViewAdapter(itemList) ;

        // 리스트뷰 참조 및 Adapter달기
        listview = (ListView) findViewById(R.id.listview1);
        listview.setAdapter(adapter);

        // 코드 계속 ...
    }
}

2.7 데이터 추가.

이제, ListView 아이템 데이터를 추가합니다. 정렬된 결과와 식별이 잘 되도록, 초기 입력 데이터는 순서가 섞이도록 입력합니다.

[STEP-6] "MainActivity.java" - 데이터 추가.
public class MainActivity extends AppCompatActivity {

    // ... 코드 계속

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        // ... 코드 계속

        // 아이템 추가.
        adapter.addItem(4, "John Smith") ;
        adapter.addItem(2, "Micheal Jacson") ;
        adapter.addItem(3, "Eric Clapton") ;
        adapter.addItem(1, "Beatles") ;
        adapter.addItem(5, "Queen") ;
    }
}

2.8 Button 이벤트에 따라 정렬 기능 구현.

마지막으로, 이 글의 핵심인 ListView 아이템을 정렬하는 코드를 작성하겠습니다.
예제 Layout 구성도에서 본 대로, Button들이 클릭될 때, No 및 Text 항목에 대해 오름차순 또는 내림차순으로 정렬을 수행하도록 만듭니다.


먼저, "No ASC" Button 클릭 시, "No" 항목에 대해 오름차순으로 정렬되도록 작성한 코드입니다. 낮은 숫자(1)부터 높은 숫자(5) 순으로 화면에 표시됩니다.

[STEP-7.1] "MainActivity.java" - "No ASC" 클릭 시, "No" 항목에 대한 오름차순 정렬.
        Button buttonNoAsc = (Button) findViewById(R.id.buttonNoAsc) ;
        buttonNoAsc.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View v) {
                Comparator<ListViewItem> noAsc = new Comparator<ListViewItem>() {
                    @Override
                    public int compare(ListViewItem item1, ListViewItem item2) {
                        int ret ;

                        if (item1.getNo() < item2.getNo())
                            ret = -1 ;
                        else if (item1.getNo() == item2.getNo())
                            ret = 0 ;
                        else
                            ret = 1 ;

                        return ret ;

                        // 위의 코드를 간단히 만드는 방법.
                        // return (item1.getNo() - item2.getNo()) ;
                    }
                } ;

                Collections.sort(itemList, noAsc) ;
                adapter.notifyDataSetChanged() ;
            }
        });

다음은 "No DESC" Button 클릭 시, "No" 항목에 대해 내림차순으로 정렬되도록 작성한 코드입니다. 높은 숫자(5)부터 낮은 숫자(1) 순으로 화면에 표시됩니다.

[STEP-7.2] "MainActivity.java" - "No DESC" 클릭 시, "No" 항목에 대한 내림차순 정렬.
        Button buttonNoDesc = (Button) findViewById(R.id.buttonNoDesc) ;
        buttonNoDesc.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View v) {
                Comparator<ListViewItem> noDesc = new Comparator<ListViewItem>() {
                    @Override
                    public int compare(ListViewItem item1, ListViewItem item2) {
                        int ret = 0 ;

                        if (item1.getNo() < item2.getNo())
                            ret = 1 ;
                        else if (item1.getNo() == item2.getNo())
                            ret = 0 ;
                        else
                            ret = -1 ;

                        return ret ;

                        // 위의 코드를 간단히 만드는 방법.
                        // return (item2.getNo() - item1.getNo()) ;
                    }
                } ;

                Collections.sort(itemList, noDesc) ;
                adapter.notifyDataSetChanged() ;
            }
        });

이번엔 문자열 기준으로 정렬하는 코드를 살펴 보도록 하겠습니다. "Text ASC" Button 클릭 시, 알파벳 순으로 오름차순 정렬을 하도록 작성한 코드입니다. 순서 상 앞에 위치하는 알파벳('B')부터 뒤에 위치하는 알파벳('Q') 순으로 화면에 정렬됩니다.

[STEP-7.3] "MainActivity.java" - "Text ASC" 클릭 시, "Text" 항목에 대한 오름차순 정렬.

Button buttonTextAsc = (Button) findViewById(R.id.buttonTextAsc) ; buttonTextAsc.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { Comparator<ListViewItem> textAsc = new Comparator<ListViewItem>() { @Override public int compare(ListViewItem item1, ListViewItem item2) { return item1.getText().compareTo(item2.getText()) ; // 아래와 같은 코드 /* int ret ; if (item1.getText().compareTo(item2.getText()) < 0) // item1이 작은 경우, ret = -1 ; else if (item1.getText().compareTo(item2.getText()) == 0) ret = 0 ; else // item1이 큰 경우, ret = 1 ; return ret ; */ } } ; Collections.sort(itemList, textAsc) ; adapter.notifyDataSetChanged() ; } });

아래는 "Text DESC" Button 클릭 시, "Text" 항목에 대해 내림차순으로 정렬한 코드입니다.

[STEP-7.4] "MainActivity.java" - "Text DESC" 클릭 시, "Text" 항목에 대한 내림차순 정렬.
        Button buttonTextDesc = (Button) findViewById(R.id.buttonTextDesc) ;
        buttonTextDesc.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View v) {
                Comparator<ListViewItem> textDesc = new Comparator<ListViewItem>() {
                    @Override
                    public int compare(ListViewItem item1, ListViewItem item2) {
                        return item2.getText().compareTo(item1.getText()) ;
                        // 아래와 같은 코드
                        /*
                        int ret ;

                        if (item1.getText().compareTo(item2.getText()) < 0)      // item1이 작은 경우,
                            ret = 1 ;
                        else if (item1.getText().compareTo(item2.getText()) == 0)
                            ret = 0 ;
                        else                                        // item1이 큰 경우,
                            ret = -1 ;

                        return ret ;
                        */
                    }
                } ;

                Collections.sort(itemList, textDesc) ;
                adapter.notifyDataSetChanged() ;
            }
        });

3. 예제 실행 화면.

예제를 작성하고 실행하면, 다음과 같은 실행 화면이 출력됩니다.

리스트뷰 정렬 예제 실행 화면


"No ASC"와 "No DESC" 버튼을 눌렀을 때, 각각 "No"에 대한 오름차순 및 내림차순으로 정렬된 결과는 아래와 같습니다.

리스트뷰 정수 타입 정렬


다음은 "Text ASC", "Text DESC" 버튼을 눌렀을 때, "Text" 값에 대한 오름차순, 내림차순 정렬 결과입니다.

리스트뷰 문자열 타입 정렬


4. 두 가지 이상의 항목을 기준으로 아이템 정렬하기.

위에서 살펴본 예제에서는 ListView 아이템에서 "No"와 "Text" 중 한 가지 항목에 대해서만 정렬하는 코드를 작성하였습니다. 그런데 만약 "No" 항목에서 중복된 값이 존재한다면 어떻게 해야 할까요?

리스트뷰 항목의 중복


물론 예제처럼, compare()함수에서 0을 리턴함으로써 아이템 위치의 "재배치"를 수행하지 않는 방법을 선택할 수도 있습니다. 하지만 어떤 상황에서는, "No" 항목이 같으면 그 다음 "Text" 항목을 비교해서라도 아이템을 정렬해야만 하는 경우도 있죠.


이런 경우, compare() 함수에서 두 가지 항목에 대한 비교를 수행하여 아이템의 순서를 결정해주면 됩니다.

    "No"와 "Text" 두 가지 항목 비교
    Comparator<ListViewItem> noAscTextAsc = new Comparator<ListViewItem>() {
        @Override
        public int compare(ListViewItem item1, ListViewItem item2) {
            int ret ;

            if (item1.getNo() < item2.getNo()) {
                ret = -1;
            } else if (item1.getNo() == item2.getNo()) {
                ret = item1.getText().compareTo(item2.getText()) ;
            } else {
                ret = 1;
            }

            return ret ;
        }
    } ;

    Collections.sort(itemList, noAscTextAsc) ;
    adapter.notifyDataSetChanged() ;

리스트뷰 두 가지 항목 비교 결과


5. 참고.


ANDROID 프로그래밍/LISTVIEW