ListView를 만들고 나니까 이번엔 ListView 안에 또 다른 ListView를 만들 필요가 생겼다. ListView 안에 객체로 다른 ListView를 선언해야 하나 하고 다시 여기저기 자료를 찾아봤는데(Google 만세!) 굳이 ListView를 두 번 선언하지 않고도 ExpandableListView라는 class를 이용하여 ParentList, ChildList 식으로 구성하면 되었다. 다만 Android Studio의 자동 완성 기능을 활용해 보니, ExpandableListViewAdapter의 Customizing이 필요했다.

1. 목적 : ExpandableListView의 기본 구조 및 사용법을 알아보자.

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Android Hive - Android Expandable List View Tutorial (http://www.androidhive.info/2013/07/android-expandable-list-view-tutorial/)
 2) 아라비안나이트 블로그 - ExpandableListView 제작 예시(http://arabiannight.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9CAndroid-ExpandableListView-%EB%A7%8C%EB%93%A4%EA%B8%B0)

4. 과정
 1) activity_main.xml(App에 보여질 전체적인 Layout)에 ExpandableListView를 정의하고,  ExpandableListView의 ParentList와 ChildList에 해당하는 list의 객체들을 각각 parent_listview.xml과 child_listview.xml로 다음과 같이 정의한다. 이 때, .xml 파일명의 사용자의 편의에 따라 다르게 쓸 수 있고 .java 에서 연결할 때 해당 .xml 파일명을 적용하면 된다.

<!-- activity_main.xml -->
<?
xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
    <ExpandableListView
        android:id="@+id/expandablelist"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:groupIndicator="@null">
    </ExpandableListView>
</RelativeLayout>
<!-- parent_listview.xml -->
<?
xml version="1.0" encoding="utf-8"?>

<LinearLayout 
   xmlns:
android="http://schemas.android.com/apk/res/android"
   android:orientation="vertical"  
   android
:layout_width="match_parent"
   android:layout_height="match_parent"
   android:padding="10dp">
   <TextView
       android:id="@+id/parenttext"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:padding="5dp"
       android:text="Parent TextView"
       android:textColor="#AA000000"/>
</LinearLayout

 

[xml Code] ExpandableListView에서 ParentListView에 해당하는 'parent_listview.xml'을 통해 parent는 각 원소별로 1개의 TextView로 이루어지게 설계했음을 알 수 있으며, ParentList의 원소를 선택했을 경우 나타날 ChildListView는 'child_listview.xml'에서 보이는 바와 같이 1개의 ImageView와 1개의 TextView로 구성되도록 설계했다.
<!-- child_listview.xml -->
<?
xml version="1.0" encoding="utf-8"?>

<RelativeLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="vertical"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:padding="10dp">
   <ImageView
       android:id="@+id/child_item_icon"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:padding="10dp"
       android:src="@mipmap/ic_launcher"/>
   <TextView
       android:id="@+id/childtext"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_toRightOf="@id/child_item_icon"
       android:layout_gravity="center_vertical"
       android:gravity="center_vertical"
       android:padding="5dp"
       android:text="Child TextView"
       android:textColor="#88000000"/>
</RelativeLayout>

 

 2) 1)의 ExpandableListView가 적용될 CustomAdapter를 다음과 같이 정의한다. 상속받은 method를 @Override 해야함을 잊으면 안된다. 그리고 아래 보기에서 나는 생성자를 통해서 받는 childList를 HashMap 변수를 통해서 연결했지만, 다른 참고자료에서 처럼 List<>등으로 바꾸어 입맛에 맞게 편집하면 된다.


public class
CustomExpandableListViewAdapter extends BaseExpandableListAdapter {

    private Context mContext;
    private ArrayList<String> mParentList;
    private ArrayList<ChildListData> mChildList;
    private ChildListViewHolder mChildListViewHolder;
    private HashMap<String, ArrayList<ChildListData>> mChildHashMap;

    // CustomExpandableListViewAdapter 생성자
    public CustomExpandableListViewAdapter(Context context, ArrayList<String> parentList, HashMap<String, ArrayList<ChildListData>> childHashMap){
        this.mContext = context;
        this.mParentList = parentList;
        this.mChildHashMap = childHashMap;
    }

    /* ParentListView에 대한 method */
   
@Override
    public String getGroup(int groupPosition) { // ParentList의 position을 받아 해당 TextView에 반영될 String을 반환
        return mParentList.get(groupPosition);
    }
    
    @Override
    public int getGroupCount() { // ParentList의 원소 개수를 반환
        return mParentList.size();
    }
    
    @Override
    public long getGroupId(int groupPosition) { // ParentList의 position을 받아 long값으로 반환 
        return groupPosition;
    }
    
    @Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { // ParentList의 View
        if(convertView == null){
            LayoutInflater groupInfla = (LayoutInflater) this.mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            // ParentList의 layout 연결. root로 argument 중 parent를 받으며 root로 고정하지는 않음
            convertView = groupInfla.inflate(R.layout.parent_listview, parent, false);
        }

        // ParentList의 Layout 연결 후, 해당 layout 내 TextView를 연결
        TextView parentText = (TextView)convertView.findViewById(R.id.parenttext);
        parentText.setText(getGroup(groupPosition));
        return convertView;
    }

    /* 여기서부터 ChildListView에 대한 method */
    @Override
    public ChildListData getChild(int groupPosition, int childPosition) { // groupPostion과 childPosition을 통해 childList의 원소를 얻어옴
        return this.mChildHashMap.get(this.mParentList.get(groupPosition)).get(childPosition);

    }
    
    @Override
    public int getChildrenCount(int groupPosition) { // ChildList의 크기를 int 형으로 반환
        return this.mChildHashMap.get(this.mParentList.get(groupPosition)).size();

    }
    
    @Override
    public long getChildId(int groupPosition, int childPosition) { // ChildList의 ID로 long 형 값을 반환
        return childPosition;
    }
    
    @Override 
    public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
        // ChildList의 View. 위 ParentList의 View를 얻을 때와 비슷하게 Layout 연결 후, layout 내 TextView, ImageView를 연결
        ChildListData childData = (ChildListData)getChild(groupPosition, childPosition);
        if(convertView == null){
            LayoutInflater childInfla = (LayoutInflater) this.mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = childInfla.inflate(R.layout.child_listview, null);

            mChildListViewHolder = new ChildListViewHolder();
            mChildListViewHolder.mChildListViewIcon = (ImageView)convertView.findViewById(R.id.child_item_icon);
            mChildListViewHolder.mChildListViewText = (TextView)convertView.findViewById(R.id.childtext);
            convertView.setTag(mChildListViewHolder);
        } else{
            mChildListViewHolder = (ChildListViewHolder)convertView.getTag();
        }

        mChildListViewHolder.mChildListViewText.setText(getChild(groupPosition, childPosition).mChildText);
        mChildListViewHolder.mChildListViewIcon.setImageDrawable(getChild(groupPosition, childPosition).mChildItem);
        return convertView;

    }

    @Override
    public boolean hasStableIds() { return true; } // stable ID인지 boolean 값으로 반환

    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } // 선택여부를 boolean 값으로 반환

 

 3) MainActivity.java(ExpandableListView에 객체를 입력하고 동작에 대한 code 작성)을 다음과 같이 coding한다.

