2015. 8. 3. 15:00

안드로이드에서 커스텀 한 리스트를 표현하고 싶다면 리스트 뷰를 이용해야 합니다.

리스트뷰를 생성하고 데이터를 추가 삭제하여 활용하는 방법을 알아보겠습니다.

 

 

0. 테스트 환경 만들기

메인 액티비티의 레이아웃을 다음과 같이 만듭니다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

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

        <EditText android:id="@+id/txtData1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
        <EditText android:id="@+id/txtData2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
        <EditText android:id="@+id/txtData3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>

        <Button android:id="@+id/btnDataAdd"
            android:layout_width="80dp"
            android:layout_height="wrap_content"
            android:text="Add"
            android:onClick="onClick_btnDataAdd" />

    </LinearLayout>

    <ListView android:id="@+id/lvTest"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#3f00"
        android:listSelector="#00000000" >

    </ListView>

</LinearLayout>

 

각 위젯은 내부에 아래와 같이 생성되어 있습니다.

this.txtData1 = (EditText)findViewById(R.id.txtData1);
this.txtData2 = (EditText)findViewById(R.id.txtData2);
this.txtData3 = (EditText)findViewById(R.id.txtData3);
this.btnDataAdd = (Button)findViewById(R.id.btnDataAdd);
this.lvTest = (ListView)findViewById(R.id.lvTest);

 

 

1. 제공되는 레이아웃을 이용하는 방법

안드로이드에서 기본 제공되는 레이아웃을 이용하면 간단하게 리스트뷰를 이용할 수 있습니다.

기본 제공되는 레이아웃은 다음과 같습니다.

 

simple_expandable_list_item_1
simple_expandable_list_item_2
simple_list_item_1
simple_list_item_2
simple_list_item_activated_1
simple_list_item_activated_2
simple_list_item_checked
simple_list_item_multiple_choice
simple_list_item_single_choice
simple_selectable_list_item

(참고 : Android Developers - R.layout)

 

버튼의 클릭 이벤트에 다음과 같은 코드를 넣어 줍니다.

String[] sData = {"a", "b", "c", "d", "e", "f", "g"};
ArrayAdapter adapter = new ArrayAdapter<String>(this, android.R.layout.simple_expandable_list_item_1 , sData);
this.lvTest.setAdapter(adapter);

 

이 방법은 아이템 레이아웃을 만들 필요가 없다는 장점이 있습니다.

간단하게 사용할 수 있죠.

문제는 원하는 레이아웃이 있을 리가-_-;;

배열 말고도 'ArrayList' 같은 것들도 사용할 수 있습니다.

 

 

2. 커스텀 리스트뷰 아이템

이제 원하는 레이아웃을 만들어 리스트뷰의 아이템으로 써봅시다.

 

2-1. 아이템 레이아웃(Item layout) 만들기

프로젝트 > app > res > layout

에서 오른쪽 클릭을 하고

New > Layout resource file

을 선택하여 레이아웃 파일을 생성합니다.

(전 'listview_item'로 만들었습니다.)

 

아이템 레이아웃을 다음과 같이 작성합니다.

<?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:id="@+id/txtIndex"
        android:text="0"
        android:layout_width="30dp"
        android:layout_height="wrap_content" />

    <TextView android:id="@+id/txtData1"
        android:text="Data1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"/>
    <TextView android:id="@+id/txtData2"
        android:text="Data2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"/>
    <TextView android:id="@+id/txtData3"
        android:text="Data2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"/>

    <Button android:id="@+id/btnEdit"
        android:layout_width="80dp"
        android:layout_height="wrap_content"
        android:text="Edit"  />
</LinearLayout>

 

2-2. 커스텀 어레이 어댑터(Cusrom Array Adapter) 만들기

'ArrayAdapter'를 상속받아 원하는 동작을 하도록 만듭니다.

일단 어댑터에 사용할 데이터 클래스를 만듭니다.

public class dataTest
{
    public int Index;
    public String Data1;
    public String Data2;
    public String Data3;

