코딩스토리

[Android/안드로이드] Expandable RecyclerView 접기/펼치기(아코디언) 본문

Android/유용한 기술

[Android/안드로이드] Expandable RecyclerView 접기/펼치기(아코디언)

라크라꾸 2019. 12. 20. 17:55

이전 글에서 리사이클러뷰 사용법에 대해 설명을 했었습니다.

이번 글에서는 리사이클러뷰를 클릭했을 때 접고, 펼칠 수 있는 기능을 추가해 보도록 하겠습니다.

 

이전 글에서 만들어 놓았던 DataMovie클래스와 MainActivity클래스는 수정할 부분이 없으므로 생략하고 수정사항이 있는 부분말 적어놓도록 하겠습니다. DataMovie클래스와 MainActivity부분이 없으신 분들은 이전 글을 참조해주세요.

 

2019/12/19 - [Android/유용한 기술] - [Android/안드로이드] RecyclerView(리사이클러뷰) 뷰 재활용하기

 

[Android/안드로이드] RecyclerView(리사이클러뷰) 뷰 재활용하기

기존의 ListView는 커스터마이징 하기에도 힘들었고, 구조적인 문제로 성능상의 문제가 있었기 때문에 보다 유연하고 성능이 향상된 RecyclerView가 생겨났습니다. RecyclerView와 ListView의 가장 큰 차이점은 리..

lakue.tistory.com

 

우선 뷰홀더에 있는 버튼을 클릭했을 경우 Adapter에서도 알 수 있도록 이벤트 리스너를 만들겠습니다.

 

이벤트 리스너 생성

OnViewHolderItemClickListener.java

  
  package com.example.recyclerviewtest;
  
  public interface OnViewHolderItemClickListener {
      void onViewHolderItemClick();
  }
  

 

 

이제 Adapter에서 클릭했을 때 접고, 펼칠 수 있도록 아코디언 형식을 만들어 보겠습니다.

 

RecyclerVierAdapter.java

    
    package com.lakue.recyclerviewarcodian;
    
    import android.util.SparseBooleanArray;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    
    import androidx.annotation.NonNull;
    import androidx.recyclerview.widget.RecyclerView;
    
    import java.util.ArrayList;
    
    public class RecyclerVierAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    
        // adapter에 들어갈 list 입니다.
        private ArrayList<DataMovie> listData = new ArrayList<>();
    
        // Item의 클릭 상태를 저장할 array 객체
        private SparseBooleanArray selectedItems = new SparseBooleanArray();
        // 직전에 클릭됐던 Item의 position
        private int prePosition = -1;
    
    
        @NonNull
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_movie, parent, false);
            return new ViewHolderMovie(view);
        }
    
        @Override
        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
            ViewHolderMovie viewHolderMovie = (ViewHolderMovie)holder;
            viewHolderMovie.onBind(listData.get(position),position, selectedItems);
            viewHolderMovie.setOnViewHolderItemClickListener(new OnViewHolderItemClickListener() {
                @Override
                public void onViewHolderItemClick() {
                    if (selectedItems.get(position)) {
                        // 펼쳐진 Item을 클릭 시
                        selectedItems.delete(position);
                    } else {
                        // 직전의 클릭됐던 Item의 클릭상태를 지움
                        selectedItems.delete(prePosition);
                        // 클릭한 Item의 position을 저장
                        selectedItems.put(position, true);
                    }
                    // 해당 포지션의 변화를 알림
                    if (prePosition != -1) notifyItemChanged(prePosition);
                    notifyItemChanged(position);
                    // 클릭된 position 저장
                    prePosition = position;
                }
            });
        }
    
        @Override
        public int getItemCount() {
            return listData.size();
        }
    
        void addItem(DataMovie data) {
            // 외부에서 item을 추가시킬 함수입니다.
            listData.add(data);
        }
    }
    

이전 소스코드와 다른점은 SparseBooleanArray형의 객체를 생성했고, 이전에 클릭했던 아이템의 position을 저장하는 int형 prePosition 변수를 생성했습니다.

onBind부분에서 아까 생성했던 selectedItem객체의 position번째값을 넘겨주도록 합니다.

setOnViewHolderItemClickListener부분은 이제 ViewHolder에서 해당 Item을 클릭했을 경우 발생하는 리스너입니다.

클릭했을 경우 사용되는 기능은 주석을 통해 설명하였습니다.

 

