问题场景
上篇文章,我们讨论了一个常规的Android页面模型:NetFragment,完成了网络加载的一系列逻辑,那么更常见的一类网络数据加载页面是列表数据展示页。比如简书的专题展示页:
对于这类页面,变化的部分往往只有以下几个:
- 加载后台数据。
- 和数据的绑定的界面是什么。
- 数据和界面怎么绑定。
不需要的变化的逻辑部分为:
- 页面滑到顶部时,下拉刷新整体数据;
- 页面滑到底部时,上拉加载下一页数据。
- 数据都加载完了,再次上拉界面,执行数据加载完成的接口。
怎么实现呢?
一、设计供上层实现的抽象接口:
/**
* 加载list数据
*
* @param startIndex 起始数据位置
* @param pageSize 预期加载多少个数据
* @return
*/
protected abstract ListNetResultInfo<O> onDoInBackgroundSafely(int startIndex, int pageSize);
/**
* 获取对应Item布局的id
* @return
*/
public abstract int getItemLayoutId();
/**
* 将view和数据绑定
* @param position
* @param view
* @param parent
* @return
*/
public abstract View bindView(int position, View view, ViewGroup parent);
/**
* 获取item布局中一个textview的id (这是因为底层采用ArrayAdapter,所以初始化时需要用到一个textview的id)
* @return
*/
public abstract int getItemTextViewResourceId();
二、 选用上拉、下拉控件
这里选择使用开源组件BGARefreshLayout
同样采用了约定id名的方式来进行集成,
- 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>
相关的配置代码如下:
protected void configRefreshLayout() {
mRefreshLayout = (BGARefreshLayout) mViewResult;
if (mRefreshLayout == null) {
return;
}
mListView = (ListView) mViewResult.findViewById(R.id.net_list);
// 设置下拉刷新和上拉加载更多的风格 参数1:应用程序上下文,参数2:是否具有上拉加载更多功能
BGARefreshViewHolder refreshViewHolder = new BGANormalRefreshViewHolder(getActivity(), true);
// 设置下拉刷新和上拉加载更多的风格
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封装相应的逻辑
- 记录加载数据的个数和位置;
- 透过ArrayAdapter将数据和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;
/**
* 加载网络List数据的通用模型。
*
* @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; //开始数据
private int mPageSize = 10; //起始页的数据
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数据
*
* @param startIndex 起始数据项
* @param pageSize 预期加载多少项
* @return
*/
protected abstract ListNetResultInfo<O> onDoInBackgroundSafely(int startIndex, int pageSize);
/**
* 获取item布局中一个textview的id
* @return
*/
public abstract int getItemTextViewResourceId();
/**
* 获取对应Item布局的id
* @return
*/
public abstract int getItemLayoutId();
/**
* 将view和数据绑定
* @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);
// 设置下拉刷新和上拉加载更多的风格 参数1:应用程序上下文,参数2:是否具有上拉加载更多功能
BGARefreshViewHolder refreshViewHolder = new BGANormalRefreshViewHolder(getActivity(), true);
// 设置下拉刷新和上拉加载更多的风格
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数据
*
* @param list 最近一次交互获取的数据
*/
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);
//保存位置状态
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();
}
}
/**
* 加载下一页数据
*/
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);
}
/**
* 所有数据加载完毕
*/
public void onLoadAllNetData() {
Log.d(TAG, "所有数据加载完毕");
}
@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);
//恢复原来的位置状态
if (mViewResult != null) {
if (mViewResult instanceof AbsListView) {
Log.d(TAG, "onRestoreInstanceState, mViewResultState = " + mViewResultState);
((AbsListView) mViewResult).onRestoreInstanceState(mViewResultState);
}
}
}
/**
* 获取每一页数据的大小
*
* @return
*/
public int getPageSize() {
return mPageSize;
}
/**
* 设置每一页数据的大小
*
* @param mPageSize
*/
public void setPageSize(int mPageSize) {
this.mPageSize = mPageSize;
}
/**
* 获取下一次加载的其实位置
*
* @return
*/
public int getStartIndex() {
return mStartIndex;
}
/**
* 设置下一次加载的其实位置
*
* @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,实现四个抽象方法即可。
小小吐槽一下~
BGARefreshLayout这个组件有一个open的bug——正在刷新或加载更多时,用户上下滑动不会让下拉刷新视图和加载更多视图跟着滑动
,效果有点挫,需要优化。
Panda
2016-06-12