본문 바로가기
안드로이드 자바

[JAVA][Android] 하나의 리사이클러뷰에 여러 타입의 뷰 추가하기 (Multi-View Type)

by teamnova 2024. 7. 28.
728x90

안녕하세요 

오늘은 멀티뷰 타입을 지원하는 리사이클러뷰를 만들어보겠습니다. 

 

리사이클러뷰를 사용하여 목록을 만들 때,

다르게 생긴 아이템들을 하나의 리사이클러뷰 내에서 보여주고 싶을 때가 있습니다.  

리사이클러뷰에서는 다양한 레이아웃을 지원하는 아이템들을

하나의 리스트에서 처리할 수 있도록 하는 기능을 제공합니다.

 

본 게시글에서는 View Type 을 활용하여 데이터의 타입을 구분하고 각 타입에 맞는 아이템 레이아웃을 적용시켜보도록 하겠습니다. 

 

먼저 메인 액티비티에 대한 레이아웃 입니다. 

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:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/amount_edit_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="금액을 작성해 주세요"/>

    <Spinner
        android:id="@+id/type_spinner"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:entries="@array/types_array"/>

    <Button
        android:id="@+id/add_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="작성하기"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

</LinearLayout>

 

아이템을 추가하기 위한 텍스트 입력칸, 데이터 타입을 선택하기 위한 spinner, 리사이클러뷰가 포함되어 있습니다. 

spinner 에 들어갈 내용은 string.xml 에 string arrary 로 정의해줍니다. 

 

 

<resources>
    <string name="app_name">ledger_example</string>
    <string-array name="types_array">
        <item>수입 내역</item>
        <item>지출 내역</item>
        <item>저축 내역</item>
    </string-array>
</resources>

 

 

 

 

아래는 아이템에 관한 레이아웃 파일입니다. 총 3개의 아이템을 사용해보겠습니다. 

 

item_income.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:orientation="horizontal"
    android:padding="16dp" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="수입 : "
        android:textSize="18sp"/>

    <TextView
        android:id="@+id/income_amount"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Income Amount"
        android:textSize="18sp"/>

</LinearLayout >

 

 

item_expense.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:orientation="horizontal"
    android:padding="16dp" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="지출 : "
        android:textSize="18sp"/>

    <TextView
        android:id="@+id/expense_amount"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Expense Amount"
        android:textSize="18sp"/>

</LinearLayout >

 

 

item_savings.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:orientation="horizontal"
    android:padding="16dp" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="저축 : "
        android:textSize="18sp"/>


    <TextView
        android:id="@+id/savings_amount"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Savings Amount"
        android:textSize="18sp"/>

</LinearLayout >

 

 

 

 

다음으로는 아이템 레이아웃에 들어갈 데이터를 정의한 모델 클래스입니다. 

수입, 지출, 저축 항목 각각에 대해 금액(amount)과 타입(type)을 포함합니다.

데이터 클래스를 따로 설정함으로써 RecyclerView 어댑터에서 아이템 데이터를 쉽게 관리하고 바인딩할 수 있습니다.

 

TransactionItem.java 

package com.example.ledger_example;

public class TransactionItem {
    public static final int TYPE_INCOME = 1;
    public static final int TYPE_EXPENSE = 2;
    public static final int TYPE_SAVINGS = 3;

    private int amount;
    private int type;

    public TransactionItem(int amount, int type) {
        this.amount = amount;
        this.type = type;
    }

    public int getAmount() {
        return amount;
    }

    public int getType() {
        return type;
    }
}

 

 

다음으로 리사이클러뷰 어댑터입니다. 

각각의 아이템 뷰를 초기화하고 데이터를 바인딩하며, 리사이클러뷰에 아이템을 추가하는 역할을 합니다. 

 

 