ViewHolderMovie.java

    
    package com.lakue.recyclerviewarcodian;
    
    import android.animation.ValueAnimator;
    import android.util.SparseBooleanArray;
    import android.view.View;
    import android.widget.ImageView;
    import android.widget.LinearLayout;
    import android.widget.TextView;
    
    import androidx.annotation.NonNull;
    import androidx.recyclerview.widget.RecyclerView;
    
    public class ViewHolderMovie extends  RecyclerView.ViewHolder {
    
        TextView tv_movie_title;
        ImageView iv_movie,iv_movie2;
        LinearLayout linearlayout;
    
        OnViewHolderItemClickListener onViewHolderItemClickListener;
    
    
        public ViewHolderMovie(@NonNull View itemView) {
            super(itemView);
    
            iv_movie = itemView.findViewById(R.id.iv_movie);
            tv_movie_title = itemView.findViewById(R.id.tv_movie_title);
            iv_movie2 = itemView.findViewById(R.id.iv_movie2);
            linearlayout = itemView.findViewById(R.id.linearlayout);
    
            linearlayout.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onViewHolderItemClickListener.onViewHolderItemClick();
                }
            });
        }
    
        public void onBind(DataMovie data,int position, SparseBooleanArray selectedItems){
            tv_movie_title.setText(data.getTitle());
            iv_movie.setImageResource(data.getImage());
            iv_movie2.setImageResource(data.getImage());
            changeVisibility(selectedItems.get(position));
        }
    
        private void changeVisibility(final boolean isExpanded) {
            // ValueAnimator.ofInt(int... values)는 View가 변할 값을 지정, 인자는 int 배열
            ValueAnimator va = isExpanded ? ValueAnimator.ofInt(0, 600) : ValueAnimator.ofInt(600, 0);
            // Animation이 실행되는 시간, n/1000초
            va.setDuration(500);
            va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    // imageView의 높이 변경
                    iv_movie2.getLayoutParams().height = (int) animation.getAnimatedValue();
                    iv_movie2.requestLayout();
                    // imageView가 실제로 사라지게하는 부분
                    iv_movie2.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
                }
            });
            // Animation start
            va.start();
        }
    
        public void setOnViewHolderItemClickListener(OnViewHolderItemClickListener onViewHolderItemClickListener) {
            this.onViewHolderItemClickListener = onViewHolderItemClickListener;
        }
    }
    

이전 글과는 UI가 조금 바뀌습니다. 아까 생성했던 이벤트 리스너를 선언해주고, 레이아웃을 클릭했을 때 onViewHolderItemClickListener.onViewHolderItemClick()를 호출하여 이벤트를 발생시킵니다. 발생된 이벤트는 RecyclerViewAdapter에서 setOnViewHolderItemClickListener로 받아서 처리하였습니다.

 

changeVisibility(final boolean isExpanded)함수에서는 adapter에서 받아온 isSelect값이 true이면 이미지를 보여주고, false일 경우 이미지를 gone시켜주는 기능을 하고있습니다.

 

item_movie.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/linearlayout"
    android:orientation="vertical">


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

        <ImageView
            android:id="@+id/iv_movie"
            android:scaleType="centerCrop"
            android:layout_width="100dp"
            android:layout_height="80dp"/>

        <TextView
            android:padding="20dp"
            android:gravity="center"
            android:textSize="16dp"
            android:id="@+id/tv_movie_title"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    </LinearLayout>

    <ImageView
        android:visibility="gone"
        android:id="@+id/iv_movie2"
        android:scaleType="centerCrop"
        android:layout_width="match_parent"
        android:layout_height="200dp"/>

</LinearLayout>

 

결과화면

 

다음과 같이 아이템을 클릭하면 RecyclerView를 접었다 폈다 할 수 있는 기능을 구현해보았습니다. 

 

궁금하신점이나 제가 실수한 부분이 있다면 댓글로 남겨주세요~~

 

코드작동 안되시는분들을 위해 Git에 저장해놓았습니다. 참고해주세요~~

https://github.com/lakue119/RecyclerVIewAccordion

