• [안드로이드 스튜디오] 목록/내용을 접었다 펼 수 있는 Expandable Recycler View 사용 예제

    2021. 7. 30.

    by. 하루플스토리

    반응형

    안녕하세요, 하루플 입니다.

     

    최근에 취미로 개발해보고 싶은 앱이 생겨 개발중인데요!

    조금 더 깔끔한 UI 개발을 고민하던 중 Expandable Recycler View 라는 것을 보았습니다!

    RecyclerView가 기존에 리스트뷰 상위호환 버전이였는데 그걸 접을 수 있게 해주는 겁니다.

     

    아래 예시로 먼저 보겠습니다.

     

    이렇게 띄워주는겁니다!

     

    여기서 작성한 소스코드는 아래 Github에서 다운로드하여 사용하실 수 있습니다.

    https://github.com/haruple97/Open-Source-Library

     

    GitHub - haruple97/Open-Source-Library: 혼자 만든 오픈소스 공간 (Feel free to use it!)

    혼자 만든 오픈소스 공간 (Feel free to use it!). Contribute to haruple97/Open-Source-Library development by creating an account on GitHub.

    github.com

     

     


    우선, 소스코드 구현전 아래 이미지를 다운로드 하여 drawble 폴더에 넣어줍니다.

    circle_plus

    circle_minus

     

     

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 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"
        tools:context=".MainActivity">
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    
    </LinearLayout>

     

     

    list_header.xml

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:paddingLeft="13dp"
        android:paddingStart="13dp"
        android:paddingRight="10dp"
        android:paddingEnd="10dp"
        android:paddingTop="5dp"
        android:paddingBottom="5dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <TextView
            android:id="@+id/header_title"
            android:textColor="#045CB1"
            android:textSize="18sp"
            android:gravity="center_vertical"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"/>
    
        <ImageView
            android:id="@+id/btn_expand_toggle"
            android:src="@drawable/circle_plus"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_weight="0"/>
    
    </LinearLayout>

     

     

     

    이제 JAVA 부분을 작성해보겠습니다.

    MainActivity.java

    public class MainActivity extends AppCompatActivity {
    
        private RecyclerView recyclerview;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            recyclerview = findViewById(R.id.recyclerview);
            recyclerview.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
            List<ExpandableListAdapter.Item> data = new ArrayList<>();
    
            data.add(new ExpandableListAdapter.Item(ExpandableListAdapter.HEADER, "모험가 마법사"));
            data.add(new ExpandableListAdapter.Item(ExpandableListAdapter.CHILD, "썬콜"));
            data.add(new ExpandableListAdapter.Item(ExpandableListAdapter.CHILD, "불독"));
            data.add(new ExpandableListAdapter.Item(ExpandableListAdapter.CHILD, "바숍"));
    
            data.add(new ExpandableListAdapter.Item(ExpandableListAdapter.HEADER, "레지스탕스"));
            data.add(new ExpandableListAdapter.Item(ExpandableListAdapter.CHILD, "메카닉"));
            data.add(new ExpandableListAdapter.Item(ExpandableListAdapter.CHILD, "배틀메이지"));
            data.add(new ExpandableListAdapter.Item(ExpandableListAdapter.CHILD, "와일드헌터"));
            data.add(new ExpandableListAdapter.Item(ExpandableListAdapter.CHILD, "블래스터"));
    
            ExpandableListAdapter.Item places = new ExpandableListAdapter.Item(ExpandableListAdapter.HEADER, "노바");
            places.invisibleChildren = new ArrayList<>();
            places.invisibleChildren.add(new ExpandableListAdapter.Item(ExpandableListAdapter.CHILD, "카이저"));
            places.invisibleChildren.add(new ExpandableListAdapter.Item(ExpandableListAdapter.CHILD, "카인"));
            places.invisibleChildren.add(new ExpandableListAdapter.Item(ExpandableListAdapter.CHILD, "카데나"));
            places.invisibleChildren.add(new ExpandableListAdapter.Item(ExpandableListAdapter.CHILD, "엔젤릭버스터"));
    
            data.add(places);
    
            recyclerview.setAdapter(new ExpandableListAdapter(data));
    
        }
    }

    리스트에 추가할 아이템들을 입력해줍니다.

    HEADER 속성을 주면 목록의 메인 헤더명으로 사용할 수 있습니다.

     

     

     

     

    ExpandableListAdapter.java

    이제 MainActivity에서 추가한 리스트를 받아주는 Adapter를 구현해보겠습니다.

    public class ExpandableListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
    
        public static final int HEADER = 0;
        public static final int CHILD = 1;
    
        private List<Item> data;
    
        public ExpandableListAdapter(List<Item> data) {
            this.data = data;
        }
    
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) {
            View view = null;
            Context context = parent.getContext();
            float dp = context.getResources().getDisplayMetrics().density;
            int subItemPaddingLeft = (int) (18 * dp);
            int subItemPaddingTopAndBottom = (int) (5 * dp);
            switch (type) {
                case HEADER:
                    LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                    view = inflater.inflate(R.layout.list_header, parent, false);
                    ListHeaderViewHolder header = new ListHeaderViewHolder(view);
                    return header;
                case CHILD:
                    TextView itemTextView = new TextView(context);
                    itemTextView.setPadding(subItemPaddingLeft, subItemPaddingTopAndBottom, 0, subItemPaddingTopAndBottom);
                    itemTextView.setTextColor(0x88000000);
                    itemTextView.setLayoutParams(
                            new ViewGroup.LayoutParams(
                                    ViewGroup.LayoutParams.MATCH_PARENT,
                                    ViewGroup.LayoutParams.WRAP_CONTENT));
                    return new RecyclerView.ViewHolder(itemTextView) {
                    };
            }
            return null;
        }
    
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            final Item item = data.get(position);
            switch (item.type) {
                case HEADER:
                    final ListHeaderViewHolder itemController = (ListHeaderViewHolder) holder;
                    itemController.refferalItem = item;
                    itemController.header_title.setText(item.text);
                    if (item.invisibleChildren == null) {
                        itemController.btn_expand_toggle.setImageResource(R.drawable.circle_minus);
                    } else {
                        itemController.btn_expand_toggle.setImageResource(R.drawable.circle_plus);
                    }
                    itemController.btn_expand_toggle.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if (item.invisibleChildren == null) {
                                item.invisibleChildren = new ArrayList<Item>();
                                int count = 0;
                                int pos = data.indexOf(itemController.refferalItem);
                                while (data.size() > pos + 1 && data.get(pos + 1).type == CHILD) {
                                    item.invisibleChildren.add(data.remove(pos + 1));
                                    count++;
                                }
                                notifyItemRangeRemoved(pos + 1, count);
                                itemController.btn_expand_toggle.setImageResource(R.drawable.circle_plus);
                            } else {
                                int pos = data.indexOf(itemController.refferalItem);
                                int index = pos + 1;
                                for (Item i : item.invisibleChildren) {
                                    data.add(index, i);
                                    index++;
                                }
                                notifyItemRangeInserted(pos + 1, index - pos - 1);
                                itemController.btn_expand_toggle.setImageResource(R.drawable.circle_minus);
                                item.invisibleChildren = null;
                            }
                        }
                    });
                    break;
                case CHILD:
                    TextView itemTextView = (TextView) holder.itemView;
                    itemTextView.setText(data.get(position).text);
                    break;
            }
        }
    
        @Override
        public int getItemViewType(int position) {
            return data.get(position).type;
        }
    
    
        @Override
        public int getItemCount() {
            return data.size();
        }
    
        private static class ListHeaderViewHolder extends RecyclerView.ViewHolder {
            public TextView header_title;
            public ImageView btn_expand_toggle;
            public Item refferalItem;
    
            public ListHeaderViewHolder(View itemView) {
                super(itemView);
                header_title = (TextView) itemView.findViewById(R.id.header_title);
                btn_expand_toggle = (ImageView) itemView.findViewById(R.id.btn_expand_toggle);
            }
        }
    
        public static class Item {
            public int type;
            public String text;
            public List<Item> invisibleChildren;
    
            public Item() {
            }
    
            public Item(int type, String text) {
                this.type = type;
                this.text = text;
            }
        }
    
    }

    여기까지만 작성하시면 잘 작동되는걸 확인하실 수 있을겁니다!

    지금까지는 text만 추가하였지만 저는 text와 imge가 함께 출력되는 CardView 형식으로 개발중입니다.

    Adapter에 이미지를 받아주는 속성을 추가하면 될 것 같은데 확실한건 구현해보고 다시 포스팅하겠습니다!

     

    모르거나 궁금한점 있으면 댓글로 부탁드립니다!

     

    참고블로그[https://dreamaz.tistory.com/223]

    반응형

    댓글