    public dataTest(int nIndex, String sData1, String sData2, String sData3 )
    {
        this.Index = nIndex;
        this.Data1 = sData1;
        this.Data2 = sData2;
        this.Data3 = sData3;
    }
}

 

 

'ArrayAdapter'를 상속받아 CusromAdapter를 만들어 봅시다.

{
    //리스트에 사용할 데이터
    private ArrayList<dataTest> m_listItem;

    public CusromAdapter(Context context, int textViewResourceId, ArrayList<dataTest> objects )
    {
        super(context, textViewResourceId, objects);

        this.m_listItem = objects;
    }

    public View getView(int position, View convertView, ViewGroup parent)
    {
        View v = convertView;
        if( null == v)
        {
            LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            v = vi.inflate(R.layout.listview_item, null);
        }

        //위젯 찾기
        TextView txtIndex = (TextView)v.findViewById(R.id.txtIndex);
        TextView txtData1 = (TextView)v.findViewById(R.id.txtData1);
        TextView txtData2 = (TextView)v.findViewById(R.id.txtData2);
        TextView txtData3 = (TextView)v.findViewById(R.id.txtData3);
        Button btnEdit = (Button)v.findViewById(R.id.btnEdit);

        //위젯에 데이터를 넣는다.
        dataTest dataItem = m_listItem.get(position);
        txtIndex.setText(dataItem.Index + "");
        txtData1.setText(dataItem.Data1);
        txtData2.setText(dataItem.Data2);
        txtData3.setText(dataItem.Data3);

        btnEdit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LinearLayout itemParent = (LinearLayout)v.getParent();
                Toast.makeText(
                        getApplicationContext(),
                        "선택된 데이터 : " + ((TextView)itemParent.findViewById(R.id.txtIndex)).getText(),
                        Toast.LENGTH_SHORT
                ).show();
            }
        });

        return v;
    }

}//end class CusromAdapter

 

여기서 주의 깊게 봐야 하는 내용은 'getView'에서 데이터를 받고 이 데이터를 각각의 컨트롤에 바인딩하는 과정입니다.

그리고 클릭 리스너를 연결할 때 'getParent()'를 호출하여 상위 레이아웃에서 데이터를 찾는 방법입니다.

 

 

'Add' 클릭 이벤트를 다음과 같이 다시 작성합니다.

ArrayList<dataTest> listReturn = new ArrayList<dataTest>();

listReturn.add(new dataTest(0, "a", "a2", "a3"));
listReturn.add(new dataTest(1, "b", "b2", "b3"));
listReturn.add(new dataTest(2, "c", "c2", "c3"));
listReturn.add(new dataTest(3, "d", "d2", "d3"));
listReturn.add(new dataTest(4, "e", "e2", "e3"));
listReturn.add(new dataTest(5, "f", "f2", "f3"));
listReturn.add(new dataTest(6, "g", "g2", "g3"));

CusromAdapter adapter = new CusromAdapter(this, 0, listReturn );
this.lvTest.setAdapter(adapter);

 

 

이제 테스트해봅시다.

 

 

3. 데이터 추가 삭제

데이터를 추가하고 삭제할 수 있도록 하여 그럴싸한 리스트 뷰를 만들어 봅시다.

 

3-1. 준비 작업

클릭 이벤트 안에 생성했던 어댑터와 리스트를 클래스 맴버 변수로 빼줍니다.

하는 김에 인덱스로 사용할 변수도 만듭시다.

 

 

클래스의 생성자(우리는 메인 액티비티에서 작업 중이니 'onCreate()')에서 어댑터와 리스트를 초기화해줍니다.

adapter = new CusromAdapter(this, 0, listReturn );
this.lvTest.setAdapter(adapter);

 

3-2. 데이터 추가

데이터는 어댑터에 직접 추가하면 됩니다.

this.adapter.add(new dataTest(this.m_nIndex
        , this.txtData1.getText().toString()
        , this.txtData2.getText().toString()
        , this.txtData3.getText().toString()));

this.adapter.notifyDataSetChanged();
++this.m_nIndex;

 

여기서 주의 깊게 봐야 할 코드는 'ArrayAdapter.notifyDataSetChanged();'는 리스트를 갱신할 때 사용합니다.