public class TransactionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final int TYPE_INCOME = 1;
    private static final int TYPE_EXPENSE = 2;
    private static final int TYPE_SAVINGS = 3;

    private ArrayList<TransactionItem> transactionList;

    public TransactionAdapter() {
        this.transactionList = new ArrayList<>();
    }


    @Override
    public int getItemViewType(int position) {
        return transactionList.get(position).getType();
    }


    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view;
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());

		// 뷰타입에 따라 각자 다른 아이템 레이아웃을 추가합니다 
        switch (viewType) {
            case TYPE_INCOME:
                view = inflater.inflate(R.layout.item_income, parent, false);
                return new IncomeViewHolder(view);
            case TYPE_EXPENSE:
                view = inflater.inflate(R.layout.item_expense, parent, false);
                return new ExpenseViewHolder(view);
            case TYPE_SAVINGS:
                view = inflater.inflate(R.layout.item_savings, parent, false);
                return new SavingsViewHolder(view);
            default:
                throw new IllegalArgumentException("Invalid view type");
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        TransactionItem item = transactionList.get(position);
        switch (holder.getItemViewType()) {
            case TYPE_INCOME:
                ((IncomeViewHolder) holder).bind(item);
                break;
            case TYPE_EXPENSE:
                ((ExpenseViewHolder) holder).bind(item);
                break;
            case TYPE_SAVINGS:
                ((SavingsViewHolder) holder).bind(item);
                break;
        }
    }

    @Override
    public int getItemCount() {
        return transactionList.size();
    }

    public void addItem(TransactionItem item) {
        transactionList.add(item);
        notifyDataSetChanged();
    }


    public static class IncomeViewHolder extends RecyclerView.ViewHolder {
        TextView incomeAmount;

        public IncomeViewHolder(@NonNull View itemView) {
            super(itemView);
            incomeAmount = itemView.findViewById(R.id.income_amount);
        }

        public void bind(TransactionItem item) {
            incomeAmount.setText(String.valueOf(item.getAmount()));
        }
    }

    public static class ExpenseViewHolder extends RecyclerView.ViewHolder {
        TextView expenseAmount;

        public ExpenseViewHolder(@NonNull View itemView) {
            super(itemView);
            expenseAmount = itemView.findViewById(R.id.expense_amount);
        }

        public void bind(TransactionItem item) {
            expenseAmount.setText(String.valueOf(item.getAmount()));
        }
    }


    public static class SavingsViewHolder extends RecyclerView.ViewHolder {
        TextView savingsAmount;

        public SavingsViewHolder(@NonNull View itemView) {
            super(itemView);
            savingsAmount = itemView.findViewById(R.id.savings_amount);
        }

        public void bind(TransactionItem item) {
            savingsAmount.setText(String.valueOf(item.getAmount()));
        }

    }

}

 

 

마지막으로 메인 액티비티입니다. 

금액 입력, 유형 선택 후 작성 버튼을 누르면 선택한 유형에 따라 뷰타입이 구분됩니다. 

어댑터에서는 사용자가 아이템을 추가하면 선택한 유형에 맞는 레이아웃에 데이터를 바인딩 하여 

리사이클러뷰에 추가해줍니다. 

addButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                String amountStr = amountEditText.getText().toString();
                if (amountStr.isEmpty()) {
                    Toast.makeText(MainActivity.this, "금액을 입력하세요", Toast.LENGTH_SHORT).show();
                    return;
                }

                int amount = Integer.parseInt(amountStr);
                String type = typeSpinner.getSelectedItem().toString();
                int typeId;

                switch (type) {
                    case "Income":
                        typeId = TransactionItem.TYPE_INCOME;
                        break;
                    case "Expense":
                        typeId = TransactionItem.TYPE_EXPENSE;
                        break;
                    case "Savings":
                        typeId = TransactionItem.TYPE_SAVINGS;
                        break;
                    default:
                        throw new IllegalArgumentException("Invalid type");
                }

                TransactionItem item = new TransactionItem(amount, typeId);
                adapter.addItem(item);
                amountEditText.setText("");

            }
        });
    }

 

 

 

아래는 메인 액티비티의 전체 코드입니다. 

 

package com.example.ledger_example;

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

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {


    private EditText amountEditText;
    private Spinner typeSpinner;
    private Button addButton;
    private RecyclerView recyclerView;
    private TransactionAdapter adapter;



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

        amountEditText = findViewById(R.id.amount_edit_text);
        typeSpinner = findViewById(R.id.type_spinner);
        addButton = findViewById(R.id.add_button);
        recyclerView = findViewById(R.id.recycler_view);

        adapter = new TransactionAdapter();
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(adapter);


        addButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                String amountStr = amountEditText.getText().toString();
                if (amountStr.isEmpty()) {
                    Toast.makeText(MainActivity.this, "금액을 입력하세요", Toast.LENGTH_SHORT).show();
                    return;
                }

                int amount = Integer.parseInt(amountStr);
                String type = typeSpinner.getSelectedItem().toString();
                int typeId;

                switch (type) {
                    case "Income":
                        typeId = TransactionItem.TYPE_INCOME;
                        break;
                    case "Expense":
                        typeId = TransactionItem.TYPE_EXPENSE;
                        break;
                    case "Savings":
                        typeId = TransactionItem.TYPE_SAVINGS;
                        break;
                    default:
                        throw new IllegalArgumentException("Invalid type");
                }

                TransactionItem item = new TransactionItem(amount, typeId);
                adapter.addItem(item);
                amountEditText.setText("");

            }
        });
    }
}

 

 

이상으로 리사이클러뷰에 멀티뷰를 추가하는 방법을 알아보았습니다. 

감사합니다 !