안드로이드 리스트뷰 관련하여 잘 정리되어있는 문서를 발견하여 저도 공부할겸 번역해보았습니다.


안드로이드 프로그래밍에서 리스트뷰를 처음 사용해보시게 된 분이라면 천천히 읽어보는것도 괜찮을것 같습니다!


각주를 달정도의 지식은 없기에ㅜㅜ 그냥 직역하였습니다. 번역이 어색하거나 틀린부분이 혹시 있다면 알려주시면 감사하겠습니다 :)

(+ 하다보니 번역 어색한 부분이 은근히 많은 것 같네요ㅜ 완전히 틀렸거나 이건 정말 아니다 싶은 부분이 있다면 알려주세요!)


referenced

Using lists in Android with listview - tutorial

Lars Vogel, (c) 2010,20176 vogella GmbH- version 6.2(09.11.2016)

url : http://www.vogella.com/tutorials/AndroidListView/article.html





[목차]

1. 안드로이드에서 리스트 활용하기

2. 안드로이드와 ListView 위젯

3. 디폴트 어댑터

4. 커스텀 어댑터 구현하기

5. ListActivity 와 ListFragment

6. 연습: ListView 와 ListActivity 를 사용하는 예제

7. 연습: 커스텀 레이아웃을 사용하는 ListActivity 예제

8. 튜토리얼: 커스텀 어댑터 만들기 예제

9. ListView와 그 성능

10. ListView 기기화면에서 선택된 뷰 알아내기


[안드로이드에서 리스트뷰 활용]



1. 안드로이드에서 리스트 활용하기


리스트를 사용하여 원소값들을 보여주는 방법은 모바일앱에서 흔히 사용되는 방식입니다.

사용자는 아이템 리스트를 보고 위아래로 스크롤도 할 수 있습니다.(밑의 사진처럼)


리스트의 각 아이템들은 선택될 수 있습니다. 선택 시, 툴바를 업데이트 시킬 수도 있고 상세설명을 위한 새로운 화면으로 넘어갈 수도 있습니다.

다음 사진이 이를 나타냅니다(아이템 하나를 선택하면, 그에 해당하는 새로운 activity가 시작)





2. 안드로이드와 ListView 위젯


(1) 리스트를 위한 뷰


안드로이드는 스크롤 기능을 제공하는 리스트형식을 위해 ListViewExpandableListView 두 개의 클래스를 제공합니다.

ExpandableListView 는 아이템을 그룹핑할 수 있는 기능을 지원합니다.


(2) 리스트에 사용가능한 인풋타입


리스트에 넣을 수 있는 타입은 임의의 자바 오브젝트입니다. 어댑터(adapter)를 활용하여 임의의 데이터 오브젝트로부터 올바른 데이터값을 추출하고,

이를 ListView의 열들에 해당하는 각각의 view에 값을 넣어줍니다.


이 각각의 아이템들은 리스트의 '데이터 모델'로 불립니다. 어댑터는 data를 인풋으로 받아들입니다.



(3) 어댑터(adapters)


어댑터 데이터 모델들을 관리하고, 위젯의 각각의 엔트리에 값을 적용시킵니다. 어댑터는 BaseAdapter클래스를 상속받습니다.


위젯에서 리스트의 각 열에 해당하는 레이아웃은 원하는대로 자유롭게 구성할 수 있습니다.


위와같은 구성을 위한 레이아웃 xml 파일은 다음과 같이 구성될 수 있습니다.


    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:padding="6dip" >
<ImageView android:id="@+id/icon" android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_alignParentBottom="true" android:layout_alignParentTop="true" android:layout_marginRight="6dip" android:contentDescription="TODO" android:src="@drawable/ic_launcher" /> <TextView android:id="@+id/secondLine" android:layout_width="fill_parent" android:layout_height="26dip" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:layout_toRightOf="@id/icon" android:ellipsize="marquee" android:maxLines="1" android:text="Description" android:textSize="12sp" /> <TextView android:id="@+id/firstLine" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_above="@id/secondLine" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:layout_alignWithParentIfMissing="true" android:layout_toRightOf="@id/icon" android:gravity="center_vertical" android:text="Example application" android:textSize="16sp" /> </RelativeLayout>


