問題場(chǎng)景
上篇文章塘偎,我們討論了一個(gè)常規(guī)的Android頁面模型:NetFragment疗涉,完成了網(wǎng)絡(luò)加載的一系列邏輯拿霉,那么更常見的一類網(wǎng)絡(luò)數(shù)據(jù)加載頁面是列表數(shù)據(jù)展示頁。比如簡(jiǎn)書的專題展示頁:
對(duì)于這類頁面咱扣,變化的部分往往只有以下幾個(gè):
- 加載后臺(tái)數(shù)據(jù)绽淘。
- 和數(shù)據(jù)的綁定的界面是什么。
- 數(shù)據(jù)和界面怎么綁定闹伪。
不需要的變化的邏輯部分為:
- 頁面滑到頂部時(shí)沪铭,下拉刷新整體數(shù)據(jù)壮池;
- 頁面滑到底部時(shí),上拉加載下一頁數(shù)據(jù)杀怠。
- 數(shù)據(jù)都加載完了椰憋,再次上拉界面,執(zhí)行數(shù)據(jù)加載完成的接口赔退。
怎么實(shí)現(xiàn)呢橙依?
一、設(shè)計(jì)供上層實(shí)現(xiàn)的抽象接口:
/**
* 加載list數(shù)據(jù)
*
* @param startIndex 起始數(shù)據(jù)位置
* @param pageSize 預(yù)期加載多少個(gè)數(shù)據(jù)
* @return
*/
protected abstract ListNetResultInfo<O> onDoInBackgroundSafely(int startIndex, int pageSize);
/**
* 獲取對(duì)應(yīng)Item布局的id
* @return
*/
public abstract int getItemLayoutId();
/**
* 將view和數(shù)據(jù)綁定
* @param position
* @param view
* @param parent
* @return
*/
public abstract View bindView(int position, View view, ViewGroup parent);
/**
* 獲取item布局中一個(gè)textview的id (這是因?yàn)榈讓硬捎肁rrayAdapter硕旗,所以初始化時(shí)需要用到一個(gè)textview的id)
* @return
*/
public abstract int getItemTextViewResourceId();
二窗骑、 選用上拉、下拉控件
這里選擇使用開源組件BGARefreshLayout
同樣采用了約定id名的方式來進(jìn)行集成漆枚,
- ListView界面 R.id.net_listview
所以界面如下:
<?xml version="1.0" encoding="utf-8"?>
<!-- 下拉刷新创译、上拉加載下一頁樣例 -->
<cn.bingoogolapple.refreshlayout.BGARefreshLayout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/net_result"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/net_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/width_720_1280_20"
android:layout_marginRight="@dimen/width_720_1280_20"
android:clipToPadding="false"
android:divider="#EEEEEE"
android:dividerHeight="@dimen/height_720_1280_26"
android:paddingTop="@dimen/height_720_1280_25"></ListView>
</cn.bingoogolapple.refreshlayout.BGARefreshLayout>
相關(guān)的配置代碼如下:
protected void configRefreshLayout() {
mRefreshLayout = (BGARefreshLayout) mViewResult;
if (mRefreshLayout == null) {
return;
}
mListView = (ListView) mViewResult.findViewById(R.id.net_list);
// 設(shè)置下拉刷新和上拉加載更多的風(fēng)格 參數(shù)1:應(yīng)用程序上下文,參數(shù)2:是否具有上拉加載更多功能
BGARefreshViewHolder refreshViewHolder = new BGANormalRefreshViewHolder(getActivity(), true);
// 設(shè)置下拉刷新和上拉加載更多的風(fēng)格
mRefreshLayout.setRefreshViewHolder(refreshViewHolder);
mRefreshLayout.setDelegate(new BGARefreshLayout.BGARefreshLayoutDelegate() {
@Override
public void onBGARefreshLayoutBeginRefreshing(BGARefreshLayout refreshLayout) {
useSecondModel = false;
loadNetData(useSecondModel);
}
@Override
public boolean onBGARefreshLayoutBeginLoadingMore(BGARefreshLayout refreshLayout) {
if (isLoadedAllNetData){
onLoadAllNetData();
return false;
}
loadNextPageNetData();
return true;
}
});
}
三墙基、基于NetFragment封裝相應(yīng)的邏輯
- 記錄加載數(shù)據(jù)的個(gè)數(shù)和位置软族;
- 透過ArrayAdapter將數(shù)據(jù)和Listview綁定。
最后完成的類文件如下:
package lib;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import com.freetek.deepsea.R;
import java.util.ArrayList;
import java.util.List;
import cn.bingoogolapple.refreshlayout.BGAMoocStyleRefreshViewHolder;
import cn.bingoogolapple.refreshlayout.BGANormalRefreshViewHolder;
import cn.bingoogolapple.refreshlayout.BGARefreshLayout;
import cn.bingoogolapple.refreshlayout.BGARefreshViewHolder;
import panda.android.lib.base.model.ListNetResultInfo;
import panda.android.lib.base.util.Log;
/**
* 加載網(wǎng)絡(luò)List數(shù)據(jù)的通用模型残制。
*
* @author shitianci
*/
public abstract class ListNetFragment<O> extends NetFragment<ListNetResultInfo<O>> {
private static final String TAG = panda.android.lib.base.ui.fragment.ListNetFragment.class.getSimpleName();
private int mStartIndex = 0; //開始數(shù)據(jù)
private int mPageSize = 10; //起始頁的數(shù)據(jù)
private ArrayList<O> mAllDataList = new ArrayList<O>();
private boolean isLoadedAllNetData = false;
protected Parcelable mViewResultState = null;
private boolean useSecondModel = false;
private BGARefreshLayout mRefreshLayout;
private DataAdapter dataAdapter;
private ListView mListView;
/**
* -------------------------
* START: 最重要的流程方法
* -------------------------
*/
/**
* 加載list數(shù)據(jù)
*
* @param startIndex 起始數(shù)據(jù)項(xiàng)
* @param pageSize 預(yù)期加載多少項(xiàng)
* @return
*/
protected abstract ListNetResultInfo<O> onDoInBackgroundSafely(int startIndex, int pageSize);
/**
* 獲取item布局中一個(gè)textview的id
* @return
*/
public abstract int getItemTextViewResourceId();
/**
* 獲取對(duì)應(yīng)Item布局的id
* @return
*/
public abstract int getItemLayoutId();
/**
* 將view和數(shù)據(jù)綁定
* @param position
* @param view
* @param parent
* @return
*/
public abstract View bindView(int position, View view, ViewGroup parent);
/**
* -------------------------
* END
* -------------------------
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = super.onCreateView(inflater, container, savedInstanceState);
if (mViewResult instanceof BGARefreshLayout) {
configRefreshLayout();
} else {
Log.e(TAG, "沒有找到BGARefreshLayout");
}
return rootView;
}
protected void configRefreshLayout() {
mRefreshLayout = (BGARefreshLayout) mViewResult;
if (mRefreshLayout == null) {
return;
}
mListView = (ListView) mViewResult.findViewById(R.id.net_list);
// 設(shè)置下拉刷新和上拉加載更多的風(fēng)格 參數(shù)1:應(yīng)用程序上下文互订,參數(shù)2:是否具有上拉加載更多功能
BGARefreshViewHolder refreshViewHolder = new BGANormalRefreshViewHolder(getActivity(), true);
// 設(shè)置下拉刷新和上拉加載更多的風(fēng)格
mRefreshLayout.setRefreshViewHolder(refreshViewHolder);
mRefreshLayout.setDelegate(new BGARefreshLayout.BGARefreshLayoutDelegate() {
@Override
public void onBGARefreshLayoutBeginRefreshing(BGARefreshLayout refreshLayout) {
useSecondModel = false;
loadNetData(useSecondModel);
}
@Override
public boolean onBGARefreshLayoutBeginLoadingMore(BGARefreshLayout refreshLayout) {
if (isLoadedAllNetData){
onLoadAllNetData();
return false;
}
loadNextPageNetData();
return true;
}
});
}
/**
* 顯示list數(shù)據(jù)
*
* @param list 最近一次交互獲取的數(shù)據(jù)
*/
private void displayResult(List<O> list){
Log.d(TAG, "displayResult: " + list.toString());
if (dataAdapter == null) {
dataAdapter = new DataAdapter(getActivity());
mListView.setAdapter(dataAdapter);
}
android.util.Log.d(TAG, "displayResult: " + list.toString());
for (int i = 0; i < list.size(); i++) {
dataAdapter.add(list.get(i));
}
dataAdapter.notifyDataSetChanged();
}
//不同的訂單的適配器
public class DataAdapter extends ArrayAdapter<O> {
public DataAdapter(Context context) {
super(context, getItemLayoutId(), getItemTextViewResourceId());
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return bindView(position, super.getView(position, convertView, parent), parent);
}
}
public O getItem(int position) {
if(dataAdapter == null){
Log.d(TAG, "dataAdapter 沒有初始化");
return null;
}
return dataAdapter.getItem(position);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mRefreshLayout.beginRefreshing();
}
@Override
public void onPreloadNetData(boolean useSecondModel) {
super.onPreloadNetData(useSecondModel);
if (!useSecondModel){
mStartIndex = 0;
mAllDataList.clear();
isLoadedAllNetData = false;
}
mRefreshLayout.setVisibility(View.VISIBLE);
//保存位置狀態(tài)
if (mViewResult != null) {
if (mViewResult instanceof AbsListView) {
mViewResultState = ((AbsListView) mViewResult.findViewById(R.id.net_list)).onSaveInstanceState();
Log.d(TAG, "onSaveInstanceState, mViewResultState = " + mViewResultState);
}
}
}
@Override
public void onPostloadNetData(boolean useSecondModel) {
super.onPostloadNetData(useSecondModel);
if(useSecondModel){
mRefreshLayout.endLoadingMore();
}else{
mRefreshLayout.endRefreshing();
}
}
/**
* 加載下一頁數(shù)據(jù)
*/
public void loadNextPageNetData() {
Log.d(TAG, "loadNextPageNetData, isLoadedAllNetData = " + isLoadedAllNetData);
if (mStartIndex != mAllDataList.size()) {
Log.w(TAG, "mStartIndex = " + mStartIndex);
Log.w(TAG, "mAllDataList.size() = " + mAllDataList.size());
mStartIndex = mAllDataList.size();
}
useSecondModel = true;
super.loadNetData(useSecondModel);
}
/**
* 所有數(shù)據(jù)加載完畢
*/
public void onLoadAllNetData() {
Log.d(TAG, "所有數(shù)據(jù)加載完畢");
}
@Override
protected ListNetResultInfo<O> onDoInBackgroundSafely() {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return onDoInBackgroundSafely(mStartIndex, mPageSize);
}
final protected void onDisplayResult(ListNetResultInfo<O> result) {
if (result.getList() == null || result.getList().size() == 0) {
showOnlyView(R.id.net_no_result, useSecondModel);
return;
}
if (result.getList().size() < mPageSize) {
Log.w(TAG, "onLoadAllNetData");
isLoadedAllNetData = true;
onLoadAllNetData();
}
List<O> list = result.getList();
for (O o : list) {
mAllDataList.add(o);
}
mStartIndex += list.size();
displayResult(list);
//恢復(fù)原來的位置狀態(tài)
if (mViewResult != null) {
if (mViewResult instanceof AbsListView) {
Log.d(TAG, "onRestoreInstanceState, mViewResultState = " + mViewResultState);
((AbsListView) mViewResult).onRestoreInstanceState(mViewResultState);
}
}
}
/**
* 獲取每一頁數(shù)據(jù)的大小
*
* @return
*/
public int getPageSize() {
return mPageSize;
}
/**
* 設(shè)置每一頁數(shù)據(jù)的大小
*
* @param mPageSize
*/
public void setPageSize(int mPageSize) {
this.mPageSize = mPageSize;
}
/**
* 獲取下一次加載的其實(shí)位置
*
* @return
*/
public int getStartIndex() {
return mStartIndex;
}
/**
* 設(shè)置下一次加載的其實(shí)位置
*
* @param mStartIndex
*/
public void setStartIndex(int mStartIndex) {
this.mStartIndex = mStartIndex;
}
/**
* @return the mAllDataList
*/
public ArrayList<O> getAllDataList() {
return mAllDataList;
}
/**
* @param mAllDataList the mAllDataList to set
*/
public void setAllDataList(ArrayList<O> mAllDataList) {
this.mAllDataList = mAllDataList;
}
}
上層怎么使用呢?
- 界面里面增加BGARefreshLayout和ListView痘拆;
<?xml version="1.0" encoding="utf-8"?>
<!-- 下拉刷新仰禽、上拉加載下一頁樣例 -->
<cn.bingoogolapple.refreshlayout.BGARefreshLayout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/net_result"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/net_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/width_720_1280_20"
android:layout_marginRight="@dimen/width_720_1280_20"
android:clipToPadding="false"
android:divider="#EEEEEE"
android:dividerHeight="@dimen/height_720_1280_26"
android:paddingTop="@dimen/height_720_1280_25"></ListView>
</cn.bingoogolapple.refreshlayout.BGARefreshLayout>
- 頁面的Fragment繼承ListNetFragment,實(shí)現(xiàn)四個(gè)抽象方法即可纺蛆。
小小吐槽一下~
BGARefreshLayout這個(gè)組件有一個(gè)open的bug——正在刷新或加載更多時(shí)吐葵,用戶上下滑動(dòng)不會(huì)讓下拉刷新視圖和加載更多視圖跟著滑動(dòng)
,效果有點(diǎn)挫桥氏,需要優(yōu)化温峭。
Panda
2016-06-12