如何使用ListView实现一个带有网络请求,解析,分页,缓存的公共的List页面来大大的提高工作效率
生活随笔
收集整理的這篇文章主要介紹了
如何使用ListView实现一个带有网络请求,解析,分页,缓存的公共的List页面来大大的提高工作效率
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
在平常的開發(fā)中經常會有很多列表頁面,每做一個列表頁就需要創(chuàng)建這個布局文件那個Adapter適配器文件等等一大堆與之相關的附屬的不必要的冗余文件。如果版本更新迭代比較頻繁,如此以往,就會使項目工程變得無比龐大臃腫。
如果看過這篇文章或者在使用過這種方式之后呢,所有的工作都可以被壓縮成只有兩個文件,一個JAVA文件一個XML布局文件。而且代碼還少少的。
咱們來看看實際情況:
平常的一個列表頁面的生成需要以下文件:
- 一個Activity文件,有時候可能還會忘記注冊
- 一個包含上下拉刷新控件以及無數(shù)據(jù)時提示的布局文件
- 一個Listview的item的布局文件
- 一個Adapter適配器文件
- 一個需要被解析的Bean文件
當然在Activity中還需要處理以下功能:
- 數(shù)據(jù)解析
- 分頁加載
- 數(shù)據(jù)緩存
- 網(wǎng)絡請求
現(xiàn)在你可能想知道一個公共的List頁有什么特點呢?
- 無需再關心網(wǎng)絡請求、數(shù)據(jù)解析、分頁、緩存等相同的功能
- 不需要寫那么多的相同的布局文件,只用寫那些不同的item布局文件就可以,只需要關心你關心的
- 只會有一個Adapter適配器,一個ViewHolder存在,Activity也可以只有一個
- 可復用性超強,無論是Activity中展示,還是在被要求放在ViewPager中顯示都沒問題
- 大大減小項目的工程文件數(shù)量,提高編譯速度,不用再把一天的時間都浪費在編譯時間上
- 提高你的工作效率,不用再復制粘貼,那個時間沒有這個快,只用實現(xiàn)你的getView方法就可以
- 減少維護成本,如果某一天需要在網(wǎng)絡請求加上某個參數(shù),以前的方法需要改無數(shù)個地方,而現(xiàn)在只用改一個地方就OK,如果如探需要更改上下拉刷新控件,比如需要將XListView改成PullToRefreshListView,你是不是就苦逼了?很多地方都需要跟著改,現(xiàn)在不用了,只用動一個地方全都OK
- 還有很多我一時間想不起來等你去發(fā)掘的功能
好,BB了這么多,到底是怎么實現(xiàn)和怎么使用呢?容我慢慢道來:
好,先來看看使用起來有多便捷: /*** 示例代碼,將關鍵的部分放在fragment中,無論是viewpager還是Activity,還是其它容器,都可以將fragment嵌入其中顯示* * @author Sahadev**/ public class ExampleFragment extends SuperAbstListFragment<ExampleBean> {public static AbstListFragment getInstance(String requestUrl) {AbstListFragment fragment = new ExampleFragment();Bundle bundle = new Bundle();bundle.putString(AbstListFragment.URL, requestUrl);fragment.setArguments(bundle);return fragment;}@Overridepublic Type getInstanceType() {// 返回需要實例化的對象類型return new TypeToken<List<ExampleBean>>() {}.getType();}/*** 需要實例化的類,這里僅用一個屬性做例子* * @author Work**/public static class ExampleBean implements Serializable {/*** */private static final long serialVersionUID = 7624541082621792974L;@SerializedName("title")public String title;}//在這里完成數(shù)據(jù)綁定就可以了,支持鏈式調用@Overridepublic void setView(ViewHolder viewHolder, ExampleBean t) {viewHolder.setText(R.id.title, t.title);}@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {} }
這是實際運行效果圖:僅僅通過短短的幾行代碼就可以實現(xiàn)強大的功能,是不是很方便?
好,效果先看到了,接下來描述一下是如何完成這么多功能的: 當然,子類寫的代碼少,那說明父類已經幫它完成了不少功能,所以咱們先看看整體的目錄結構:
這個圖可能畫的有些毛糙,還是再用小文來簡述一下,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;@Overridepublic void onAttach(Activity activity) {super.onAttach(activity);mContext = activity;mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);}@Overridepublic 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();}}@Overridepublic void onDestroy() {super.onDestroy();dismissLoadingDialog();}}
AbstListFragment則是咱們項目的關鍵部分了,它集成了界面生成、空數(shù)據(jù)展示界面、網(wǎng)絡請求及分頁請求,網(wǎng)絡請求回調,item點擊回調,界面主動刷新廣播接收器等功能, 可以使用戶自己定義適配器: /*** 含有ListView的Fragment* 抽取公共的含有ListView的Fragment,此Fragment已經包括基本的下拉刷新,網(wǎng)絡加載,分頁加載等公共功能,只需要關心實現(xiàn) 推薦使用* {@link #SuperAbstListFragment}實例化子類方式參見{@link #ExampleFragment}* * @author Sahadev* */ public abstract class AbstListFragment extends BaseFragment implements OnItemClickListener, OnClickListener,Listener<JSONObject>, ErrorListener, OnRefreshListener2<ListView> {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;/*** 由子類實現(xiàn),安全傳參* * @param requestUrl* @return*/public static AbstListFragment getInstance(String requestUrl) {return null;}@Overridepublic 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;}@Overridepublic void onStart() {super.onStart();if (mListView != null) {mListView.setRefreshing(false);}}@Overridepublic void onPullDownToRefresh(PullToRefreshBase<ListView> refreshView) {page = 0;getData(requestUrl + "&p=" + page);// 這里可以添加分頁和其它請求服務器所需要的必要參數(shù),比如token或者其它什么的,所以在傳入的地方只用傳入必要的參數(shù)就OK}@Overridepublic void onPullUpToRefresh(PullToRefreshBase<ListView> 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);}}/*** 這個方法用于返回是否是從網(wǎng)絡加載,有些數(shù)據(jù)是需要從本地加載的,這個 方法就可以由子類來控制具體是什么* * @return*/protected boolean isNeedLoadDataFromNet() {return true;}@Overridepublic void onResponse(JSONObject response) {// 設置請求完畢之后的狀態(tài)rocketAnimation.stop();emptyView.setImageResource(R.drawable.nocontent);mListView.onRefreshComplete();}@Overridepublic void onErrorResponse(VolleyError error) {// 設置請求完畢之后的狀態(tài)rocketAnimation.stop();emptyView.setImageResource(R.drawable.nocontent);toast("咦?網(wǎng)絡狀況貌似出了點問題.");mListView.onRefreshComplete();}@Overridepublic void onClick(View v) {switch (v.getId()) {// 當點擊無數(shù)據(jù)提示的時候重新加載case R.id.empty_view:mListView.setRefreshing();break;default:break;}}private BroadcastReceiver receiver;@Overridepublic void onResume() {super.onResume();receiver = new NeedRefreshBroadcastReceiver();IntentFilter filter = new IntentFilter(NEED_REFRESH_BROADCAST_RECEIVER);filter.addCategory(Intent.CATEGORY_DEFAULT);mContext.registerReceiver(receiver, filter);}@Overridepublic void onPause() {super.onPause();mContext.unregisterReceiver(receiver);}/*** 主動刷新廣播接收器,當數(shù)據(jù)發(fā)生改變的時候(比如添加或者刪除)主動刷新* * @author Work**/private class NeedRefreshBroadcastReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {mListView.setCurrentMode(Mode.PULL_FROM_START);mListView.setRefreshing(false);}}}
SuperAbstListFragment<T> 是對父類AbstListFragment的進一步抽象,它里面集成了一個適配器與一個萬能的ViewHolder,使子類只用 實現(xiàn)幾個基本的方法就可以,比如要解析的類型、當item點擊之后的處理方式、數(shù)據(jù)與界面如何綁定等等,來看看這個 類都有什么:
/*** 抽象的AbstListFragment中間層,具有更強大的功能* * @author Work**/ public abstract class SuperAbstListFragment<T> extends AbstListFragment {protected AbstBaseAdapter<T> adapter;@Overridepublic void onActivityCreated(Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);try {// 如果有些Adapter中不滿足實際情況的話,可以使用反射來實例化// adapter = (AbstBaseAdapter<T>)// 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<T> {public SuperAdapter(Context context) {super(context);}@Overridepublic 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) {// 這里使用的是萬能的ViewHolderViewHolder viewHolder = ViewHolder.get(mContext, convertView, parent, R.layout.fragment_list_item, position);// 這一行可以進一步的抽取到父類中T t = adapter.getData().get(position);setView(viewHolder, t);return viewHolder.getConvertView();}/*** 綁定數(shù)據(jù),使用戶真正關心的只有他們想要關心的* * @param viewHolder* @param t*/public abstract void setView(ViewHolder viewHolder, T t);/*** 如果單單的getView方法不滿足需求的話,可以通過自定義Adapter的方法來實現(xiàn),該方法用來返回需要實例化的Adapter的類名* * @return*/public Class<?> getAdapterClass() {return null;}/*** 需要解析的數(shù)據(jù)類型是一個對象還是對象的集合,由這個返回* * @return*/public abstract Type getInstanceType();/** (non-Javadoc)* * @see* com.sahadev.general_assembly.base.AbstListFragment#onResponse(org.json* .JSONObject) 當網(wǎng)絡請求成功之后回調該方法,開始解析數(shù)據(jù)*/@Overridepublic void onResponse(JSONObject response) {super.onResponse(response);if (response != null && response.optBoolean("success")) {Gson gson = new Gson();List<T> datas = gson.fromJson(response.optJSONArray("data").toString(), getInstanceType());initAdapter(datas);}}/*** 數(shù)據(jù)解析完畢之后刷新數(shù)據(jù)* * @param list*/protected void initAdapter(List<T> list) {if (page == 0) {adapter.addFirstPageData(list);} else {adapter.addOtherPageData(list);}}}
通過以上幾個類的不斷抽取,當最后在使用的時候,實現(xiàn)類只用簡單的幾行代碼就可以完成很多很多的功能,怎么樣,是不是很簡單?
接下來簡單介紹一下如何在各種頁面只用一個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")@Overrideprotected 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<BaseFragment> newInstance = (Class<BaseFragment>) 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();}}
就是這個文件,可以通過傳入的標題,需要實現(xiàn)的Fragment類,需要請求的地址來生成多種多樣的界面,所以在實現(xiàn)子類的時候每個子類都需要重寫public static AbstListFragment getInstance(String requestUrl) 方法以供外部可以調用到它。
說了這么多,在這里面其實是沒有緩存的,其實這個公共的項目與緩存關系是不大的,既然提到就說一下是怎么實現(xiàn)的,在項目開發(fā)的時候很多時候都用到了第三方網(wǎng)絡框架,也是有源碼的,這里就用Volley舉個栗子: 在Volley請求的時候,在請求的基類方法中,根據(jù)請求的URL去數(shù)據(jù)庫中尋找,數(shù)據(jù)庫這里推薦使用xUtils提供的數(shù)據(jù)庫存儲,如果沒找到,則調用網(wǎng)絡請求,在網(wǎng)絡請求成功回調的部分將請求的數(shù)據(jù)存入數(shù)據(jù)庫,以便第二次查找,基本思路就是這樣: 使用代碼舉例: 網(wǎng)絡請求部分: public JsonRequest(int method, String url, Map<String, String> body, Listener<T> listener,ErrorListener errorListener, boolean isNeedCache, Type type) {super(method, url + "&token=token" + "&vid=vid"), errorListener);mListener = listener;mRequestBody = null;mType = type;if (isNeedCache && !(檢查網(wǎng)絡是否可用)) {url += "&token=token" + "&vid=vid";try {//使用自定義的方式去數(shù)據(jù)庫中查找,這里使用的是xUtils舉例:List<Cache> 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;}網(wǎng)絡回調部分,將服務器數(shù)據(jù)存儲: @Overrideprotected void deliverResponse(final T response) {if (response != null&& (response.getClass().equals(String.class) || response.getClass().equals(JSONObject.class))) {new Thread(new Runnable() {@Overridepublic 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 && (如果網(wǎng)絡可用)) {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解析工具等,如果是作為一個新項目的話,本項目還是可以作為一個最基本的起始項目來用用。
項目地址在這里:https://git.oschina.net/sahadev/General-Assembly 項目是一個開源項目,迫切的想要更多的人可以加入進來,將自己工作中可以提高工作效率的知識和成果分享出來,出一份力。如果想加入 請聯(lián)系我:sahadev@foxmail.com,希望可以一起發(fā)展壯大,擁有很多為大家減輕負擔的成果。
總結
以上是生活随笔為你收集整理的如何使用ListView实现一个带有网络请求,解析,分页,缓存的公共的List页面来大大的提高工作效率的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Dataset、IterableData
- 下一篇: TI-RTOS实时操作系统开发之功耗测试