어댑터는 getView() 메소드를 통하여 레이아웃을 부풀리고(inflate) 각 열에 data를 배정합니다.

또한 이 어댑터는 setAdapter() 메소드를 통하여 ListView에 적용됩니다.


(4) 필터링 및 정렬


데이터의 필터링 및 정렬은 어댑터를 통하여 할 수 있습니다. (커스텀)어댑터 구현 시 기능에 맞는 로직을 넣으면 됩니다.



(5) 데이터 업데이트


데이터에 변동사항이 있거나 새로운 데이터가 사용가능해지면 어댑터의 notifyDataSetInvalidated() 메소드가 호출됩니다.

notifyDataSetInvalidated() 메소드는 데이터가 사용불가능 해졌을때 호출됩니다.


 

(6) 리스너(Listener)


리스트의 각 요소들을 선택했을때의 이벤트 처리를 위하여, ListViewOnItemClickListener() 를 설정합니다.


listView.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view,
        int position, long id) {
        Toast.makeText(getApplicationContext(),
            "Click ListItem Number " + position, Toast.LENGTH_LONG)
            .show();
    }
});







3. 디폴트 어댑터



(1) 디폴트 플랫폼 어댑터


안드로이드는 기능이 구현된(implemented) 디폴트 어댑터를 제공합니다. 가장 흔히 쓰이는 두가지는 ArrayAdapterCursorAdapter 으로, 

ArrayAdapter는 배열 또는 java.util.List에 기반한 데이터 처리에 용이하고,  CursorAdapter는 데이터베이스와 관련된 데이터 처리에 적합합니다.



(2) ArrayAdapter 사용하기


ArrayAdapter는 자바 오브젝트로 이루어진 리스트나 배열을 인풋으로 받아 처리합니다. 각각의 자바 오브젝트는 ListView에서의 하나의 열에 매핑됩니다.

디폴트로 오브젝트의 toString()메소드를 호출하여 결과값을 각 열에 넣습니다.


ArrayAdapter의 생성자를 통하여 view의 ID를 지정해줄 수 있는데, 설정하지 않는다면 기본값으로 android.R.id.text1 이 사용됩니다.


ArrayAdapter 클래스는 clear() 메소드를 제공하여, 모든 내용을 제거할 수 있도록 도와줍니다. 

그 후, add()를 통하여 새로운 원소값을 추가하거나, addAll()를 통하여 원소들의 집합 Collection을 추가 할 수 있습니다.


또한, 사용자가 직접 데이터를 수정한 뒤, notifyDataSetChanged() 를 호출하여 어댑터에게 데이터의 변동사항을 알릴 수 있습니다.




(3) ArrayAdapter를 사용한 예제


* ListView를 사용하고 있는 activity_listviewexampleactivity.xml 레이아웃 파일


<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/listview"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />


* 다음의 example은 activity에서 ListView를 활용하는 예를 보여줍니다.

  각 행의 layout으로는 안드로이드 플랫폼 디폴트 레이아웃을 사용하고, 리스트에서 아이템의 삭제하는 방법도 보여줍니다.


package com.vogella.android.listview.withanimation;
public class ListViewExampleActivity extends Activity {

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

