코코딩딩

[안드로이드/android] recyclerview 접기,펼치기(아코디언) 본문

일단기록/매일기록

[안드로이드/android] recyclerview 접기,펼치기(아코디언)

겟츄 2022. 5. 2. 12:52

고객센터 기능을 하는 뷰를 구현하기 위해 다른 어플을 확인한 결과 카카오톡의 공지사항과 같은 뷰를 구현 하기로 결정하였다. recyclerview 형태의 리스트에 제목과 날짜가 출력되어 있고 각 리스트를 클릭하면 해당 리스트에 맞는 내용이 펼쳐지면서 출력되는 형태로 구현하고자 한다.

 

 

item.xml 만들기

 

 

예전해 했던 것과 마찬가지로 recyclerview의 각 리스트에 해당하는 레이아웃을 만드는데 이번에는 펼친 후 표시될 레이아웃 까지 한번에 만들어준다.

 

 

<?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:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="15dp"
        android:background="@drawable/bg_round"
        android:orientation="vertical">
        <TextView
            android:id="@+id/qna_insert_date"
            android:layout_width="match_parent"
            android:layout_height="29dp"
            android:text="2022.04.29 10:09"
            android:textSize="16sp"
            />
        <GridLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/qna_title_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textStyle="bold"
                android:text="문의내역의 제목이 표시됨"
                android:textColor="@color/black"
                android:textSize="16sp" />

        </GridLayout>
    </LinearLayout>

    <LinearLayout
        android:id="@+id/qna_detail_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:visibility="visible"
        android:background="@drawable/bg_round"
        android:orientation="vertical">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="29dp"
            android:textColor="@color/black"
            android:text="문의내용"
            android:textSize="16sp"
            />
        <TextView
            android:id="@+id/question_content_tv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="유저가 입력한 문의 내용 들어가는 곳"
            android:textSize="14sp"
            />
        <TextView
            android:layout_width="match_parent"
            android:layout_height="29dp"
            android:textColor="@color/black"
            android:text="답변내용"
            android:textSize="16sp"
            />
        <TextView
            android:id="@+id/answer_content_tv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="답변 내용이 들어가는 곳"
            android:textSize="14sp"
            />
    </LinearLayout>

</LinearLayout>

 

위와 같은 코드로 레이아웃을 구성하면 아래와 같이 나오고 처음에는 접힌 상태가 되어야 하므로 visibility="gone" 으로 변경해준다.

DTO 만들기

 

 

이전과는 다르게 리스트에 들어갈 각 item을 dto로 관리하기 위해 dto를 만들어준다.

 

 

package com.example.recycletest;

public class ItemDTO {
    String title;
    String date;
    String qContent;
    String aContent;

    public ItemDTO(String title, String date, String qContent, String aContent) {
        this.title = title;
        this.date = date;
        this.qContent = qContent;
        this.aContent = aContent;
    }

    @Override
    public String toString() {

        return "ItemDTO{" +
                "title='" + title + '\'' +
                ", date='" + date + '\'' +
                ", qContent='" + qContent + '\'' +
                ", aContent='" + aContent + '\'' +
                '}';
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getqContent() {
        return qContent;
    }

    public void setqContent(String qContent) {
        this.qContent = qContent;
    }

    public String getaContent() {
        return aContent;
    }

    public void setaContent(String aContent) {
        this.aContent = aContent;
    }
}

 

 

clickListener 만들기

 

자바 클래스를 새로 만들어 interface로 listener를 만들어준다.

 

public interface OnViewHolderItemClickListener {
    void onViewHolderItemClick();
}

 

 

adapter 만들기

 

 

이전에 했던 예제 에서는 adapter와 viewholder가 한 클래스에 있었지만 이번에는 따로 분리가 되었다.

 

import android.annotation.SuppressLint;
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 QnaRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    // item 리스트 array
    private ArrayList<ItemDTO> 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) {
        //자신이 만든 리스트의 layout 설정
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_qna_list, parent, false);
        //뷰홀더
        return new QnaViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, @SuppressLint("RecyclerView") final int position) {
        QnaViewHolder qnaViewHolder = (QnaViewHolder)holder;
        qnaViewHolder.onBind(listData.get(position),position, selectedItems);
        //클릭리스너
        qnaViewHolder.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();
    }

    // dto의 값들을 array에 추가해주는 메서드
    void addItem(ItemDTO itemData) {
        listData.add(itemData);
    }
}

 

 

ViewHolder 만들기

 

 

package com.example.recycletest;

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 QnaViewHolder extends  RecyclerView.ViewHolder {
    // 자신이 만든 레이아웃 요소들 선언
    TextView date;
    TextView title;
    TextView qContent;
    TextView aContent;
    //레이아웃 전체
    LinearLayout linearlayout;
    // 클릭하면 나올 숨겨진 레이아웃
    LinearLayout detailLayout;

    OnViewHolderItemClickListener onViewHolderItemClickListener;


    public QnaViewHolder(@NonNull View itemView) {
        super(itemView);
        date = itemView.findViewById(R.id.qna_insert_date);
        qContent = itemView.findViewById(R.id.question_content_tv);
        aContent = itemView.findViewById(R.id.answer_content_tv);
        title = itemView.findViewById(R.id.qna_title_tv);
        detailLayout = itemView.findViewById(R.id.qna_detail_layout);
        linearlayout = itemView.findViewById(R.id.linearlayout);

        //레이아웃 전체를 클릭시 리스너 호출
        linearlayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onViewHolderItemClickListener.onViewHolderItemClick();
            }
        });
    }

    public void onBind(ItemDTO itemData, int position, SparseBooleanArray selectedItems){
        //dto에 있는 값을 뷰에 set
        title.setText(itemData.getTitle());
        date.setText(itemData.getDate());
        qContent.setText(itemData.getqContent());
        aContent.setText(itemData.getaContent());
        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의 높이 변경
                detailLayout.getLayoutParams().height = (int) animation.getAnimatedValue();
                detailLayout.requestLayout();
                // imageView가 실제로 사라지게하는 부분
                detailLayout.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
            }
        });
        // Animation start
        va.start();
    }

    public void setOnViewHolderItemClickListener(OnViewHolderItemClickListener onViewHolderItemClickListener) {
        this.onViewHolderItemClickListener = onViewHolderItemClickListener;
    }
}

 

 

MainActivity

 

 

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    QnaRecyclerAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        addDTO();

    }

    private void init(){
        RecyclerView recyclerView = findViewById(R.id.recyclerView);

        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(linearLayoutManager);

        adapter = new QnaRecyclerAdapter();
        recyclerView.setAdapter(adapter);
    }

    //임의로 dto 10개 생성해서 adapter에 있는 array에 추가
    private void addDTO(){
        for(int i=0;i<10;i++){
            ItemDTO itemData = new ItemDTO("문의내용", "2022/04/29 14:00","asd","asdasd");
            adapter.addItem(itemData);
        }
    }
}

 

 

 

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>

 

 

화면

 

 

 


자신이 원하는 형태로 변환 하려면 item 레이아웃을 변환하고 레이아웃요소들의 id를 ViewHolder에 알맞게 선언해주어야 한다.

 

코드 출처

https://stickode.tistory.com/286