ABOUT ME

dddd-

Today
-
Yesterday
-
Total
-
  • [RecyclerView] 안드로이드 리사이클러뷰 접기/펼치기
    Android/개발 2018. 11. 14. 22:07


    안드로이드 RecyclerView 접기/펼치기 (Expandable RecyclerView)







     RecyclerView(리사이클러뷰)를 사용하다보면 아이템을 접고 펼치고를 구현해야 할 때가 있습니다. ListView에서는 ExpandableListView라는 기본 UI가 존재하지만 RecyclerView에서는 직접 구현해줘야 합니다.


     간단하게 아이템 클릭 시 VISIBLE, GONE만 해줘도 되지만 접기/펼치기 시 애니메이션이 들어가면 훨씬 세련된 UI가 됩니다. 그럼 RecyclerView 아이템 접기/펴기 기능(Expandable RecyclerView)을 알아보도록 하겠습니다.


     이 예제는 RecyclerView 아이템 클릭 예제를 이용하여 RecyclerView의 서브뷰를 접었다 폈다 할 수 있는 기능을 만든 예제입니다.





    RecyclerView 접기/펼치기(Expandable RecyclerView) 주의사항


     RecyclerView의 아이템을 클릭해서 UI에 변화를 줄 땐 항상 주의해야 될게 있습니다. RecyclerView는 ItemViewHolder를 사용하고 ItemViewHolder는 View의 재활용을 담당합니다. 그렇기 때문에 UI에 변화를 주고 스크롤을 하다보면 다른 position의 아이템에도 변화가 갈 수 있습니다.


     그렇기 때문에 SparseBooleanArray라는 것을 이용하여 Item의 클릭 상태를 저장합니다.






    Adapter에 SparseBooleanArray 객체 만들기


    // Item의 클릭 상태를 저장할 array 객체
    private SparseBooleanArray selectedItems = new SparseBooleanArray();
    // 직전에 클릭됐던 Item의 position
    private int prePosition = -1;


     - 이렇게 간단하게 SparseBooleanArray 객체를 만들어줍니다.

     - prePosition에 직전 클릭 아이템의 position을 저장 해줍니다.





    Item 클릭 시 클릭 상태 저장하기(SparseBooleanArray 사용)


    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;


     - 주석을 천천히 보시면 이해되실 겁니다! 이해가 안되시는 부분이 있으면 댓글로 남겨주세요~!






    Item 접기/펼치기 기능 구현


    /**
    * 클릭된 Item의 상태 변경
    * @param isExpanded Item을 펼칠 것인지 여부
    */
    private void changeVisibility(final boolean isExpanded) {
    // height 값을 dp로 지정해서 넣고싶으면 아래 소스를 이용
    int dpValue = 150;
    float d = context.getResources().getDisplayMetrics().density;
    int height = (int) (dpValue * d);

    // ValueAnimator.ofInt(int... values)는 View가 변할 값을 지정, 인자는 int 배열
    ValueAnimator va = isExpanded ? ValueAnimator.ofInt(0, height) : ValueAnimator.ofInt(height, 0);
    // Animation이 실행되는 시간, n/1000초
    va.setDuration(600);
    va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
    // value는 height 값
    int value = (int) animation.getAnimatedValue();
    // imageView의 높이 변경
    imageView2.getLayoutParams().height = value;
    imageView2.requestLayout();
    // imageView가 실제로 사라지게하는 부분
    imageView2.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
    }
    });
    // Animation start
    va.start();
    }


    - 위와 같은 메소드를 만들어 View가 Bind 되는 부분에서 호출해줍니다.





    item.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/linearItem"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="8dp"
    android:gravity="center_vertical"
    android:layout_marginBottom="8dp"
    android:background="@android:color/white">

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

    <LinearLayout
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:orientation="vertical"
    android:gravity="center_vertical">

    <TextView
    android:id="@+id/textView1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:includeFontPadding="false"
    android:text="TITLE"
    android:textSize="15sp"
    android:textColor="@android:color/black"
    android:textStyle="bold"/>

    <TextView
    android:id="@+id/textView2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:includeFontPadding="false"
    android:text="content"
    android:textSize="13sp"
    android:textColor="@android:color/darker_gray"/>
    </LinearLayout>

    <ImageView
    android:id="@+id/imageView1"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:src="@mipmap/ic_launcher"/>
    </LinearLayout>

    <ImageView
    android:id="@+id/imageView2"
    android:layout_width="match_parent"
    android:layout_height="150dp"
    android:layout_gravity="center"
    android:background="#CECECE"
    android:src="@drawable/chrysanthemum"
    android:animateLayoutChanges="true"/>
    </LinearLayout>



     - preview





    전체소스



    RecyclerView 아이템 클릭 예제에서 item.xml에 ImageView를 하나 추가하고, RecyclerAdapter.java 소스만 수정했습니다.


    RecyclerAdapter.java

    package com.imaec.forblog;

    import android.animation.ValueAnimator;
    import android.content.Context;
    import android.support.annotation.NonNull;
    import android.support.v7.widget.RecyclerView;
    import android.util.SparseBooleanArray;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ImageView;
    import android.widget.TextView;
    import android.widget.Toast;

    import java.util.ArrayList;

    public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ItemViewHolder> {

    // adapter에 들어갈 list 입니다.
    private ArrayList<Data> listData = new ArrayList<>();
    private Context context;
    // Item의 클릭 상태를 저장할 array 객체
    private SparseBooleanArray selectedItems = new SparseBooleanArray();
    // 직전에 클릭됐던 Item의 position
    private int prePosition = -1;

    @NonNull
    @Override
    public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    // LayoutInflater를 이용하여 전 단계에서 만들었던 item.xml을 inflate 시킵니다.
    // return 인자는 ViewHolder 입니다.
    context = parent.getContext();
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
    return new ItemViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {
    // Item을 하나, 하나 보여주는(bind 되는) 함수입니다.
    holder.onBind(listData.get(position), position);
    }

    @Override
    public int getItemCount() {
    // RecyclerView의 총 개수 입니다.
    return listData.size();
    }

    void addItem(Data data) {
    // 외부에서 item을 추가시킬 함수입니다.
    listData.add(data);
    }

    // RecyclerView의 핵심인 ViewHolder 입니다.
    // 여기서 subView를 setting 해줍니다.
    class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    private TextView textView1;
    private TextView textView2;
    private ImageView imageView1;
    private ImageView imageView2;
    private Data data;
    private int position;

    ItemViewHolder(View itemView) {
    super(itemView);

    textView1 = itemView.findViewById(R.id.textView1);
    textView2 = itemView.findViewById(R.id.textView2);
    imageView1 = itemView.findViewById(R.id.imageView1);
    imageView2 = itemView.findViewById(R.id.imageView2);
    }

    void onBind(Data data, int position) {
    this.data = data;
    this.position = position;

    textView1.setText(data.getTitle());
    textView2.setText(data.getContent());
    imageView1.setImageResource(data.getResId());
    imageView2.setImageResource(data.getResId());

    changeVisibility(selectedItems.get(position));

    itemView.setOnClickListener(this);
    textView1.setOnClickListener(this);
    textView2.setOnClickListener(this);
    imageView1.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
    switch (v.getId()) {
    case R.id.linearItem:
    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;
    break;
    case R.id.textView1:
    Toast.makeText(context, data.getTitle(), Toast.LENGTH_SHORT).show();
    break;
    case R.id.textView2:
    Toast.makeText(context, data.getContent(), Toast.LENGTH_SHORT).show();
    break;
    case R.id.imageView:
    Toast.makeText(context, data.getTitle() + " 이미지 입니다.", Toast.LENGTH_SHORT).show();
    break;
    }
    }

    /**
    * 클릭된 Item의 상태 변경
    * @param isExpanded Item을 펼칠 것인지 여부
    */
    private void changeVisibility(final boolean isExpanded) {
    // height 값을 dp로 지정해서 넣고싶으면 아래 소스를 이용
    int dpValue = 150;
    float d = context.getResources().getDisplayMetrics().density;
    int height = (int) (dpValue * d);

    // ValueAnimator.ofInt(int... values)는 View가 변할 값을 지정, 인자는 int 배열
    ValueAnimator va = isExpanded ? ValueAnimator.ofInt(0, height) : ValueAnimator.ofInt(height, 0);
    // Animation이 실행되는 시간, n/1000초
    va.setDuration(600);
    va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
    // value는 height 값
    int value = (int) animation.getAnimatedValue();
    // imageView의 높이 변경
    imageView2.getLayoutParams().height = value;
    imageView2.requestLayout();
    // imageView가 실제로 사라지게하는 부분
    imageView2.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
    }
    });
    // Animation start
    va.start();
    }
    }
    }






    결과


    ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓






     간단하게 RecyclerView를 접었다 폈다 할 수 있는 기능을 알아봤습니다. RecyclerView에서 아이템에 UI 변화를 주기위해선 클릭 상태를 저장해야 한다는게 핵심이라고 할 수 있겠네요~


     그럼 모두 열코하세요! 궁금한점은 댓글로 남겨주세요^^



    RecyclerView 기본예제 알아보기

    RecyclerView 아이템 클릭예제 알아보기

    RecyclerView를 이용한 프로젝트 구경하기





    댓글

Designed by Tistory.