        final ListView listview = (ListView) findViewById(R.id.listview);
        String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
                "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
                "Linux", "OS/2", "Ubuntu", "Windows7", "Max OS X", "Linux",
                "OS/2", "Ubuntu", "Windows7", "Max OS X", "Linux", "OS/2",
                "Android", "iPhone", "WindowsMobile" };

        final ArrayList<String> list = new ArrayList<String>();
        for (int i = 0; i < values.length; ++i) {
            list.add(values[i]);
        }
        final StableArrayAdapter adapter = new StableArrayAdapter(this,
                android.R.layout.simple_list_item_1, list);
        listview.setAdapter(adapter);

        listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, final View view,
                    int position, long id) {
                final String item = (String) parent.getItemAtPosition(position);
                view.animate().setDuration(2000).alpha(0)
                        .withEndAction(new Runnable() {
                            @Override
                            public void run() {
                                list.remove(item);
                                adapter.notifyDataSetChanged();
                                view.setAlpha(1);
                            }
                        });
            }

        });
    }

    private class StableArrayAdapter extends ArrayAdapter<String> {

        HashMap<String, Integer> mIdMap = new HashMap<String, Integer>();

        public StableArrayAdapter(Context context, int textViewResourceId,
                List<String> objects) {
            super(context, textViewResourceId, objects);
            for (int i = 0; i < objects.size(); ++i) {
                mIdMap.put(objects.get(i), i);
            }
        }

        @Override
        public long getItemId(int position) {
            String item = getItem(position);
            return mIdMap.get(item);
        }

        @Override
        public boolean hasStableIds() {
            return true;
        }

    }

}






4. 커스텀 어댑터 구현하기



(1) 커스텀 어댑터 만들기


ArrayAdapter는 오직 toString() 만을 사용하여 row 레이아웃의 단 한개의 뷰에만 매핑이 가능하므로 사용이 제한적입니다.

데이터 배정(assignment)와 여러 개의 뷰를 지원하기 위해서는 직접 커스텀 어댑터를 구현하여 만들어야합니다.


이를 위해서는, 기존의 구현된 어댑터(adapter implementaion), 또는 그냥 단순히 BaseAdapter 클래스를 extend(상속)하여 사용하면 됩니다.

(* 보통은 BaseAdapter를 직접 상속받기보다는 ArrayAdapter를 상속받아 사용하는 것이 더 일반적입니다)



(2) 리스트를 위한 열(row) 준비하기


어댑터에서 리스트의 각 열을 위한 레이아웃을 만들어야합니다.

ListView 객체는 어댑터의 getView() 메소드를 각각의 데이터 요소들에 대하여 호출합니다.

이 메소드에서 어댑터는 row(열) 레이아웃을 만들고, 각각의 레이아웃 뷰(열)에 데이터를 매핑해줍니다.


이 레이아웃은 일반적으로 ViewGroup(레이아웃 매니저)을 근간으로 하고, 여러가지 종류의 뷰를 포함시킬 수 있습니다.(예- ImageView, TextView...)


다음의 사진은 홀수번째, 짝수번째 열에 대해 각각 다른 레이아웃을 적용시킨 리스트를 나타냅니다.


getView() 메소드 내에서, XML기반의 레이아웃을 inflate하고, 하나의 행 내의 여러 개의 독립적인 view들 각각에게 내용을 지정해줍니다.

XML 레이아웃 파일을 inflate 하기위하여 LayoutInflator 시스템 서비스를 활용할 수 있습니다.

(* 이 LayoutInflator 서비스는 getLayoutInflator() 메소드, 또는 context.getSystemService(Context.LAYOUT_INFLATOR_SERVICE) 메소드 콜을 통하여 접근 할 수 있습니다.)


어댑터가 레이아웃을 inflate한 이후, 레이아웃의 관련있는 뷰들을 찾아서, 데이터로 채웁니다. 레이아웃의 개별적인 요소들은 최상위레벨 뷰에서의  findViewById() 메소드 호출을 통해 접근할 수 있습니다.



(3) 커스텀 어댑터를 사용하는 예제코드


다음의 코드는 커스텀 어댑터의 구현을 보여줍니다. 이 어댑터는 res/drawable 디렉토리 내에 두 개의 png file을 가지고 있다는 가정하에 실행됩니다.(no.png, ok.png)

이 코드는 XML레이아웃 파일을 inflate하고, 관련있는 뷰를 찾은 후, 인풋 데이터에 기반하여 각 뷰에 해당하는 내용을 넣어줍니다.


