본문 바로가기

개발 이야기/Android (안드로이드)

Android RecyclerView를 이용한 페이징 기법정리

320x100

 

Room 사용을 배제한 rawQuery()를 이용하여 SQL을 직접 작성해서 사용하는 방식으로 코드 구현한 내용을 정리해 봅니다.

옛날 프로젝트는 이런 환경에서 개발했기 때문에 별도 정리해 봅니다.

 

1. SQLiteOpenHelper 설정

먼저, SQLiteOpenHelper 클래스를 설정합니다.

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class MyDatabaseHelper extends SQLiteOpenHelper {

    private static final String DATABASE_NAME = "mydatabase.db";
    private static final int DATABASE_VERSION = 1;

    public MyDatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String createTable = "CREATE TABLE my_table (" +
                "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                "name TEXT, " +
                "age INTEGER)";
        db.execSQL(createTable);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS my_table");
        onCreate(db);
    }
}

2. 데이터 삽입

데이터를 삽입하는 메서드를 작성합니다.

import android.content.ContentValues;
import android.database.sqlite.SQLiteDatabase;

public void insertData(String name, int age) {
    SQLiteDatabase db = getWritableDatabase();
    ContentValues values = new ContentValues();
    values.put("name", name);
    values.put("age", age);
    db.insert("my_table", null, values);
}

3. 페이징 데이터 조회 (rawQuery 사용)

페이징을 적용하여 데이터를 조회하는 메서드를 작성합니다.

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

public Cursor getPagedData(int offset, int limit) {
    SQLiteDatabase db = getReadableDatabase();
    String query = "SELECT * FROM my_table LIMIT ? OFFSET ?";
    Cursor cursor = db.rawQuery(query, new String[]{String.valueOf(limit), String.valueOf(offset)});
    return cursor;
}

4. RecyclerView 어댑터 설정

RecyclerView 어댑터를 설정합니다.

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;

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

    private List<MyData> dataList;
    private Context context;

    public MyAdapter(Context context) {
        this.context = context;
        this.dataList = new ArrayList<>();
    }

    public void addData(List<MyData> data) {
        int startPosition = dataList.size();
        dataList.addAll(data);
        notifyItemRangeInserted(startPosition, data.size());
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.item_layout, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        MyData data = dataList.get(position);
        holder.nameTextView.setText(data.getName());
        holder.ageTextView.setText(String.valueOf(data.getAge()));
    }

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

    static class ViewHolder extends RecyclerView.ViewHolder {
        TextView nameTextView, ageTextView;

        ViewHolder(View itemView) {
            super(itemView);
            nameTextView = itemView.findViewById(R.id.nameTextView);
            ageTextView = itemView.findViewById(R.id.ageTextView);
        }
    }
}

5. MainActivity에서 페이징 설정

MainActivity에서 페이징을 설정하고 데이터를 로드합니다.

import android.database.Cursor;
import android.os.Bundle;

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

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;
    private MyAdapter adapter;
    private RecyclerView recyclerView;
    private boolean isLoading = false;
    private int currentPage = 0;
    private static final int PAGE_SIZE = 20;

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

        dbHelper = new MyDatabaseHelper(this);
        adapter = new MyAdapter(this);
        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(adapter);

        // 데이터 삽입 (테스트용)
        for (int i = 1; i <= 1000; i++) {
            dbHelper.insertData("Name " + i, 20 + (i % 10));
        }

        loadMoreData();

        // 스크롤 리스너 설정
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
                if (!isLoading && layoutManager != null && layoutManager.findLastCompletelyVisibleItemPosition() == adapter.getItemCount() - 1) {
                    // 리스트의 끝에 도달했을 때 더 많은 데이터 로드
                    loadMoreData();
                }
            }
        });
    }

    private void loadMoreData() {
        isLoading = true;
        int offset = currentPage * PAGE_SIZE;
        Cursor cursor = dbHelper.getPagedData(offset, PAGE_SIZE);
        List<MyData> data = new ArrayList<>();
        if (cursor.moveToFirst()) {
            do {
                int id = cursor.getInt(cursor.getColumnIndexOrThrow("id"));
                String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
                int age = cursor.getInt(cursor.getColumnIndexOrThrow("age"));
                data.add(new MyData(id, name, age));
            } while (cursor.moveToNext());
        }
        cursor.close();

        adapter.addData(data);
        isLoading = false;
        currentPage++;
    }
}

6. 데이터 모델 클래스

MyData 클래스를 정의합니다.

public class MyData {
    private int id;
    private String name;
    private int age;

