프로그래밍/안드로이드

[안드로이드] ListView paging ( 리스트뷰 스크롤이 맨 하단으로 갔을 때 페이징 )처리

오치리일상 2017. 8. 8. 17:00

안드로이드에 가장 많이 쓰이는 위젯(widget) 중 하나가 리스트뷰(ListView)이다.

스마트폰이라는 작은 화면에 많은 데이터를 보여주기 위한 방법 중 하나로 리스트뷰가 사용된다.

하지만 아무리 좋은 리스트뷰라도 한번에 많은 데이터를 몽땅 불러온다면, 로딩시간이 오래걸릴 수 밖에 없다.

그래서 일정한 갯수의 데이터를 정해놓고, 로딩하는 것이 효율적이다.

그러기 위해서는 리스트뷰가 맨 하단(ListView의 마지막 셀)에 스크롤(scroll) 되었을때가 언제인지를 캐치하여 그 다음 데이터를 불러오는것을 알아보도록 한다.

 

 

안드로이드 앱의 ListView에서 데이터 20개씩 페이징 하는 영상

 

 

우선 ListView와 Adapter에서 UI로 사용 할 xml을 먼저 정의한다.

 

 

 

* listview_paging.xml - 리스트뷰가 있는 메인 액티비티의 UI를 작성한다.

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

    <!--ListView 끝에서 다음 데이터 로딩중에 보여줄 프로그레스바-->
    <ProgressBar
        android:id="@+id/progressbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"/>
    
    <!--ListView를 정의한다.-->
    <ListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/progressbar"
        />

</RelativeLayout>

 

 

* row_listview.xml - 리스트뷰의 아답터의 각 셀에 적용시킬 UI를 작성한다.

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

    <!--ListView의 각 셀에 데이터가 들어갈 텍스트 라벨-->
    <TextView
        android:id="@+id/label"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:background="#ffffff"
        android:gravity="center"
        android:text="test"
        android:textColor="#000000"
        android:textSize="20dp" />


</LinearLayout>

 

 

다음은 java 파일를 작성한다.

 

*ListViewAdapter.java - 리스트뷰에 연결할 리스트뷰 아답터 코드를 작성한다.

package com.studio572.listviewpaging;

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

import java.util.List;

/**
 * Created by Administrator on 2016-10-13.
 */
public class ListViewAdapter extends BaseAdapter {
    private LayoutInflater inflate;
    private ViewHolder viewHolder;
    private List<String> list;

    public ListViewAdapter(Context context, List<String> list){
        // MainActivity 에서 데이터 리스트를 넘겨 받는다.
        this.list = list;
        this.inflate = LayoutInflater.from(context);
    }
    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public Object getItem(int i) {
        return null;
    }

    @Override
    public long getItemId(int i) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup viewGroup) {
        if(convertView == null){
            convertView = inflate.inflate(R.layout.row_listview,null);
            viewHolder = new ViewHolder();
            viewHolder.label = (TextView) convertView.findViewById(R.id.label);
            convertView.setTag(viewHolder);
        }else{
            viewHolder = (ViewHolder)convertView.getTag();
        }

        // 각 셀에 넘겨받은 텍스트 데이터를 넣는다.
        viewHolder.label.setText( list.get(position) );
        return convertView;
    }

    class ViewHolder{
        public TextView label;
    }
}

 

 

* MainActivity.java - 리스트뷰의 페이징을 적용하는 메인 액티비티.

 

package com.studio572.listviewpaging;

import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.ProgressBar;

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

public class MainActivity extends AppCompatActivity implements AbsListView.OnScrollListener  {

    private ListView listView;                      // 리스트뷰
    private boolean lastItemVisibleFlag = false;    // 리스트 스크롤이 마지막 셀(맨 바닥)로 이동했는지 체크할 변수
    private List<String> list;                      // String 데이터를 담고있는 리스트
    private ListViewAdapter adapter;                // 리스트뷰의 아답터
    private int page = 0;                           // 페이징변수. 초기 값은 0 이다.
    private final int OFFSET = 20;                  // 한 페이지마다 로드할 데이터 갯수.
    private ProgressBar progressBar;                // 데이터 로딩중을 표시할 프로그레스바
    private boolean mLockListView = false;          // 데이터 불러올때 중복안되게 하기위한 변수

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

        listView = (ListView) findViewById(R.id.listview);
        progressBar = (ProgressBar) findViewById(R.id.progressbar);

        list = new ArrayList<String>();
        adapter = new ListViewAdapter(this, list);
        listView.setAdapter(adapter);

        progressBar.setVisibility(View.GONE);


        listView.setOnScrollListener(this);
        getItem();
    }

    @Override
    public void onScrollStateChanged(AbsListView absListView, int scrollState) {
        // 1. OnScrollListener.SCROLL_STATE_IDLE : 스크롤이 이동하지 않을때의 이벤트(즉 스크롤이 멈추었을때).
        // 2. lastItemVisibleFlag : 리스트뷰의 마지막 셀의 끝에 스크롤이 이동했을때.
        // 3. mLockListView == false : 데이터 리스트에 다음 데이터를 불러오는 작업이 끝났을때.
        // 1, 2, 3 모두가 true일때 다음 데이터를 불러온다.
        if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE && lastItemVisibleFlag && mLockListView == false) {
            // 화면이 바닦에 닿을때 처리
            // 로딩중을 알리는 프로그레스바를 보인다.
            progressBar.setVisibility(View.VISIBLE);
            
            // 다음 데이터를 불러온다.
            getItem();
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        // firstVisibleItem : 화면에 보이는 첫번째 리스트의 아이템 번호.
        // visibleItemCount : 화면에 보이는 리스트 아이템의 갯수
        // totalItemCount : 리스트 전체의 총 갯수
        // 리스트의 갯수가 0개 이상이고, 화면에 보이는 맨 하단까지의 아이템 갯수가 총 갯수보다 크거나 같을때.. 즉 리스트의 끝일때. true
        lastItemVisibleFlag = (totalItemCount > 0) && (firstVisibleItem + visibleItemCount >= totalItemCount);
    }

    private void getItem(){

        // 리스트에 다음 데이터를 입력할 동안에 이 메소드가 또 호출되지 않도록 mLockListView 를 true로 설정한다.
        mLockListView = true;

        // 다음 20개의 데이터를 불러와서 리스트에 저장한다.
        for(int i = 0; i < 20; i++){
            String label = "Label " + ((page * OFFSET) + i);
            list.add(label);
        }
        
        // 1초 뒤 프로그레스바를 감추고 데이터를 갱신하고, 중복 로딩 체크하는 Lock을 했던 mLockListView변수를 풀어준다.
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                page++;
                adapter.notifyDataSetChanged();
                progressBar.setVisibility(View.GONE);
                mLockListView = false;
            }
        },1000);
    }
}

 

 

한번에 20개씩 보여주므로, 0부터 시작하여 20번째인 "Label 19"에서 프로그래스바로 로딩중임을 보여준다