package de.vogella.android.listactivity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class MySimpleArrayAdapter extends ArrayAdapter<String> {
    private final Context context;
    private final String[] values;

    public MySimpleArrayAdapter(Context context, String[] values) {
        super(context, -1, values);
        this.context = context;
        this.values = values;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        LayoutInflater inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View rowView = inflater.inflate(R.layout.rowlayout, parent, false);
        TextView textView = (TextView) rowView.findViewById(R.id.label);
        ImageView imageView = (ImageView) rowView.findViewById(R.id.icon);
        textView.setText(values[position]);
        // change the icon for Windows and iPhone
        String s = values[position];
        if (s.startsWith("iPhone")) {
            imageView.setImageResource(R.drawable.no);
        } else {
            imageView.setImageResource(R.drawable.ok);
        }

        return rowView;
    }
}



(4) 어댑터로부터 데이터모델 업데이트 하기


열(row)은 데이터모델과 상호교류할 수 있는 뷰를 포함할 수 있습니다.

예를 들어, 열에 체크박스를 넣고, 체크박스가 선택된다면 포함하고 있는 정보를 변경시킬 수 있습니다.






5. ListActivity 와 ListFragment



(1) ListView를 사용하기 위한 디폴트 컨테이너


안드로이드는 리스트를 처리/관리하기 위하여 특수 fragment와 activity 클래스를 제공합니다.


activity내에서 리스트를 사용하고 싶을때는 ListActivity 클래스를 사용하고, fragment내에서 사용하고자 할때는 ListFragment 클래스를 사용합니다.


위와같은 요소들( ListActivity, ListFragment 클래스)에는 따로 레이아웃을 지정해주지 않아도 사용가능합니다.

레이아웃을 따로 지정해주지 않는다면, activity나 fragment는 디폴트로 단 하나의 ListView를 가지게 됩니다.

또한, 위의 ListActivity, ListFragment  두 개의 클래스는, 리스트의 아이템들에 대한 선택(selection)을 처리하기 위하여 onListItemClick() 메소드를 오버라이드 할 수 있도록 허용해줍니다.


양 클래스는 어댑터를 디폴트인 ListView로 설정할 수 있도록  setListAdapter() 메소드를 제공합니다.


다음은 ListFragment 을 implement한 간단한 예제코드입니다.


package de.vogella.android.fragments;


import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.ArrayAdapter; import android.widget.ListView; import android.app.ListFragment; public class MyListFragment extends ListFragment { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); String[] values = new String[] { "Android", "iPhone", "WindowsMobile", "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X", "Linux", "OS/2" }; ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, values); setListAdapter(adapter); } @Override public void onListItemClick(ListView l, View v, int position, long id) { // TODO implement some logic } }



다음의 코드는 ListActivity 의 활용하는 예시코드입니다.


package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;

public class MyListActivity extends ListActivity {
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
                "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
                "Linux", "OS/2" };
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, values);
        setListAdapter(adapter);
    } 
}



(2) ListActivity와 커스텀 레이아웃


ListActivity 또는 ListFragment에 적용시킬 자신만의 레이아웃을 만들 수 있습니다.

이 경우, fragment/activity는 제공된 레이아웃 내에서 android:id 속성값으로 @android:id/list 를 가지고있는 ListView를 찾게됩니다.


다음의 코드가 이를 보여줍니다.


<ListView
  android:id="@android:id/list"
  android:layout_width="match_parent"
  android:layout_height="wrap_content" >
</ListView>

* 만약 위의 아이디값(@android:id/list)를 사용하지 않거나, 레이아웃에 ListView 를 넣지 않은 경우, 

activity 또는 fragment를 화면에 표시하려고 경우 앱이 죽어버립니다.(application crashes)



(3) 비어있는 리스트를 위한 Placeholder


그 외에도, 레이아웃에 @android:id/empty 라는 아이디를 가진 뷰를 사용할 수 있습니다.

