[나중에 참고하기 위해 기록]
POST로 요청 시, Request Body 부분을 다음과 같이 한다고 가정한다.
{
"type": 0
"startidx": 1
"endidx": 40
}
응답으로 예를들어 다음과 같이 온다고 가정한다.
{
"resultcode": 0,
"message": "success",
"response": [
{
"idx": 1,
"title": "hello",
"date": "20201201"
},
{
"idx": 2,
"title": "world",
"date": "20201202"
},
{
"idx": 3,
"title": "I'm json",
"date": "20201203"
},
{
"idx": 4,
"title": "retrofit2",
"date": "20201204"
},
{
"idx": 5,
"title": "rxjava2",
"date": "20201205"
},
. . .
{
"idx": n,
"title": "final",
"date": "20201230"
}
]
}
우선, 이 형태의 json을 받기 위한 클래스를 다음과 같이 작성한다.
LoadMoreDataList.java
public class LoadMoreDataList{
@SerializedName("resultcode")
@Expose
private int resultcode;
@SerializedName("message")
@Expose
private int message;
@SerializedName("response")
@Expose
private List<LoadMoreDataListItem> items = null;
public List<LoadMoreDataListItem> getItems() { return items; }
public class LoadMoreDataListItem {
@SerializedName("idx")
@Expose
private int idx;
@SerializedName("title")
@Expose
private String title;
@SerializedName("date")
@Expose
private String date;
public int getIdx() { return idx; }
public String getTitle() { return title; }
public String getDate() { return date; }
public void setIdx(int idx) { this.idx = idx; }
public void setTitle(String title) { this.title = title; }
public void setDate(String date) { this.date = date; }
}
}
다음으로, 화면 하단에 보여줄 로딩 레이아웃을 xml로 아래 코드와 같이 작성한다.
loadmore_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:id="@+id/loadmore"
android:layout_marginVertical="20dp"
android:layout_gravity="center"
android:indeterminate="true"
android:indeterminateDrawable="@drawable/loading"/>
</LinearLayout>
다음으로 RecyclerView가 나타나는 Activity이며, RecyclerView 아이템들을 스크롤 시 불러온 모든 데이터가 다 로드되어 화면 아래에 도착하면 다음으로 로드해야할 데이터를 불러오는 곳이다. 해당 기능을 LoadMoreActivity.java에 구현했다.
mPageCnt는 아이템이 로드될 때 몇 개의 아이템을 불러올 지 선언한 변수이다.
mLoadItemCnt는 현재 불러온 아이템의 개수를 의미한다.
isLoading은 현재 로딩중인지 체크하는 변수이다.
isLastLoading은 더이상 불러올 데이터가 없을 경우 true로 바꾸는 변수이다.
LoadMoreActivity.java
private RecyclerView recyclerView;
private RecyclerAdapter adapter;
private int type = 0;
private final int mPageCnt = 40;
private int mLoadItemCnt = 40;
private boolean isLoading = false;
private boolean isLastLoading = false;
recyclerview에 scrollListener를 달아 스크롤 이벤트를 다루는 부분에 다음 데이터를 불러올 지 결정한다.
onScrolled에서 현재 로딩중인지, 혹시 더 이상 불러올 게 없는지 먼저 체크한다.
현재 로딩중을 체크하는 이유는 간혹 두번 데이터가 로드되어 같은 데이터가 중복으로 보여지는 것을 막기 위함이다.
이렇게 체크한 후, 현재 마지막 아이템에 도달했다면, 이제 데이터를 로드할 것이기 때문에 isLoading을 true로 바꾼 후, onLoadMore() 메소드를 호출하여 데이터를 불러온다.
final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
recyclerview.addOnScrollListener(new RecyclerView.OnScrollListener() {
. . .
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int lastItem = linearLayoutManager.findLastVisibleItemPosition();
if(!isLoading & !isLastLoading){
if(linearLayoutManager != null && lastItem == adapter.getItemCount() - 1){
isLoading = true;
onLoadMore(type, adapter.getItemCount());
}
}
}
});
onLoadMore() 함수에서 adapter에 있는 item을 null 변수로 하나 더 추가하여 로딩 뷰가 보여질 공간을 만든다.
그리고 adapter에게 아이템이 삽입되었다는 것을 알려준 후, startRxLoadMore() 함수를 호출한다.
private void onLoadMore(int type, int startIdx){
adapter.addItem(null);
adapter.notifyItemInserted(adapter.getItemCount() - 1);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
startRxLoadMore(type, startIdx + 1, startIdx + mPageCnt);
}, 1000);
}
startRxLoadMore() 함수에서 idx를 기준으로 Retrofit2를 활용하여 데이터를 가져온다.
데이터를 가져오면, 로딩 뷰를 띄웠던 아이템을 지우고 새로 불러온 데이터 리스트를 addAll 함수로 넣는다.
데이터 리스트를 삽입한 후, mLoadItemCnt 값을 증가시킨 후, isLoading을 false로 변경하여 로딩이 종료됨을 표시한다.
private CompositeDisposable mCompositeDisposable = new CompositeDisposable();
private LoadMoreDataList morelist;
private void startRxLoadMore(int type, int startIdx, int endIdx){
final String strName = "loadmore_test";
LoadMoreBody body = new LoadMoreBody(type, startIdx, endIdx);
RestfulServiceApi service = RestfulAdapter.getInstance().getServiceApi();
Observable<LoadMoreDataList> observable = service.getloadmoredatalist(strName, body);
mCompositeDisposable.add(observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<LoadMoreDataList>(){
@Override
public void onNext(LoadMoreDataList contributors){
morelist = new LoadMoreDataList();
morelist = contributors;
}
@Override
public void onError(Throwable e){
Log.d(TAG, "onError: " + e.toString());
}
@Override
public void onComplete(){
adapter.removeItem(adapter.getItemCount()-1);
int scrollPosition = adapter.getItemCount();
adapter.notifyItemRemoved(scrollPosition);
if(morelist.getItems().size() > 0) {
adapter.addAll(morelist);
loadChk = true;
mLoadItemCnt = scrollPosition;
mLoadItemCnt += mPageCnt;
isLastLoading = false;
} else
isLastLoading = true;
isLoading = false;
}
})
)
}
LoadMoreBody.java
public class LoadMoreBody {
private int type, startIdx, endIdx;
public LoadMoreBody(int type, int startIdx, int endIdx){
this.type = type;
this.startIdx = startIdx;
this.endIdx = endIdx;
}
}
RecyclerAdapter에서는 데이터가 로드된 아이템을 보여줄 view_type과 로딩 뷰를 보여줄 view_type을 아래와 같이 정의한다. 이를 쓰기 위해서는 getItemViewType(int position) 함수를 Override하여 재정의 해야한다.
RecyclerAdapter.java
private final int VIEW_TYPE_ITEM = 0;
private final int VIEW_TYPE_LOADING = 1;
public void addAll(LoadMoreDataList items){
this.items.getItems().addAll(items.getItems());
notifyDataSetChanged();
}
public void removeItem(int itemIdx){
this.items.getItems().remove(itemIdx);
}
public void addItem(LoadMoreDataListItem item) {
this.items.getItems().add(item);
}
@Override
public int getItemCount(){
return this.items != null ? this.items.getItems().size() : 0;
}
@Override
public int getItemViewType(int position){
return this.items.getItems().get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM;
}
@Override
public long getItemId(int position){
return this.items.getItems().get(position) != null ? this.items.getItems().get(position).getIdx() : 0;
}
private void showLoadingView(LoadingViewHolder holder){
holder.mProgressBar.setVisibility(View.VISIBLE);
}
onCreateViewHolder() 함수에서 viewType에 따라 아이템들을 보여줄 경우와 로딩뷰를 보여줄 경우를 두 가지로 나누어 아래와 같이 코드를 작성한다. 이후 onBindViewHolder() 함수를 통해 데이터를 뷰에 바인딩할 때 VIEW_TYPE_ITEM일 경우, 데이터를 바인딩하고, VIEW_TYPE_LOADING일 경우 로딩뷰를 보여주도록 구현한다.
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
if(viewType == VIEW_TYPE_ITEM){
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.adapter_item, parent, false);
return new ViewHolder(view);
} else {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.loadmore_layout, parent, false);
return new ViewHolder(view);
}
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position){
switch(getItemViewType(position)){
case VIEW_TYPE_ITEM:
ViewHolder viewHolder = (ViweHolder) holder;
bindItem(viewHolder, position);
break;
case VIEW_TYPE_LOADING:
LoadingViewHolder loadingViewHolder = (LoadingViweHolder) holder;
loadingViewHolder.mProgressBar.setIndeterminate(true);
showLoadingView(loadingViewHolder, position);
break;
}
}
private class LoadingViewHolder extends ViewHolder {
ProgressBar mProgressBar;
public LoadingViewHolder(@NonNull View itemView) {
super(itemView);
mProgressBar = itemView.findViewById(R.id.loadmore);
}
}
+)참고한 사이트
-> RecyclerView Loading Layout
class-programming.tistory.com/34
codentrick.com/load-more-recyclerview-bottom-progressbar/
-> Retrofit2 + Rxjava2
'안드로이드' 카테고리의 다른 글
[Android] 네이버 로그인 구현 (with Custom Thread) (2) | 2020.12.08 |
---|---|
[Android] Disable tooltip Text in Bottom Navigation View (0) | 2020.12.03 |
[Android] Bottom navigation view + FAB border customizing (0) | 2020.12.03 |
[Android] Powermenu android.view.windowLeaked Error (2) | 2020.12.02 |
[Android] FAB image size 조정하기 (0) | 2020.12.02 |