博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
怎样使用ListView实现一个带有网络请求,解析,分页,缓存的公共的List页面来大大的提高工作效率...
阅读量:6733 次
发布时间:2019-06-25

本文共 15128 字,大约阅读时间需要 50 分钟。

在寻常的开发中常常会有非常多列表页面。每做一个列表页就须要创建这个布局文件那个Adapter适配器文件等等一大堆与之相关的附属的不必要的冗余文件。

假设版本号更新迭代比較频繁,如此以往,就会使项目project变得无比庞大臃肿。

假设看过这篇文章或者在使用过这样的方式之后呢,全部的工作都能够被压缩成仅仅有两个文件,一个JAVA文件一个XML布局文件。

并且代码还少少的。

咱们来看看实际情况:

寻常的一个列表页面的生成须要下面文件:

  • 一个Activity文件。有时候可能还会忘记注冊
  • 一个包括上下拉刷新控件以及无数据时提示的布局文件
  • 一个Listview的item的布局文件
  • 一个Adapter适配器文件
  • 一个须要被解析的Bean文件
当然在Activity中还须要处理下面功能:
  • 数据解析
  • 分页载入
  • 数据缓存
  • 网络请求
当然,以上功能能够通过频繁的复制粘贴来实现,可是这会给以后的维护留下不少不小的坑,比方突然须要更换Listview为Recyclerview,你是不是顿时就想哭了呢?是不是就得一个个文件去改呢?如今不用了,改俩地方即可了。
如今你可能想知道一个公共的List页有什么特点呢?
  • 无需再关心网络请求、数据解析、分页、缓存等同样的功能
  • 不须要写那么多的同样的布局文件,仅仅用写那些不同的item布局文件就能够。仅仅须要关心你关心的
  • 仅仅会有一个Adapter适配器,一个ViewHolder存在,Activity也能够仅仅有一个
  • 可复用性超强,不管是Activity中展示,还是在被要求放在ViewPager中显示都没问题
  • 大大减小项目的project文件数量。提高编译速度,不用再把一天的时间都浪费在编译时间上
  • 提高你的工作效率。不用再复制粘贴,那个时间没有这个快。仅仅用实现你的getView方法就能够
  • 降低维护成本。假设某一天须要在网络请求加上某个參数,曾经的方法须要改无数个地方,而如今仅仅用改一个地方就OK,假设如探须要更改上下拉刷新控件,比方须要将XListView改成PullToRefreshListView。你是不是就苦逼了?非常多地方都须要跟着改,如今不用了,仅仅用动一个地方全都OK
  • 还有非常多我一时间想不起来等你去发掘的功能