만약 ListView가 비어있는 경우(표시할 데이터가 없는 경우) 이 ListView를 숨기고 해당하는 activity/fragment는 이 뷰를 자동으로 보여주게 됩니다.

(예시로, 위와 같은 뷰를 통해 데이터가 없는 경우, 데이터가 없다는 에러 메세지를 보여줄 수 있습니다.)






6. 연습: ListView 와 ListActivity 를 사용하는 예제


다음의 예제는 ListActivity에서 ListView를 사용하는 방법을 보여줍니다.

열들을 구성하기 위하여 사전에 정의된 ArrayAdapter 클래스와 기존에 존재하는 안드로이드 레이아웃을 사용합니다.


새로운 안드로이드 프로젝트를 생성하고, 이름을 de.vogella.android.listactivity 으로, 그리고 activity의 이름은 MyListActivity로 설정합니다.


다음 코드를 보고 MyListActivity 클래스에 내용을 추가합니다. (* setContentView() 메소드는 사용되지 않는다는 점을 참고하세요)


package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class MyListActivity extends ListActivity {
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
                "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
                "Linux", "OS/2" };
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, values);
        setListAdapter(adapter);
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        String item = (String) getListAdapter().getItem(position);
        Toast.makeText(this, item + " selected", Toast.LENGTH_LONG).show();
    }
}


(실행 결과)





7. 연습: 커스텀 레이아웃을 사용하는 ListActivity 예제


이 예제에서는 열(row)의 구성을 위한 레이아웃을 정의하고, 어댑터에 적용시켜봅니다.

de.vogella.android.listactivity 프로젝트의 res/layout 폴더에 rowlayout.xml 레이아웃 파일을 새로 만듭니다.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" >

    <ImageView
        android:id="@+id/icon"
        android:layout_width="22px"
        android:layout_height="22px"
        android:layout_marginLeft="4px"
        android:layout_marginRight="10px"
        android:layout_marginTop="4px"
        android:src="@drawable/ic_launcher" >
    </ImageView>

    <TextView
        android:id="@+id/label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@+id/label"
        android:textSize="20px" >
    </TextView>

</LinearLayout>


위의 새롭게 만든 레이아웃을 사용할 수 있도록 기존의 activity (MyListActivity)를 수정합니다.


package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class MyListActivity extends ListActivity {
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
                "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
                "Linux", "OS/2" };
        // use your custom layout
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                R.layout.rowlayout, R.id.label, values);
        setListAdapter(adapter);
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        String item = (String) getListAdapter().getItem(position);
        Toast.makeText(this, item + " selected", Toast.LENGTH_LONG).show();
    }
}



(실행 결과)





8. 튜토리얼: 커스텀 어댑터 만들기 예제


다음의 예제는 'no.png' , 'ok.png' 두 개의 이미지를 사용합니다.(res/drawable-mpdi 폴더 내 / 다른 이미지 사용해도 무방)


이 예제에서의 어댑터 역할을 수행하게 될 클래스인 MySimpleArrayAdapter 클래스를 새로 추가합니다.


package de.vogella.android.listactivity;

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

public class MySimpleArrayAdapter extends ArrayAdapter<String> {
    private final Context context;
    private final String[] values;

    public MySimpleArrayAdapter(Context context, String[] values) {
        super(context, R.layout.rowlayout, values);
        this.context = context;
        this.values = values;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        LayoutInflater inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View rowView = inflater.inflate(R.layout.rowlayout, parent, false);
        TextView textView = (TextView) rowView.findViewById(R.id.label);
        ImageView imageView = (ImageView) rowView.findViewById(R.id.icon);
        textView.setText(values[position]);
        // Change the icon for Windows and iPhone
        String s = values[position];
        if (s.startsWith("Windows7") || s.startsWith("iPhone")
                || s.startsWith("Solaris")) {
            imageView.setImageResource(R.drawable.no);
        } else {
            imageView.setImageResource(R.drawable.ok);
        }

        return rowView;
    }
}