13 Comments
  • 프로필사진 2020.05.31 00:35 안드로이드 스튜디오로 제가 지금 어플을 만들고 있는데 여쭤볼 것이 있는데 혹시 메일 알려주실 수 있나용 ??
  • 프로필사진 2020.05.31 01:10 비밀댓글입니다
  • 프로필사진 sss 2020.06.11 18:02 안녕하세요! 안드로이드 스튜디오로 공부하는 학생입니다. iv_movie2와 linearlayout은 어떻게 생성하셨나요?...아예 처음 접해서 만들어보려햇는데 안되고 설명이 없어서요 ㅠㅠ
  • 프로필사진 라크라꾸 2020.06.11 23:01 신고 안녕하세요. 접기/펼치기 기능을 중점으로 만들어서 레이아웃에 신경을 못썻네요 ㅠㅠ 회사에서 급하게 만드느라 이게 맞는지 잘 모르겠지만 기억을더듬어 얼추 비슷하게 만들어보았습니다.

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/linearlayout"
    android:orientation="vertical">


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

    <ImageView
    android:id="@+id/iv_movie"
    android:scaleType="centerCrop"
    android:layout_width="100dp"
    android:layout_height="80dp"/>

    <TextView
    android:padding="20dp"
    android:gravity="center"
    android:textSize="16dp"
    android:id="@+id/tv_movie_title"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

    </LinearLayout>

    <ImageView
    android:id="@+id/iv_movie2"
    android:scaleType="centerCrop"
    android:layout_width="match_parent"
    android:layout_height="200dp"/>

    </LinearLayout>

    한번 확인하시고 안되면 다시 댓글 남겨주시면 집에서 다시한번 정리하겠습니다 ㅠㅠ
  • 프로필사진 SSS 2020.06.12 17:48 네!! 해결됬습니다 감사합니다 ~!!!
  • 프로필사진 솜사탕 2020.06.20 00:02 RecyclerVierAdapter에서 onBind에 selectedItems.get(position)이 boolean 타입이라고 int로 바꾸라고 에러가 뜹니다. 그래서 int로 바꿔줬는데도 빨간줄이 사라지지 않습니다. 뭐가 문제일까요??
  • 프로필사진 라크라꾸 2020.06.20 00:26 혹시 ViewHolderMovie클래스에서 onBind에서 받는 파라미터가 int형으로되어있는게 아닐까요..? 오류로그좀 볼수있을까요??
  • 프로필사진 솜사탕 2020.06.20 00:31 onBind() in ViewHolderMovie cannot be applied to:
    Expected Parameters:
    Actual Arguments:

    data:
    DataMovie
    listData.get(position)  
    position:
    int
    selectedItems.get(position)  (boolean)
    selectedItems:

    SparseBooleanArray

    오류 로그가 마우스 올리면 박스로 떠서 복사해서 올려드려요 사진은 안올라가네요ㅠㅠ
    일단 저 부분은 int로 형변환했는데 onBind에서 3번째 파라미터가 없어서 에러가 뜨는 거 같네요 3번째 파라미터로는 뭘 넣어야 하나요??
  • 프로필사진 라크라꾸 2020.06.20 01:01 onBind(DataMovie data,int position, SparseBooleanArray selectedItems)

    보시면 첫번째 파라미터에는 데이터,
    두번째 파라미터에는 뷰홀더의 포지션(int)형
    세번째 파라미터에는 SparseBooleanArray 형식의 데이터가 필요합니다. 중간에 position 파라미터값을 넣어주지 않아서 나는 에러같습니다.
  • 프로필사진 솜사탕 2020.06.20 01:24 viewHolderMovie.onBind(listData.get(position),selectedItems.get(position));

    위에 글에는 이렇게 되어있는데 가운데 position 파라미터값을 넣으면 되는건가요???
  • 프로필사진 라크라꾸 2020.06.20 01:28 신고 앗 그러네요... 제가 소스코드수정하면서 깜빡한것같아요 죄송해요ㅜㅜ position값을 수정해주시면 될것같습니다. 지적 감사드립니다~~
  • 프로필사진 솜사탕 2020.06.20 01:44 계속 질문해서 죄송해요ㅠㅠㅠ position 값을 넣었더니 selectedItems.get(position) 이 부분이 에러가 떠요 ㅠㅠ

    Wrong 3rd argument type. Found: 'boolean', required: 'android.util.SparseBooleanArray
    이런 에러가 뜨는데 분명 boolean 타입인데 뭐가 문제일까요???
    혹시 저 부분 수정해주실 수 있나요 ㅠㅠ
  • 프로필사진 라크라꾸 2020.06.20 16:00 신고 변경사항이 많았네요 제가 더 죄송하죠 ㅠㅠ 수정했습니다. 혹시 안되시는부분 있을까봐 깃허브에 올려놓았으니 참고 부탁드립니다~
댓글쓰기 폼