好。BB了这么多,究竟是怎么实现和怎么使用呢?容我慢慢道来:
好,先来看看使用起来有多便捷:
/** * 演示样例代码,将关键的部分放在fragment中。不管是viewpager还是Activity。还是其他容器,都能够将fragment嵌入当中显示 *  * @author Sahadev * */public class ExampleFragment extends SuperAbstListFragment
{ public static AbstListFragment getInstance(String requestUrl) { AbstListFragment fragment = new ExampleFragment(); Bundle bundle = new Bundle(); bundle.putString(AbstListFragment.URL, requestUrl); fragment.setArguments(bundle); return fragment; } @Override public Type getInstanceType() { // 返回须要实例化的对象类型 return new TypeToken
>() { }.getType(); } /** * 须要实例化的类,这里仅用一个属性做样例 * * @author Work * */ public static class ExampleBean implements Serializable { /** * */ private static final long serialVersionUID = 7624541082621792974L; @SerializedName("title") public String title; } //在这里完毕数据绑定就能够了,支持链式调用 @Override public void setView(ViewHolder viewHolder, ExampleBean t) { viewHolder.setText(R.id.title, t.title); } @Override public void onItemClick(AdapterView
parent, View view, int position, long id) { }}
这是实际执行效果图:只通过短短的几行代码就能够实现强大的功能。是不是非常方便?
好,效果先看到了,接下来描写叙述一下是怎样完毕这么多功能的:
当然,子类写的代码少,那说明父类已经帮它完毕了不少功能,所以咱们先看看总体的文件夹结构:
这个图可能画的有些毛糙,还是再用小文来简述一下,BaseFragment含有一些主要的功能,比方高速弹出一个toast,显示一个等待对话框等待,它还有子类经常使用的一些属性,activity。LayoutInflater,ImageLoader 等等:
/** * 基本类。提供一些经常使用的主要的方法属性供子类使用 *  * @author Sahadev * */public class BaseFragment extends Fragment {	/**	 * 图片载入工具	 */	protected ImageLoader mImageLoader;	/**	 * 等待对话框	 */	private LoadingDialog mLoadingDialog;	/**	 * 布局填充器	 */	protected LayoutInflater mInflater;	/**	 * context	 */	protected Activity mContext;	@Override	public void onAttach(Activity activity) {		super.onAttach(activity);		mContext = activity;		mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);	}	@Override	public void onActivityCreated(Bundle savedInstanceState) {		super.onActivityCreated(savedInstanceState);		// 在此处初始化mImageLoader,mLoadingDialog等属性		mLoadingDialog = LoadingDialog.getInstance(mContext);		// imageLoader属性可在自己定义的Application中设置全局的单例。由自己定义Application暴露接口获取单例,比方		// mImageLoader = CustomApplication.getImageLoaderInstance();	}	/**	 * 吐司	 * 	 * @param message	 */	protected void toast(String message) {		Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();	}	/**	 * 显示等待对话框。显示默认文本	 */	protected void showLoadingDialog() {		if (mLoadingDialog != null) {			mLoadingDialog.show();		}	}	/**	 * 显示等待对话框,显示传入的文本	 * 	 * @param message	 */	public void showLoadingDialog(String message) {		if (mLoadingDialog != null) {			mLoadingDialog.setMessage(message);			mLoadingDialog.show();		}	}	/**	 * 关闭等待对话框	 */	protected void dismissLoadingDialog() {		if (mLoadingDialog != null) {			mLoadingDialog.dismiss();		}	}	@Override	public void onDestroy() {		super.onDestroy();		dismissLoadingDialog();	}}
AbstListFragment则是咱们项目的关键部分了。它集成了界面生成、空数据展示界面、网络请求及分页请求。网络请求回调,item点击回调。界面主动刷新广播接收器等功能,
能够使用户自定义适配器:
/** * 含有ListView的Fragment * 抽取公共的含有ListView的Fragment。此Fragment已经包含主要的下拉刷新,网络载入,分页载入等公共功能,仅仅须要关心实现 推荐使用 * {@link #SuperAbstListFragment}实例化子类方式參见{@link #ExampleFragment} *  * @author Sahadev *  */public abstract class AbstListFragment extends BaseFragment implements OnItemClickListener, OnClickListener,		Listener
, ErrorListener, OnRefreshListener2
{ protected PullToRefreshListView mListView; protected ImageView emptyView; private AnimationDrawable rocketAnimation; private View rootView; protected int page = 0; public static final String URL = "ABST_LIST_FRAGMENT_URL"; public static final String NEED_REFRESH_BROADCAST_RECEIVER = "NEED_REFRESH_BROADCAST_RECEIVER"; /** * 请求的链接地址 */ protected String requestUrl; /** * 由子类实现。安全传參 * * @param requestUrl * @return */ public static AbstListFragment getInstance(String requestUrl) { return null; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (rootView == null) { rootView = inflater.inflate(R.layout.activity_list_layout, container, false); emptyView = (ImageView) rootView.findViewById(R.id.empty_view); mListView = (PullToRefreshListView) rootView.findViewById(R.id.list); mListView.setEmptyView(emptyView); mListView.getRefreshableView().setLayoutTransition(new LayoutTransition()); mListView.setOnRefreshListener(this); mListView.setOnItemClickListener(this); mListView.getRefreshableView().setOnItemClickListener(this); emptyView.setOnClickListener(this); Bundle bundle = getArguments(); if (bundle != null) { requestUrl = getArguments().getString(AbstListFragment.URL); } } ViewGroup parent = (ViewGroup) rootView.getParent(); if (parent != null) { parent.removeView(rootView); } return rootView; } @Override public void onStart() { super.onStart(); if (mListView != null) { mListView.setRefreshing(false); } } @Override public void onPullDownToRefresh(PullToRefreshBase
refreshView) { page = 0; getData(requestUrl + "&p=" + page);// 这里能够加入分页和其他请求server所须要的必要參数。比方token或者其他什么的,所以在传入的地方仅仅用传入必要的參数就OK } @Override public void onPullUpToRefresh(PullToRefreshBase
refreshView) { page++; getData(requestUrl + "&p=" + page); } protected void getData(String requestUrl) { if (isNeedLoadDataFromNet()) { if (page == 0) { // 能够在这里设置载入动画 emptyView.setImageResource(R.drawable.loading_animation);// R.drawable.loading_animation代表动画资源 rocketAnimation = (AnimationDrawable) emptyView.getDrawable(); rocketAnimation.start(); } RequestUtils.requesGet(requestUrl, this, this); } } /** * 这种方法用于返回是否是从网络载入,有些数据是须要从本地载入的。这个 方法就能够由子类来控制详细是什么 * * @return */ protected boolean isNeedLoadDataFromNet() { return true; } @Override public void onResponse(JSONObject response) { // 设置请求完成之后的状态 rocketAnimation.stop(); emptyView.setImageResource(R.drawable.nocontent); mListView.onRefreshComplete(); } @Override public void onErrorResponse(VolleyError error) { // 设置请求完成之后的状态 rocketAnimation.stop(); emptyView.setImageResource(R.drawable.nocontent); toast("咦?网络状况貌似出了点问题."); mListView.onRefreshComplete(); } @Override public void onClick(View v) { switch (v.getId()) { // 当点击无数据提示的时候又一次载入 case R.id.empty_view: mListView.setRefreshing(); break; default: break; } } private BroadcastReceiver receiver; @Override public void onResume() { super.onResume(); receiver = new NeedRefreshBroadcastReceiver(); IntentFilter filter = new IntentFilter(NEED_REFRESH_BROADCAST_RECEIVER); filter.addCategory(Intent.CATEGORY_DEFAULT); mContext.registerReceiver(receiver, filter); } @Override public void onPause() { super.onPause(); mContext.unregisterReceiver(receiver); } /** * 主动刷新广播接收器,当数据发生改变的时候(比方加入或者删除)主动刷新 * * @author Work * */ private class NeedRefreshBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { mListView.setCurrentMode(Mode.PULL_FROM_START); mListView.setRefreshing(false); } }}
SuperAbstListFragment<T> 是对父类AbstListFragment的进一步抽象。它里面集成了一个适配器与一个万能的ViewHolder,使子类仅仅用
实现几个主要的方法就能够,比方要解析的类型、当item点击之后的处理方式、数据与界面怎样绑定等等,来看看这个
类都有什么:
/** * 抽象的AbstListFragment中间层,具有更强大的功能 *  * @author Work * */public abstract class SuperAbstListFragment
extends AbstListFragment { protected AbstBaseAdapter
adapter; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); try { // 假设有些Adapter中不满足实际情况的话,能够使用反射来实例化 // adapter = (AbstBaseAdapter
) // getAdapterClass().getConstructor(Context.class).newInstance(mContext); adapter = new SuperAdapter(mContext); mListView.setAdapter(adapter); } catch (IllegalArgumentException e) { e.printStackTrace(); } } /** * 万能适配器,它仅仅是个中间件 * * @author Work * */ public class SuperAdapter extends AbstBaseAdapter
{ public SuperAdapter(Context context) { super(context); } @Override public View getView(int position, View convertView, ViewGroup parent) { return SuperAbstListFragment.this.getView(position, convertView, parent); } } /** * * * @param position * @param convertView * @param parent * @return */ public View getView(int position, View convertView, ViewGroup parent) { // 这里使用的是万能的ViewHolder ViewHolder viewHolder = ViewHolder.get(mContext, convertView, parent, R.layout.fragment_list_item, position);// 这一行能够进一步的抽取到父类中 T t = adapter.getData().get(position); setView(viewHolder, t); return viewHolder.getConvertView(); } /** * 绑定数据,使用户真正关心的仅仅有他们想要关心的 * * @param viewHolder * @param t */ public abstract void setView(ViewHolder viewHolder, T t); /** * 假设单单的getView方法不满足需求的话。能够通过自己定义Adapter的方法来实现,该方法用来返回须要实例化的Adapter的类名 * * @return */ public Class
getAdapterClass() { return null; } /** * 须要解析的数据类型是一个对象还是对象的集合,由这个返回 * * @return */ public abstract Type getInstanceType(); /* * (non-Javadoc) * * @see * com.sahadev.general_assembly.base.AbstListFragment#onResponse(org.json * .JSONObject) 当网络请求成功之后回调该方法,開始解析数据 */ @Override public void onResponse(JSONObject response) { super.onResponse(response); if (response != null && response.optBoolean("success")) { Gson gson = new Gson(); List
datas = gson.fromJson(response.optJSONArray("data").toString(), getInstanceType()); initAdapter(datas); } } /** * 数据解析完成之后刷新数据 * * @param list */ protected void initAdapter(List
list) { if (page == 0) { adapter.addFirstPageData(list); } else { adapter.addOtherPageData(list); } }}
通过以上几个类的不断抽取。当最后在使用的时候。实现类仅仅用简单的几行代码就能够完毕非常多非常多的功能。怎么样,是不是非常easy?
接下来简介一下怎样在各种页面仅仅用一个Activity来装载不同页面的Fragment呢:
/** * 含有ListFragment的Activity *  * @author 尚斌 *  */public class IncludeListFragmentActivity extends FragmentActivity {	private String mFragmentClass = "x.x.x.x.x.x";	private String mRequestUrl = "http://www.baidu.com";	private String title = "标题没有定义";	public static final String TITLE = "TITLE";	public static final String CLASS = "CLASS";	public static final String URL = "URL";	/**	 * @param context	 * @param fragmentClass	 *            须要实例化的Fragment的包名	 * @param requestUrl	 *            该Fragment内部的请求地址	 * @return	 */	public static Intent getIntent(Context context, String fragmentClass, String requestUrl, String title) {		Intent intent = new Intent(context, IncludeListFragmentActivity.class);		Bundle bundle = new Bundle();		bundle.putString(TITLE, title);		bundle.putString(CLASS, fragmentClass);		bundle.putString(URL, requestUrl);		intent.putExtras(bundle);		return intent;	}	@SuppressWarnings("unchecked")	@Override	protected void onCreate(Bundle savedInstanceState) {		requestWindowFeature(Window.FEATURE_NO_TITLE);		super.onCreate(savedInstanceState);		Intent intent = getIntent();		Bundle bundle = intent.getExtras();		mFragmentClass = bundle.getString(CLASS);		mRequestUrl = bundle.getString(URL);		title = bundle.getString(TITLE);		// 设置标题		setTitle(title);		// 设置布局文件		setContentView(R.layout.activity_include_list_fragment);		try {			Class
newInstance = (Class
) Class.forName(mFragmentClass); Method method = null; BaseFragment fragment = null; method = newInstance.getMethod("getInstance", String.class); fragment = (BaseFragment) method.invoke(null, mRequestUrl); if (fragment != null) { getSupportFragmentManager().beginTransaction().add(R.id.container, fragment).commit(); } else { throw new Exception("You must be have a named getInstance method!"); } } catch (Exception e) { e.printStackTrace(); } }
就是这个文件。能够通过传入的标题,须要实现的Fragment类,须要请求的地址来生成多种多样的界面,所以在实现子类的时候每一个子类都须要重写public static AbstListFragment getInstance(String requestUrl) 方法以供外部能够调用到它。

说了这么多,在这里面事实上是没有缓存的。事实上这个公共的项目与缓存关系是不大的。既然提到就说一下是怎么实现的。在项目开发的时候非常多时候都用到了第三方网络框架,也是有源代码的,这里就用Volley举个栗子:
在Volley请求的时候,
在请求的基类方法中,依据请求的URL去数据库中寻找,数据库这里推荐使用xUtils提供的数据库存储。假设没找到,则调用网络请求,在网络请求成功回调的部分将请求的数据存入数据库。以便第二次查找。基本思路就是这样:
使用代码举例:
网络请求部分:
public JsonRequest(int method, String url, Map
body, Listener
listener, ErrorListener errorListener, boolean isNeedCache, Type type) { super(method, url + "&token=token" + "&vid=vid"), errorListener); mListener = listener; mRequestBody = null; mType = type; if (isNeedCache && !(检查网络是否可用)) { url += "&token=token" + "&vid=vid"; try { //使用自己定义的方式去数据库中查找,这里使用的是xUtils举例: List
datas = xUtils .getInstance() .getDbUtils() .findAll( Selector.from(Cache.class).where("requestUrl", "=", url).orderBy("time", false) .limit(1)); if (!netAccessed && datas != null & listener != null) { for (Cache cache : datas) { //假设查找成功就进行回调 listener.onResponse((T) new JSONObject(cache.jsonString)); } } } catch (DbException e) { e.printStackTrace(); } catch (JSONException e) { e.printStackTrace(); } } bodyMap = body; }
网络回调部分。将server数据存储:
@Override	protected void deliverResponse(final T response) {		if (response != null				&& (response.getClass().equals(String.class) || response.getClass().equals(JSONObject.class))) {			new Thread(new Runnable() {				@Override				public void run() {					Cache cache = new Cache(mUrl, response.toString());					try {						//使用xUtils进行存储						xUtils.getInstance().getDbUtils().save(cache);					} catch (DbException e) {						e.printStackTrace();					}				}			}).start();		}		netAccessed = true;		if (mListener != null && (假设网络可用)) {			if (mType != null) {				try {					String simString1 = response.getClass().getName();					String simString2 = mType.toString();					//有多种类型回调,假设仅仅是回调String类。则调用下面					if (simString2.contains(simString1)) {						mListener.onResponse(response);					} else {						mListener.onResponse(null);					}				} catch (Exception e1) {				}			} else {				//另一种是JsonObject类型,则调用下面				mListener.onResponse(response);			}		}	}
大伙可能实际情况不是这个样子。可是思路可能差点儿相同,仅供參考。

当然,在该项目中还集成了不少别的主要的东西,比方ImageLoader图片载入。Volley请求工具,json解析工具等,假设是作为一个新项目的话,本项目还是能够作为一个最主要的起始项目来用用。

项目地址在这里:
项目是一个开源项目,迫切的想要很多其它的人能够增加进来。将自己工作中能够提高工作效率的知识和成果分享出来,出一份力。假设想增加 请联系我:sahadev@foxmail.com,希望能够一起发展壮大,拥有非常多为大家减轻负担的成果。
你可能感兴趣的文章
Prometheus 500 Internal Privoxy Error 异常解决
查看>>
2018年前端面试题(秋季面试随意整理的)
查看>>
深圳Android技术大会分享
查看>>
requestAnimationFrame 兼容方案
查看>>
Java™ 教程(管理源文件和类文件)
查看>>
Linux运维之路-安全防护OpenResty
查看>>
说说不知道的Golang中参数传递
查看>>
深入解析Vue底层实现原理
查看>>
es6之解构赋值
查看>>
如何用外部程序优化SQL语句中的IN和EXISTS
查看>>
webpack学习进阶(一)
查看>>
虚拟机硬盘vmdk压缩瘦身并挂载到VirtualBox
查看>>
详解css媒体查询
查看>>
关于浏览器缓存问题(图片更换后,页面仍优先读取缓存)
查看>>
Event Loop 其实也就这点事
查看>>
前端学习资源汇总
查看>>
ZooKeeper 文档(目录)
查看>>
Nuxt在SPA模式下的鉴权处理(1)
查看>>
React尝鲜
查看>>
Habitica 4.85.5 发布,习惯游戏养成应用
查看>>