-
[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 변화를 주기위해선 클릭 상태를 저장해야 한다는게 핵심이라고 할 수 있겠네요~
그럼 모두 열코하세요! 궁금한점은 댓글로 남겨주세요^^
'Android > 개발' 카테고리의 다른 글
[Kotlin] Kotlin vs. Java - 코틀린, 자바 차이점 비교 (1) 2020.01.27 [RecyclerView] 안드로이드 리사이클러뷰 Header/Footer 달기 (12) 2018.11.15 [RecyclerView] 안드로이드 리사이클러뷰 아이템 클릭 (13) 2018.11.13 [RecyclerView] 안드로이드 리사이클러뷰 기본 사용법 (12) 2018.11.13 [TextView] 안드로이드 TextView의 모든 것 3 - 특정 문자열 속성 바꾸기 (2) 2018.11.12 댓글