위의 어댑터를 사용하기 위하여, 기존의 adapter를 아래와 같이 수정합니다.


package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;

public class MyListActivity extends ListActivity {
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
                "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
                "Linux", "OS/2" };
        MySimpleArrayAdapter adapter = new MySimpleArrayAdapter(this, values);
        setListAdapter(adapter);
    }
}



(수행 결과)





9. ListView와 그 성능


사용자들은 빠른 응답시간을 기대하기 때문에 안드로이드에서 '성능'은 매우 중요합니다.

데스크탑 컴퓨터와 비교했을때, 안드로이드 기기는 하드웨어 관점으로 보았을때 상대적으로 느리기 때문입니다.


9장에서는 커스텀 list adpater를 효율적으로 상속받아 구현할 수 있도록 연산의 양을 줄이는 법을 다룹니다.


ArrayAdapter와 같은 디폴트 안드로이드 어댑터들은 이미 성능면에서 최적화가 완료되어있습니다.


(1) 시간이 오래걸리는 연산들


XML 레이아웃 파일로부터 inflate되는 모든 뷰들의 결과물로 자바 오브젝트가 생성됩니다.

레이아웃을 inflate(확장)하고, 자바 오브젝트를 만드는 것은 시간과 메모리 면에서 굉장히 비싼 연산입니다.


또한, findViewById() 메소드를 사용하는것도 비교적 시간이 많이 소요되는 작업입니다. (물론 XML inflating 보다는 아닙니다.)


(2) 레이아웃 inflation, 오브젝트 생성을 최대한 피하기(성능 향상을 위하여)


화면에 표시되는 열의 수보다 ListView에는 더 많은 양의 데이터가 있습니다.(기기 화면의 크기는 제한적이기 때문에)

사용자가 화면에서의 리스트를 위아래로 스크롤한다면, 기존에 있던 열들은 화면의 영역을 벗어나게 됩니다.

열을 대표하는 자바 오브젝트는 새롭게 화면에 나타나게 될 열들을 위하여 재사용 되게 됩니다.


안드로이드는 특정 열이 더 이상 보일 수 없다고 판단하면(스크롤하여 화면 밖으로 나갔다고 판단하면), 안드로이드는 어댑터의 getView() 메소드를 사용하여 관련된 뷰를 재사용할 수 있도록 허용합니다. ( convertView 파라미터를 통하여 )


어댑터는 convertView의 뷰 계층도에 포함되어있는 뷰들에 새로운 데이터를 배치할 수 있습니다.

이러한 방법으로 XML파일을 새롭게 inflate하거나 새로운 자바 오브젝트를 만드는 것을 회피할 수 있습니다.


만약 열을 재사용할수 없는 상황이라면, 안드로이드 시스템은 convertView파라미터에 null값을 전달합니다. 따라서 어댑터를 구현할때 이런 경우를 항상 체크해주어야 합니다.



(3) View holder 패턴


성능의 문제로 두번째로 언급되었던 findViewById() 메소드는 어댑터에서 ViewHolder를 구현함으로써 피할 수 있습니다.


ViewHolder 클래스는 어댑터 내부의 static inner class로써, 현재 레이아웃 내의 관련있는 뷰들에 관한 레퍼런스 정보를 담고있습니다.

이러한 레퍼런스 정보는 각각의 열에 태그값으로써 배정되는데, 이때 setTag() 메서드를 사용합니다.


convertView 오브젝트를 받게된 경우, getTag() 메소드를 사용하여 ViewHolder 객체를 가질 수 있고, ViewHolder의 레퍼런스 정보를 사용하여 뷰에 새로운 속성값을 배정할 수 있게됩니다.


복잡하게 보일 순 있겠지만, 이 방법은 findViewById() 메소드를 사용하는 것보다 대략 15%정도 빠릅니다.



(4) 관련 예제


다음의 코드는 holder pattern을 구현하고, 이미 존재하는 뷰를 재사용하는 (2),(3)의 방법들을 활용하는 성능 최적화된 어댑터의 구현을 나타냅니다.