    public MyData(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

7. XML 레이아웃 파일

activity_main.xml

MainActivity의 레이아웃 파일을 작성합니다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp"
        android:scrollbars="vertical" />

</RelativeLayout>

 

item_layout.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:orientation="vertical"
    android:padding="8dp">

    <TextView
        android:id="@+id/nameTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:textColor="@android:color/black" />

    <TextView
        android:id="@+id/ageTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="14sp"
        android:textColor="@android:color/darker_gray" />
</LinearLayout>

 

8. 최종 코드 구조

이제 모든 코드를 종합하여 최종 구조를 확인해보겠습니다.

  1. MyDatabaseHelper.java
  2. MainActivity.java
  3. MyAdapter.java
  4. MyData.java
  5. activity_main.xml
  6. item_layout.xml

모든 코드를 종합하면 다음과 같습니다.

MyDatabaseHelper.java

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class MyDatabaseHelper extends SQLiteOpenHelper {

    private static final String DATABASE_NAME = "mydatabase.db";
    private static final int DATABASE_VERSION = 1;

    public MyDatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String createTable = "CREATE TABLE my_table (" +
                "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                "name TEXT, " +
                "age INTEGER)";
        db.execSQL(createTable);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS my_table");
        onCreate(db);
    }

    public void insertData(String name, int age) {
        SQLiteDatabase db = getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("name", name);
        values.put("age", age);
        db.insert("my_table", null, values);
    }

    public Cursor getPagedData(int offset, int limit) {
        SQLiteDatabase db = getReadableDatabase();
        String query = "SELECT * FROM my_table LIMIT ? OFFSET ?";
        Cursor cursor = db.rawQuery(query, new String[]{String.valueOf(limit), String.valueOf(offset)});
        return cursor;
    }
}

 

MainActivity.java

import android.database.Cursor;
import android.os.Bundle;

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

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;
    private MyAdapter adapter;
    private RecyclerView recyclerView;
    private boolean isLoading = false;
    private int currentPage = 0;
    private static final int PAGE_SIZE = 20;

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

        dbHelper = new MyDatabaseHelper(this);
        adapter = new MyAdapter(this);
        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(adapter);

        // 데이터 삽입 (테스트용)
        for (int i = 1; i <= 1000; i++) {
            dbHelper.insertData("Name " + i, 20 + (i % 10));
        }

        loadMoreData();

        // 스크롤 리스너 설정
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
                if (!isLoading && layoutManager != null && layoutManager.findLastCompletelyVisibleItemPosition() == adapter.getItemCount() - 1) {
                    // 리스트의 끝에 도달했을 때 더 많은 데이터 로드
                    loadMoreData();
                }
            }
        });
    }

    private void loadMoreData() {
        isLoading = true;
        int offset = currentPage * PAGE_SIZE;
        Cursor cursor = dbHelper.getPagedData(offset, PAGE_SIZE);
        List<MyData> data = new ArrayList<>();
        if (cursor.moveToFirst()) {
            do {
                int id = cursor.getInt(cursor.getColumnIndexOrThrow("id"));
                String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
                int age = cursor.getInt(cursor.getColumnIndexOrThrow("age"));
                data.add(new MyData(id, name, age));
            } while (cursor.moveToNext());
        }
        cursor.close();

        adapter.addData(data);
        isLoading = false;
        currentPage++;
    }
}

MyAdapter.java

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;

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

    private List<MyData> dataList;
    private LayoutInflater inflater;

    public MyAdapter(Context context) {
        this.dataList = new ArrayList<>();
        this.inflater = LayoutInflater.from(context);
    }

    public void addData(List<MyData> newData) {
        int startPosition = dataList.size();
        dataList.addAll(newData);
        notifyItemRangeInserted(startPosition, newData.size());
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = inflater.inflate(R.layout.item_layout, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        MyData data = dataList.get(position);
        holder.nameTextView.setText(data.getName());
        holder.ageTextView.setText(String.valueOf(data.getAge()));
    }

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

    public static class ViewHolder extends RecyclerView.ViewHolder {

        TextView nameTextView;
        TextView ageTextView;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            nameTextView = itemView.findViewById(R.id.nameTextView);
            ageTextView = itemView.findViewById(R.id.ageTextView);
        }
    }
}

 

이제 모든 파일이 준비되었습니다. 요약하면, 각 파일의 역할은 다음과 같습니다:

  1. MyDatabaseHelper.java: 데이터베이스 생성, 데이터 삽입, 페이징된 데이터 가져오기.
  2. MainActivity.java: RecyclerView 설정 및 데이터 로드 관리.
  3. MyAdapter.java: RecyclerView 어댑터로서 데이터를 표시.
  4. MyData.java: 데이터 모델 클래스.
  5. activity_main.xml: MainActivity의 레이아웃 파일.
  6. item_layout.xml: RecyclerView의 각 아이템 레이아웃 파일.

이제 이 코드를 실행하면 데이터베이스에서 페이징을 통해 데이터를 로드하고 RecyclerView에 표시할 수 있습니다.

반응형