데이터만 추가하고 'ArrayAdapter.notifyDataSetChanged();'를 호출하지 않으면 데이터가 추가되지 않습니다.

 

3-3. 데이터 제거

데이터 제거는 조금 복잡합니다.

데이터를 제거하려면 리스트의 포지션을 얻어야 합니다.

'ListView.setOnItemClickListener'로 클릭 이벤트를 받아왔다면 파라미터로 'int position'로 넘어오기 때문에 간단한데..... 

우리는 아이템 레이아웃에 추가한 버튼이라 파라미터로 오지 않습니다.

 

결국 아이템 바인딩 단계에서 포지션정보를 입력해야 한다는 결론이 나옵니다.

'CusromAdapter.getView()'에서 'Button btnEdit = (Button)v.findViewById(R.id.btnEdit);' 이후에 태그에 포지션값을 입력해줍니다.

 

 

그러고 나서 클릭 이벤트에서 태그를 읽어 포지션값을 가지고 옵니다.

이렇게 가지고 온 포지션값을 이용하여 데이터를 삭제합니다.

 

아래는 수정된 'CusromAdapter' 코드입니다.

private class CusromAdapter extends ArrayAdapter<dataTest>
{
    //리스트에 사용할 데이터
    private ArrayList<dataTest> m_listItem;

    public CusromAdapter(Context context, int textViewResourceId, ArrayList<dataTest> objects )
    {
        super(context, textViewResourceId, objects);

        this.m_listItem = objects;
    }

    public View getView(int position, View convertView, ViewGroup parent)
    {
        View v = convertView;
        if( null == v)
        {
            LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            v = vi.inflate(R.layout.listview_item, null);
            //v = vi.inflate(R.layout.listview_item_nobutton, null);
        }

        //위젯 찾기
        TextView txtIndex = (TextView)v.findViewById(R.id.txtIndex);
        TextView txtData1 = (TextView)v.findViewById(R.id.txtData1);
        TextView txtData2 = (TextView)v.findViewById(R.id.txtData2);
        TextView txtData3 = (TextView)v.findViewById(R.id.txtData3);
        Button btnEdit = (Button)v.findViewById(R.id.btnEdit);

        //위젯에 데이터를 넣는다.
        dataTest dataItem = m_listItem.get(position);
        txtIndex.setText(dataItem.Index + "");
        txtData1.setText(dataItem.Data1);
        txtData2.setText(dataItem.Data2);
        txtData3.setText(dataItem.Data3);

        //포지션 입력
        btnEdit.setTag(position);
        btnEdit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LinearLayout itemParent = (LinearLayout)v.getParent();
                int nPosition = (int) v.getTag();
                /*Toast.makeText(
                        getApplicationContext(),
                        //"선택된 데이터 : " + ((TextView)itemParent.findViewById(R.id.txtIndex)).getText(),
                        "선택된 데이터 : " + pos,
                        Toast.LENGTH_SHORT
                ).show();*/

                MainActivity.this.RemoveData(nPosition);
            }
        });

        return v;
    }

}//end class CusromAdapter

 

 

위에 'MainActivity.this.RemoveData()' 라는 코드가 있으니 'RemoveData()'를 만들어야겠지요?

메인 클래스에서 'RemoveData(int nPosition)'라는 맴버 함수를 만듭니다.

public void RemoveData(int nPosition)
{
    this.adapter.remove(this.listReturn.get(nPosition));
}

 

 

이제 테스트해봅시다.

 

이것으로 우리가 알 수 있는 것은 'ArrayAdapter.notifyDataSetChanged();'가 호출되면 포지션값이 다시 지정된다는 것입니다.

한마디로 리스트 자체를 다시 그린다는 것이죠.

 

 

마무리

안드로이드는 구조 때문인지는 몰라도 '.NET'에서 보다 리스트처리가 복잡합니다.

예전에는 별생각 없이 썼었는데 자마린(Xamarin)을 쓰다가 다시 안드로이드 날 코딩을 하니 차이를 확 느끼네요.

 

안드로이드 스튜디오 프로젝트 파일입니다.

ListViewTest.zip
다운로드