ListFragment에 Custom ListView 만드는 방법. (ListFragment with Custom ListView)

2016. 6. 14. 17:56


1. ListFragment

Fragment에 ListView를 만드는 방법이 그리 어렵지는 않지만, 그 마저도 쉽게 구현할 수 있도록, 안드로이드에서는 ListFragment라는 클래스를 제공하고 있습니다. 지난 글 [ListView를 가지는 Fragment 만들기]을 통해 기본적인 형태(TextView만 사용)의 ListFragment를 구현하는 방법에 대해 살펴볼 수 있습니다.


ListFragment에 Adapter를 지정하는 setListAdapter() 함수의 존재를 보면 알 수 있듯이, ListFragment는 ListView의 Wrapper(감싸기, 포장) 클래스 역할을 하는 Fragment라고 정의할 수 있습니다. 즉, ListFragment는 ListView를 멤버로 가지면서, ListView에 대한 구현 절차를 ListView에 대한 직접적인 접근이 아닌, ListFragment에서 제공하는 함수를 통해 수행하도록 만든 클래스인 것입니다.


이렇게 함으로써 Fragment와 ListView가 제공하는 기능들을 한번에 구현할 수 있고, 각각을 구현하는 과정을 다소 간소화할 수 있는 장점이 있습니다. 하지만 ListFragment를 익숙하게 사용하기 위해서는 Fragment와 ListView의 구현작업에 대한 이해가 필수적이므로, 먼저 설명한 [ListView] 및 [Fragment] 에 관한 내용들을 살펴보시길 바랍니다.


1.1 Custom ListView

[안드로이드 커스텀 리스트뷰 만드는 방법]에서 살펴봤듯이, ListView를 사용할 때, 하나의 ListView 아이템에 이미지나 버튼, 텍스트 등의 View 위젯을 조합하여 여러 정보를 한번에 보여주는 경우가 흔합니다. 하지만 텍스트만으로 아이템을 표시하는 [안드로이드 리스트뷰 기본 사용법]에 비해 몇 가지 추가적인 작업이 필요합니다.


ListFragment 또한 마찬가지입니다. ListFragment에 Custom ListView를 만들기 위해서는 [ListView를 가지는 Fragment 만들기]에서 살펴본 과정에 몇 가지 작업을 더 해줘야 합니다.


2. ListFragment에 Custom ListView 사용하기

[안드로이드 커스텀 리스트뷰 만드는 방법]에서 작성한 예제를 ListFragment를 사용하여 만들어 보도록 하겠습니다.


ListFragment 커스텀 리스트뷰 아이템 레이아웃



2.1 워크 플로우


ListFragment 커스텀 리스트뷰 작성 절차



2.2 Activity에 Fragment 추가

Activity의 Layout 리소스 XML에 ListFragment를 추가합니다.


[STEP-1] "activity_main.xml" - MainActivity에 Fragment 추가.
<?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.customlistfragmentexample1.MainActivity"
    tools:showIn="@layout/activity_main">

    <fragment
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/customlistfragment"
        android:name="com.recipes4dev.examples.customlistfragmentexample1.CustomListFragment"
        tools:layout="@layout/activity_main" />

</RelativeLayout>


2.3 ListFragment 상속 및 구현

ListFragment를 상속한 새로운 클래스를 추가합니다.


[STEP-2] "CustomListFragment.java" - ListFragment 클래스 상속.
import android.support.v4.app.ListFragment;

public class CustomListFragment extends ListFragment {
    // TODO
}


2.4 ListView 아이템에 대한 Layout 구성

ListFragment의 ListView 아이템에 표시될 Layout을 구성합니다. [안드로이드 커스텀 리스트뷰 만드는 방법]에서 사용한 Layout 리소스 XML을 그대로 사용합니다.


[STEP-3] "/res/layout/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">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:id="@+id/imageView1"
        android:layout_weight="1" />

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="4">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="New Text"
            android:id="@+id/textView1"
            android:textSize="24sp"
            android:textColor="#000000"
            android:gravity="center_vertical"
            android:layout_weight="2" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="New Text"
            android:id="@+id/textView2"
            android:textSize="16sp"
            android:textColor="#666666"
            android:layout_weight="1" />
    </LinearLayout>

</LinearLayout>


2.5 아이템 데이터에 대한 클래스 정의

ListView 아이템 클래스 또한 [안드로이드 커스텀 리스트뷰 만드는 방법]의 클래스를 사용합니다.


[STEP-4] "ListViewItem.java" - ListView 아이템 데이터 클래스 정의.
package com.recipes4dev.examples.customlistfragmentexample1;

import android.graphics.drawable.Drawable;

public class ListViewItem {
    private Drawable iconDrawable ;
    private String titleStr ;
    private String descStr ;

    public void setIcon(Drawable icon) {
        iconDrawable = icon ;
    }
    public void setTitle(String title) {
        titleStr = title ;
    }
    public void setDesc(String desc) {
        descStr = desc ;
    }

    public Drawable getIcon() {
        return this.iconDrawable ;
    }
    public String getTitle() {
        return this.titleStr ;
    }
    public String getDesc() {
        return this.descStr ;
    }
}


2.6 Adapter 클래스 상속 및 구현.

BaseAdapter를 상속하여 새로운 Adapter 클래스를 추가합니다. (ListViewAdapter의 각 함수에 관한 설명은 [안드로이드 커스텀 리스트뷰 만드는 방법] 또는 코드의 주석을 참고하시기 바랍니다.)


[STEP-5] "ListViewAdapter.java" - BaseAdapter 상속 및 ListViewAdapter 구현.
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.ArrayList;