public class MainActivity extends Activity {
    public ExpandableListView expandableListView; // ExpandableListView 변수 선언
    public CustomExpandableListViewAdapter mCustomExpListViewAdapter; // 위 ExpandableListView를 받을 CustomAdapter(2번 class에 해당)를 선언
    public ArrayList<String> parentList; // ExpandableListView의 Parent 항목이 될 List 변수 선언
    public ArrayList<ChildListData> fruit; // ExpandableListView의 Child 항목이 될 List를 각각 선언
    public ArrayList<ChildListData> vegetables;
    public ArrayList<ChildListData> etc;
    public HashMap<String, ArrayList<ChildListData>> childList; // 위 ParentList와 ChildList를 연결할 HashMap 변수 선언

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); // activity_main.xml을 MainActivity에 연결

        // ExpandableListView의 ParentList에 해당할 항목을 입력
        parentList = new ArrayList<String>();
        parentList.add("과일");
        parentList.add("채소");
        parentList.add("기타");

        // 앞서 ParentList에 연결할 ChildList 항목을 선언 및 입력
        ChildListData apple = new ChildListData(getResources().getDrawable(R.mipmap.apple), "사과");
        ChildListData pair = new ChildListData(getResources().getDrawable(R.mipmap.pair), "배");
        ChildListData persimmon = new ChildListData(getResources().getDrawable(R.mipmap.persimmon), "감");
        fruit = new ArrayList<ChildListData>();
        fruit.add(apple);
        fruit.add(pair);
        fruit.add(persimmon);

        ChildListData onion = new ChildListData(getResources().getDrawable(R.mipmap.onion), "양파");
        ChildListData cabbage = new ChildListData(getResources().getDrawable(R.mipmap.cabbage), "양배추");
        ChildListData potato = new ChildListData(getResources().getDrawable(R.mipmap.potato), "감자");
        ChildListData sweetPotato = new ChildListData(getResources().getDrawable(R.mipmap.sweetpotato), "고구마");
        ChildListData pumpkin = new ChildListData(getResources().getDrawable(R.mipmap.pumpkin), "호박");
        vegetables = new ArrayList<ChildListData>();
        vegetables.add(onion);
        vegetables.add(cabbage);
        vegetables.add(potato);
        vegetables.add(sweetPotato);
        vegetables.add(pumpkin);

        ChildListData seaweed = new ChildListData(getResources().getDrawable(R.mipmap.seaweed), "미역");
        ChildListData bread = new ChildListData(getResources().getDrawable(R.mipmap.bread), "빵");
        ChildListData mackerel = new ChildListData(getResources().getDrawable(R.mipmap.mackerel), "고등어");
        etc = new ArrayList<ChildListData>();
        etc.add(seaweed);
        etc.add(bread);
        etc.add(mackerel);

        // 위에서 선언한 ParentList와 ChildList를 HashMap을 통해 
        childList = new HashMap<String, ArrayList<ChildListData>>();
        childList.put(parentList.get(0), fruit);
        childList.put(parentList.get(1), vegetables);
        childList.put(parentList.get(2), etc);

        // 앞서 정의해 놓은 ExpandableListView와 그 CustomAdapter를 선언 및 연결한 후
      // ExpandableListView에 대한 OnClickListener 등을 선언

        expandableListView = (ExpandableListView)findViewById(R.id.expandablelist);
        mCustomExpListViewAdapter = new CustomExpandableListViewAdapter(this, parentList, childList);
        expandableListView.setAdapter(mCustomExpListViewAdapter);
        expandableListView.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
            @Override
            public void onGroupExpand(int groupPosition) {
            }
        });
        expandableListView.setOnGroupCollapseListener(new ExpandableListView.OnGroupCollapseListener() {
            @Override
            public void onGroupCollapse(int groupPosition) {
            }
        });
        expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
            @Override
            public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
                return false;
            }
        });
    }
}

 

(위 Code의 실행 결과)

   

    

 

+ Recent posts