package de.vogella.android.listactivity;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class MyPerformanceArrayAdapter extends ArrayAdapter<String> {
    private final Activity context;
    private final String[] names;

    static class ViewHolder {
        public TextView text;
        public ImageView image;
    }

    public MyPerformanceArrayAdapter(Activity context, String[] names) {
        super(context, R.layout.rowlayout, names);
        this.context = context;
        this.names = names;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View rowView = convertView;
        // reuse views
        if (rowView == null) {
            LayoutInflater inflater = context.getLayoutInflater();
            rowView = inflater.inflate(R.layout.rowlayout, null);
            // configure view holder
            ViewHolder viewHolder = new ViewHolder();
            viewHolder.text = (TextView) rowView.findViewById(R.id.TextView01);
            viewHolder.image = (ImageView) rowView
                    .findViewById(R.id.ImageView01);
            rowView.setTag(viewHolder);
        }

        // fill data
        ViewHolder holder = (ViewHolder) rowView.getTag();
        String s = names[position];
        holder.text.setText(s);
        if (s.startsWith("Windows7") || s.startsWith("iPhone")
                || s.startsWith("Solaris")) {
            holder.image.setImageResource(R.drawable.no);
        } else {
            holder.image.setImageResource(R.drawable.ok);
        }

        return rowView;
    }
}







10. ListView 기기화면에서 선택된 뷰 알아내기



기본설정값(디폴트)으로는, ListView에서는 선택모드가 비활성화 되어있지만, setChoiceMode() 메소드 호출을 통하여 해당 기능을 활성화 시킬 수 있습니다.

setChoiceMode() 메소드를 호출할 때, 다중선택모드를 위해서는 인자로 ListView.CHOICE_MODE_MULTIPLE 을 넘기고, 단일선택모드를 위해서는 ListView.CHOICE_MODE_SINGLE 을 넘겨야합니다.


예) 

다중선택모드를 사용하는 경우

MainActivity onCreate() 메소드 내에 -> getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 선언


ListView에서 선택된 항목들을 알아내기 위해서는 getCheckedItemPosition() 을 사용합니다.

(위는 단일모드 기준 / 다중모드인 경우에는 listView.getCheckedItemPositions() 를 사용)


getCheckedItemIds()를 사용하여 선택된 뷰들의 id값을 가져올 수도 있습니다.



안드로이드는 이미 이러한 기능을 가지고 있는 디폴트 레이아웃을 제공하고 있습니다.

android.R.layout.simple_list_item_multiple_choice 레이아웃으로, 이미 구성된 CheckedTextView 뷰를 포함하고 있습니다.


다음의 예는 이러한 선택기능을 사용하는 법을 보여줍니다.

아래의 모드를 사용하면, ListView는 선택된 값들을 저장하고있습니다. 데이터모델(실제 데이터)에는 따로 해당내용이 저장되지 않습니다.


* 다중모드 예시


package com.vogella.android.listview.selection.multi;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

import com.vogella.android.listview.selection.R;

public class MainActivity extends ListActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String[] values = new String[] { "a", "b", "c", "d", "e", "f", "g",
                "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
                "t", "u", "w", "x", "y", "z" };

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_multiple_choice, values);
        setListAdapter(adapter);
        getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        Toast.makeText(this,
                String.valueOf(getListView().getCheckedItemCount()),
                Toast.LENGTH_LONG).show();
        return true;
    }
}



* 단일모드 예시


package com.vogella.android.listview.selection.single;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;


public class MainActivity extends ListActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String[] values = new String[] { "a", "b", "c", "d", "e", "f", "g",
                "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
                "t", "u", "w", "x", "y", "z" };

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_single_choice, values);
        setListAdapter(adapter);
        getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        Toast.makeText(this,
                String.valueOf(getListView().getCheckedItemCount()),
                Toast.LENGTH_LONG).show();
        return true;
    }
}



+ Recent posts