public class ListViewAdapter extends BaseAdapter {
    // Adapter에 추가된 데이터를 저장하기 위한 ArrayList
    private ArrayList<ListViewItem> listViewItemList = new ArrayList<ListViewItem>() ;

    // ListViewAdapter의 생성자
    public ListViewAdapter() {

    }

    // 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된)으로부터 위젯에 대한 참조 획득
        ImageView iconImageView = (ImageView) convertView.findViewById(R.id.imageView1) ;
        TextView titleTextView = (TextView) convertView.findViewById(R.id.textView1) ;
        TextView descTextView = (TextView) convertView.findViewById(R.id.textView2) ;

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

        // 아이템 내 각 위젯에 데이터 반영
        iconImageView.setImageDrawable(listViewItem.getIcon());
        titleTextView.setText(listViewItem.getTitle());
        descTextView.setText(listViewItem.getDesc());

        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(Drawable icon, String title, String desc) {
        ListViewItem item = new ListViewItem();

        item.setIcon(icon);
        item.setTitle(title);
        item.setDesc(desc);

        listViewItemList.add(item);
    }
}


2.7 Adapter 생성 후 ListView에 지정.

[ListView를 가지는 Fragment 만들기]에서는 Activity에서 Adapter를 생성한 다음, ListFragment.setListAdapter() 함수를 호출하여 Adapter를 지정하였습니다. 하지만 ListFragment를 상속받은 이상, 굳이 Adapter를 ListFragment의 외부에서 생성할 필요가 없죠. 대신, ListFragment의 내부에서 Adapter와 관련된 처리를 수행해주면 됩니다.


[STEP-6] "CustomListFragment.java" - CustomListFragment에서 Adapter 생성 및 지정.
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.content.ContextCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.R.id.*;
import android.widget.ListView;

public class CustomListFragment extends ListFragment {

    ListViewAdapter adapter ;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        // Adapter 생성 및 Adapter 지정.
        adapter = new ListViewAdapter() ;
        setListAdapter(adapter) ;

        // 첫 번째 아이템 추가.
        adapter.addItem(ContextCompat.getDrawable(getActivity(), R.drawable.ic_account_box_black_36dp),
                "Box", "Account Box Black 36dp") ;
        // 두 번째 아이템 추가.
        adapter.addItem(ContextCompat.getDrawable(getActivity(), R.drawable.ic_account_circle_black_36dp),
                "Circle", "Account Circle Black 36dp") ;
        // 세 번째 아이템 추가.
        adapter.addItem(ContextCompat.getDrawable(getActivity(), R.drawable.ic_assignment_ind_black_36dp),
                "Ind", "Assignment Ind Black 36dp") ;

        return super.onCreateView(inflater, container, savedInstanceState);
    }
}


2.8 ListView 클릭 이벤트 처리

ListFragment의 onListItemClick() 함수를 override하여 ListView 아이템 클릭 이벤트를 처리합니다.


[STEP-7] "CustomListFragment.java" - onListItemClick() 함수를 override하여 클릭 이벤트 처리
public class CustomListFragment extends ListFragment {

    // 코드 계속 ...

    @Override
    public void onListItemClick (ListView l, View v, int position, long id) {
        // get TextView's Text.
        ListViewItem item = (ListViewItem) l.getItemAtPosition(position) ;

        String titleStr = item.getTitle() ;
        String descStr = item.getDesc() ;
        Drawable iconDrawable = item.getIcon() ;

        // TODO : use item data.
    }

    // ... 코드 계속
}


2.9 ListFragment 외부(Activity)에서 아이템 추가를 위한 함수 구현.

[STEP-6]의 onCreateView에서 Adapter를 생성하고 ListView에 지정한 다음, 샘플 데이터를 추가하였지만, 이는 앱 시작 시 ListFragment 내부에서만 사용할 수 있는 방법입니다. 일반적으로 ListView에 아이템이 추가될 때는 Activity에 정의된 이벤트 핸들러 함수에서 데이터가 로드 되는 것과 같이 앱 실행 중 동적으로 이루어지므로 ListFragment 외부에서 데이터를 추가할 수 있는 방법이 있어야 합니다.


이를 위해 CustomListFragment에 아이템 추가를 위한 함수를 정의하고, Activity에서 해당 함수를 통해 아이템을 추가하는 코드를 작성하겠습니다.


[STEP-8] "CustomListFragment.java" - 아이템 추가 함수 정의.
public class CustomListFragment extends ListFragment {

    // 코드 계속 ...

    public void addItem(Drawable icon, String title, String desc) {
        adapter.addItem(icon, title, desc) ;
    }
}


[ListView를 가지는 Fragment 만들기]에서 살펴봤듯이, Activity에 추가한 CustomListFragment의 참조를 가져오기 위해, FragmentManager의 findFragmentById() 함수를 호출합니다. 그리고 addItem() 함수를 호출하여 새로운 아이템을 추가하였습니다.


[STEP-9] "MainActivity.java" - Activity에서 아이템 추가.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        CustomListFragment customListFrgmt = (CustomListFragment) getSupportFragmentManager().findFragmentById(R.id.customlistfragment);
        customListFrgmt.addItem(ContextCompat.getDrawable(this, R.drawable.ic_account_box_black_36dp),
                "New Box", "New Account Box Black 36dp") ;
    }
}


3. 예제 실행 화면

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


ListFragment 커스텀 리스트뷰 실행 화면



4. 참고.

.END.


ANDROID 프로그래밍/FRAGMENT