關于新聞類應用快速開發框架的思考
流程
目前我主要開發的是新聞資訊類的應用,所以這個框架也主要是針對這類應用設計的,在這一類應用中,最重要的就是內容展示,一般都是以列表的形式展示數據,比如下圖:
這里寫圖片描述
而這類頁面還要有下拉刷新,上滑加載下一頁,加載進度,錯誤重試等功能,但是這些功能都是公有的,而每個頁面所不同的就是展示的內容的不同(廢話),而這些內容又是以不同的item為基礎的。例如下面不同的item:
Item1
這里寫圖片描述
item2
這里寫圖片描述
而在軟件開發中,所不同的就是JSON數據的不同,JavaBean的不同,Layout的不同。大體的對應關系如下:
這里寫圖片描述
因此在實際開發中,最小的粒度就是一個Object。我的框架工作流程就是找到這些JsonObject然后轉化為對應的JavaBean傳統,實例對應的ViewHolder,然后將數據傳遞給對應的ViewHolder,ViewHolder綁定數據,最終一個新聞列表就顯示出來了,大概的流程如下:
這里寫圖片描述
實現
1. JSON數據的解析
在一般的開發流程中,我們會將服務器返回的Json數據直接轉換成一個JavaBean,然后再操作。在界面不是很復雜的情況下這么做無可厚非,但是在多個界面有相同的Item,又有部分不同的Item。這就會造成,每一個界面的數據都要有一個對應的JavaBean,而且這些解析都會固化在類中,或許可以通過繼承實現部分的復用,但是對于擴展性并不優化,三大設計原則中就有,組合優于繼承,因此我所做的就是降低粒度,即針對JsonObject,而不是全部的數據。對于如何定位,我自己設計了一種定位的描述符:
比如要定位到下面這個數組:
這里寫圖片描述
我在配置文件中是這么寫的
這里寫圖片描述
大框中括起來的最終會轉換為JsonConfig對象的素組,交給JsonAnalysisEngine 處理,而JsonAnalysisEngine 會循環遍歷然后處理每一個JsonConfig,首先會更具jsonLocation的位置對原來的數據進行過濾,找到需要的數據,最后自動轉化為對應的Javabean,關于使用大家看注釋吧,目前來看可能性能還不是很好,我在后續的版本可能會更換實現方式。
package com.zgh.smartlibrary.json;
import android.text.TextUtils;
import com.google.gson.Gson;
import com.zgh.smartlibrary.config.JsonAnalysisConfig;
import com.zgh.smartlibrary.util.GsonUtil;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
/**
Created by zhuguohui on 2016/8/17.
*/
public class JsonAnalysisEngine {
List<JsonAnalysisConfig> configItems = new ArrayList<>();
Gson gson = new Gson();
public JsonAnalysisEngine(List<JsonAnalysisConfig> configItems) {
this.configItems = configItems;
}
public JsonAnalysisEngine(JsonAnalysisConfig... configItems) {
this.configItems.clear();
for (JsonAnalysisConfig config : configItems) {
this.configItems.add(config);
}
}
private int listDataSize = 0;
/**
- 獲取數據
*
- @param jsonStr
@return
*/
public List<Object> getData(String jsonStr) {
listDataSize = 0;
List<Object> data = new ArrayList<>();
//處理每一個JsonAnalysisConfig
for (JsonAnalysisConfig item : configItems) {
Object o = getDataFromJson(jsonStr, item);
if (o != null) {
//如果過JsonAnalysisConfig設置isListData為true則代表,其為List的主要數據,直接解析為List
if (item.isListData()) {
List list = (List) o;
data.addAll(list);
//記錄List的數據的數量,對分頁來說需要這個數量判斷是否還有下一頁
listDataSize += list.size();
} else {
data.add(o);
}
}
}
return data;
}
public int getListDataSize() {
return listDataSize;
}
private Object getDataFromJson(String jsonStr, JsonAnalysisConfig item) {
Object o = null;
String jsonLocation = item.getJsonLocation();
try {
String jsonData = getJsonStringFromLocation(jsonStr, jsonLocation);
if (!TextUtils.isEmpty(jsonData)) {
//如果是[開頭的代表是一個數組,解析為對應的javabean的List
if (jsonData.startsWith("[")) {
o = GsonUtil.jsonToBeanList(jsonData, Class.forName(item.getType()));
} else {
o = GsonUtil.jsonToBean(jsonData, Class.forName(item.getType()));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return o;
}
/**
- 返回jsonLocation 所描述的String
*
- @param jsonStr 需要處理的jsonStr
- @param jsonLocation 描述的jsonLocation
- @return
@throws JSONException
*/
private String getJsonStringFromLocation(String jsonStr, String jsonLocation) throws JSONException {
//這個方法會遞歸調用,每一次調用jsonLocation就會減少一層,當解析結束的時候jsonLocation就為空
if (TextUtils.isEmpty(jsonLocation)) {
return jsonStr;
}
char a;
//記錄接下來的操作,如果是{開頭代表解析一個對象,以[開頭代表解析一個數組
char op = 0;
boolean haveFoundOP = false;
int nameStart = 0, nameEnd = 0;
//記錄需要解析的對象的名字,例如{news 代表解析一個叫做news的json對象,[news代表解析一個叫news的數組
String name;
for (int i = 0; i < jsonLocation.length(); i++) {
a = jsonLocation.charAt(i);
if ('{' == a || '[' == a) {
if (!haveFoundOP) {
op = a;
haveFoundOP = true;
nameStart = i + 1;
} else {
nameEnd = i - 1;
break;
}
}
}
if (nameStart != 0 && nameEnd != 0) {
name = jsonLocation.substring(nameStart, nameEnd + 1);
jsonLocation = jsonLocation.substring(nameEnd + 1);
} else {
name = jsonLocation.substring(nameStart);
jsonLocation = "";
}
jsonStr = jsonStr.trim();
int index = -1;
//如果name中包含:表示需要解析一個數組指定的部分,比如[news:0 代表解析名叫news的Json數組下的第一條數據。
if (name.indexOf(":") != -1) {
String[] split = name.split(":");
name = split[0];
try {
index = Integer.valueOf(split[1]);
} catch (Exception e) {
}
}
//如果原來的json字符串是以{開頭代表解析一個對象,否則解析一個數組
if (jsonStr.startsWith("{")) {
JSONObject jsonObject = new JSONObject(jsonStr);
if ('{' == op && jsonObject.has(name)) {
return getJsonStringFromLocation(jsonObject.getJSONObject(name).toString(), jsonLocation);
} else if ('[' == op) {
if (index == -1) {
return getJsonStringFromLocation(jsonObject.getJSONArray(name).toString(), jsonLocation);
} else {
return getJsonStringFromLocation(jsonObject.getJSONArray(name).getJSONObject(index).toString(), jsonLocation);
}
}
} else {
try {
if (index != -1) {
JSONArray array = new JSONArray(jsonStr);
return array.getJSONObject(index).toString();
} else {
return jsonStr;
}
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
return "";
}
}</code></pre>
2.下拉刷新,上拉加載更多
為了以后的可擴展性,我把下拉刷新,上拉加載更多抽象成一個接口
package com.zgh.smartlibrary.manager;
import android.view.View;
import android.widget.BaseAdapter;
import android.widget.ListView;
import com.zgh.smartlibrary.page.IPagePolicy;
/**
- 能提供下拉刷新,上拉加載更多的接口
Created by zhuguohui on 2016/9/5.
*/
public interface ListViewUpdateManger {
/**
- 返回ListView
@return
*/
ListView getListView();
/**
- 返回view
@return
*/
View getView();
/**
- 相應不同的狀態,比如沒有分頁信息就不顯示,加載更多等。
- @param state
*/
void setState(IPagePolicy.PageState state);
void setAdapter(BaseAdapter adapter);
/**
* 設置回調接口
* @param listener
*/
void setUpdateListener(UpdateListener listener);
interface UpdateListener {
void pullUp();
void pullDown();
}
/**
* 更新完成時回調,實現者可在這個方法中結束動畫
*/
void updateComplete();
/**
* 手動更新
* @param showAnimation 是否顯示動畫
*/
void update(boolean showAnimation);
}</code></pre>
有一個默認的實現
package com.zgh.smartlibrary.manager.impl;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshListView;
import com.zgh.smartlibrary.R;
import com.zgh.smartlibrary.manager.ListViewUpdateManger;
import com.zgh.smartlibrary.page.IPagePolicy;
import com.zgh.smartlibrary.util.AppUtil;
import com.zgh.smartlibrary.util.TimeUtil;
/**
Created by zhuguohui on 2016/9/5.
*/
public class PullToRefreshManger implements ListViewUpdateManger {
private final TextView footerView;
protected PullToRefreshListView mPullToRefreshView;
protected ListView listView;
private String mStrNextPageRetry = "加載失敗 點擊重試";
protected int LAYOUT_ID = R.layout.fragment_smart_list;
private long mLastRefreshTime = 0;
private boolean isUpdate = false;
private View mBaseView = null;
public PullToRefreshManger(Context context) {
mBaseView = View.inflate(context, LAYOUT_ID, null);
mPullToRefreshView = (PullToRefreshListView) mBaseView.findViewById(R.id.refreshView);
mPullToRefreshView.setMode(PullToRefreshBase.Mode.BOTH);
//配置加載更多文字
mPullToRefreshView.getLoadingLayoutProxy(false, true).setPullLabel("上拉加載更多");
mPullToRefreshView.getLoadingLayoutProxy(false, true).setRefreshingLabel("正在加載");
mPullToRefreshView.getLoadingLayoutProxy(false, true).setReleaseLabel("釋放加載更多");
//加載更多的借口
mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener2<ListView>() {
@Override
public void onPullDownToRefresh(final PullToRefreshBase<ListView> refreshView) {
if (listener != null) {
isUpdate = true;
listener.pullUp();
}
}
@Override
public void onPullUpToRefresh(PullToRefreshBase<ListView> refreshView) {
if (listener != null) {
isUpdate = false;
listener.pullDown();
}
}
});
mPullToRefreshView.setOnPullEventListener(new PullToRefreshBase.OnPullEventListener<ListView>() {
@Override
public void onPullEvent(PullToRefreshBase<ListView> refreshView,
PullToRefreshBase.State state,
PullToRefreshBase.Mode direction) {
if ((state == PullToRefreshBase.State.PULL_TO_REFRESH ||
state == PullToRefreshBase.State.REFRESHING || state == PullToRefreshBase.State.MANUAL_REFRESHING)
&& direction == PullToRefreshBase.Mode.PULL_FROM_START) {
if (mLastRefreshTime != 0L) {
mPullToRefreshView.getLoadingLayoutProxy(true, false)
.setLastUpdatedLabel(TimeUtil.format(mLastRefreshTime)
+ "更新");
}
}
}
});
//配置ListView
listView = mPullToRefreshView.getRefreshableView();
listView.setFooterDividersEnabled(false);
listView.setOverScrollMode(View.OVER_SCROLL_NEVER);
listView.setDivider(null);
//添加footview
footerView = (TextView) View.inflate(context, R.layout.view_bottom, null);
footerView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, AppUtil.dip2px(context, 35)));
//要隱藏footview其外部必須再包裹一層layout
LinearLayout footerViewParent = new LinearLayout(context);
footerViewParent.addView(footerView);
footerView.setVisibility(View.GONE);
footerView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (footerView.getText().equals(mStrNextPageRetry)) {
// footerView.setVisibility(View.GONE);
mPullToRefreshView.setMode(PullToRefreshBase.Mode.PULL_FROM_END);
mPullToRefreshView.setRefreshing(true);
}
}
});
listView.addFooterView(footerViewParent);
}
@Override
public ListView getListView() {
return listView;
}
@Override
public View getView() {
return mBaseView;
}
@Override
public void setState(IPagePolicy.PageState state) {
switch (state) {
case NO_MORE:
footerView.setText("沒有更多了");
footerView.setVisibility(View.VISIBLE);
mPullToRefreshView.setMode(PullToRefreshBase.Mode.PULL_FROM_START);
break;
case HAVE_MORE:
footerView.setVisibility(View.GONE);
mPullToRefreshView.setMode(PullToRefreshBase.Mode.BOTH);
break;
case NO_PAGE:
footerView.setVisibility(View.GONE);
mPullToRefreshView.setMode(PullToRefreshBase.Mode.DISABLED);
break;
case LOAD_ERROR:
footerView.setText(mStrNextPageRetry);
footerView.setVisibility(View.VISIBLE);
break;
default:
throw new IllegalArgumentException("Specified state is not supported state=" + state);
}
}
@Override
public void setAdapter(BaseAdapter adapter) {
//設置adapter
listView.setAdapter(adapter);
}
UpdateListener listener;
@Override
public void setUpdateListener(UpdateListener listener) {
this.listener = listener;
}
@Override
public void updateComplete() {
if (isUpdate) {
mLastRefreshTime = System.currentTimeMillis();
}
mPullToRefreshView.onRefreshComplete();
}
@Override
public void update(boolean showAnimation) {
mPullToRefreshView.setRefreshing(false);
}
}</code></pre>
大家可以通過重寫SmartListFragment中的這個方法實現自己的替換。
/**
* 獲取具有下拉刷新,上拉加載更多功能的接口
* @param context
* @return
*/
protected ListViewUpdateManger getUpdateManager(Context context){
return new PullToRefreshManger(context);
}</code></pre>
上述的接口只是提供上拉加載更多的功能,而具體的邏輯每一個應用都各有不同為此,我使用這個接口實現對分頁邏輯的解耦
package com.zgh.smartlibrary.page;
import com.zgh.smartlibrary.net.NetRequest;
/**
* Created by zhuguohui on 2016/9/2.
*/
public interface IPagePolicy {
/**
* 獲取分頁的狀態
*
* @param dataSize 當前頁的item數量
* @param data 需要解析的json數據
*/
PageState getPageState(int dataSize, String data);
/**
* 更具index獲取網絡請求
*
* @param index
* @return
*/
NetRequest getNetRequestByPageIndex(int index);
/**
* 設置一個基本url,例如第一頁的url,下一頁在此基礎上改變就行了
* @param baseURL
*/
void setBaseURL(String baseURL);
/**
* 分頁的狀態
*/
enum PageState {
//沒有分頁信息,還有下一頁,沒有更多,加載下一頁失敗
NO_PAGE, HAVE_MORE, NO_MORE, LOAD_ERROR
}
}
我的實閑是這樣的
package com.zgh.smartdemo.page;
import com.zgh.smartdemo.bean.PageInfo;
import com.zgh.smartlibrary.config.JsonAnalysisConfig;
import com.zgh.smartlibrary.json.JsonAnalysisEngine;
import com.zgh.smartlibrary.net.NetRequest;
import com.zgh.smartlibrary.page.IPagePolicy;
import java.util.List;
/**
* Created by zhuguohui on 2016/9/10 0010.
*/
public class DemoPagePolicy implements IPagePolicy {
//用于獲取分頁信息的JsonAnalysisEngine
protected JsonAnalysisEngine pageEngine;
private int mPageSize;
private String mBaseUrl = "";
public DemoPagePolicy() {
//每頁數量為5
mPageSize = 5;
JsonAnalysisConfig config = new JsonAnalysisConfig();
config.setJsonLocation("{response{page_info");
config.setType(PageInfo.class.getName());
pageEngine = new JsonAnalysisEngine(config);
}
@Override
public PageState getPageState(int dataSize, String data) {
PageState state;
List<Object> data1 = pageEngine.getData(data);
PageInfo page_info = data1 != null && data1.size() > 0 ? (PageInfo) data1.get(0) : null;
//如果分頁信息為空的話,表示不需要分頁即NO_PAGE
if (page_info != null) {
try {
//如果當前頁的數量小于每頁的數量,表示已到最后一頁
int count = Integer.valueOf(page_info.getPage_count());
int page_index = Integer.valueOf(page_info.getPage_index());
if (count == page_index + 1 || dataSize < mPageSize) {
state = PageState.NO_MORE;
} else {
state = PageState.HAVE_MORE;
}
} catch (Exception e) {
e.printStackTrace();
state = PageState.NO_MORE;
}
} else {
state = PageState.NO_PAGE;
}
return state;
}
@Override
public NetRequest getNetRequestByPageIndex(int index) {
//此處改變的是url地址,具體項目具體分析。
NetRequest request=new NetRequest();
String url = mBaseUrl;
if (index != 0) {
url = mBaseUrl+"_"+index;
}
request.setUrl(url);
return request;
}
@Override
public void setBaseURL(String baseURL) {
mBaseUrl = baseURL;
}
}
我的分頁信息在這里

這里寫圖片描述
大家更具自己的項目情況來實現自己的分頁策略,只需要覆蓋SmartListFragment中的這個方法就行了
/**
* 獲取分頁策略
* @return
*/
protected abstract IPagePolicy getPagePolicy();
3.狀態切換
此處使用的是張鴻洋的LoadingAndRetryManager,具體的用法如下參考這里 LoadingAndRetryManager 。
我簡單說一下用法,在Application的Oncreate中設置默認的樣式
/**
* Created by zhuguohui on 2016/9/10 0010.
*/
public class DemoApp extends Application {
@Override
public void onCreate() {
super.onCreate();
LoadingAndRetryManager.BASE_RETRY_LAYOUT_ID = R.layout.base_retry;
LoadingAndRetryManager.BASE_LOADING_LAYOUT_ID = R.layout.base_loading;
LoadingAndRetryManager.BASE_EMPTY_LAYOUT_ID = R.layout.base_empty;
}
}
如果某個Fragment有特殊需求,覆蓋這個方法就行了
protected OnLoadingAndRetryListener createLoadingAndRetryListener() {
return null;
}
4.網絡請求
目前的網絡框架有很多,但是我們不應該依賴于具體的某一個框架,而是采用面向接口編程的實現,把網絡請求這塊與具體的實現分開。于是我定義了兩個接口,一個用于網絡請求,一個用于網絡請求的處理:
package com.zgh.smartlibrary.net;
import java.util.HashMap;
import java.util.Map;
/**
* 代表網路請求的接口
* Created by zhuguohui on 2016/9/6.
*/
public class NetRequest {
//地址
String url;
//請求方式
METHOD method;
//緩存方式
CACHE cache;
//參數
Map<String, String> params;
//結果回調
NetResultListener resultListener;
//數據
Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public NetResultListener getResultListener() {
return resultListener;
}
public NetRequest setResultListener(NetResultListener resultListener) {
this.resultListener = resultListener;
return this;
}
public String getUrl() {
return url;
}
public NetRequest setUrl(String url) {
this.url = url;
return this;
}
public METHOD getMethod() {
return method;
}
public NetRequest setMethod(METHOD method) {
this.method = method;
return this;
}
public CACHE getCache() {
return cache;
}
public NetRequest setCache(CACHE cache) {
this.cache = cache;
return this;
}
public Map<String, String> getParams() {
return params;
}
public void setParams(Map<String, String> params) {
this.params = params;
}
public NetRequest addParams(String key, String value) {
if (params == null) {
params = new HashMap<>();
}
params.put(key, value);
return this;
}
public enum METHOD {
GET, POST
}
public enum CACHE {
//文件緩存,內存緩存,不需要緩存。
FILE, MEMORY, NO_CACHE
}
public interface NetResultListener {
void onSuccess(NetRequest netRequest);
void onError(String msg);
}
@Override
public boolean equals(Object o) {
return super.equals(o);
}
}
一個用于處理請求:
package com.zgh.smartlibrary.net;
/**
* Created by zhuguohui on 2016/9/6.
*/
public interface NetRequestHandler {
/**
* 處理網路請求
* @param netRequest
*/
void handleNetRequest(NetRequest netRequest);
/**
* 取消網絡請求
* @param netRequest
*/
void cancelNetRequest(NetRequest netRequest);
}
有一個采用張鴻洋的OkHttpUtil的默認實現
package com.zgh.smartlibrary.net.impl;
import android.content.Context;
import com.zgh.smartlibrary.net.NetRequest;
import com.zgh.smartlibrary.net.NetRequestHandler;
import com.zgh.smartlibrary.util.FileUtil;
import com.zhy.http.okhttp.OkHttpUtils;
import com.zhy.http.okhttp.builder.GetBuilder;
import com.zhy.http.okhttp.builder.HasParamsable;
import com.zhy.http.okhttp.builder.OkHttpRequestBuilder;
import com.zhy.http.okhttp.builder.PostFormBuilder;
import com.zhy.http.okhttp.callback.StringCallback;
import java.util.Map;
import okhttp3.Call;
/**
* Created by zhuguohui on 2016/9/6.
*/
public class SmartNetRequestHandler implements NetRequestHandler {
private final Context mContext;
private String HTTP_HEAD = "http";
private String HTTPS_HEAD = "https";
private String RAW_HEAD="raw://";
public SmartNetRequestHandler(Context context){
mContext=context;
}
@Override
public void handleNetRequest(final NetRequest netRequest) {
String url = netRequest.getUrl();
boolean isHttpRequest = false;
if (url != null && url.length() > 5) {
if (url.toLowerCase().startsWith(HTTP_HEAD) || url.toLowerCase().startsWith(HTTPS_HEAD)) {
isHttpRequest = true;
}
}
if(netRequest.getMethod()==null){
netRequest.setMethod(NetRequest.METHOD.GET);
}
if (isHttpRequest) {
GetBuilder getBuilder = null;
PostFormBuilder postFormBuilder = null;
OkHttpRequestBuilder requestBuilder;
HasParamsable hasParamsable;
switch (netRequest.getMethod()) {
case GET:
getBuilder = OkHttpUtils.get();
break;
case POST:
postFormBuilder = OkHttpUtils.post();
break;
}
requestBuilder = getBuilder != null ? getBuilder : postFormBuilder;
if (requestBuilder == null) {
onError(netRequest, "不支持的協議!");
return;
}
hasParamsable = getBuilder != null ? getBuilder : postFormBuilder;
requestBuilder.url(url);
Map<String, String> params = netRequest.getParams();
if (params != null && params.size() > 0) {
for (String key : params.keySet()) {
hasParamsable.addParams(key, params.get(key));
}
}
requestBuilder.build().execute(new StringCallback() {
@Override
public void onError(Call call, Exception e, int id) {
SmartNetRequestHandler.this.onError(netRequest, e.getMessage());
}
@Override
public void onResponse(String response, int id) {
onSuccess(netRequest,response);
}
});
} else {
if(url.toLowerCase().startsWith(RAW_HEAD)){
String rawName = url.substring(RAW_HEAD.length());
String s = FileUtil.readRaw(mContext, rawName);
onSuccess(netRequest, s);
}else{
onError(netRequest,"不支持的協議!");
return;
}
}
}
public void onError(NetRequest request, String msg) {
if (request != null && request.getResultListener() != null) {
request.getResultListener().onError(msg);
}
}
public void onSuccess(NetRequest request, Object data) {
if (request != null && request.getResultListener() != null) {
request.setData(data);
request.getResultListener().onSuccess(request);
}
}
@Override
public void cancelNetRequest(NetRequest netRequest) {
}
}
如果以后有其他的網絡框架出來了,大家可以實現自己的NetRequestHandler 并替換默的,覆蓋這個方法就行了SmartListFragment。
/**
* 獲取網絡請求處理器
* @param context
* @return
*/
protected NetRequestHandler getNetRequestHandler(Context context) {
return new SmartNetRequestHandler(context);
}
另外,為了方便大家對網絡請求的統一修改,我才用責任鏈的方式實現了一個網絡請求修改器。
package com.zgh.smartlibrary.net;
/**
* 用于對網絡請求就行修改
* Created by zhuguohui on 2016/9/6.
*/
public interface NetRequestModifiers {
NetRequest modifyNetRequest(NetRequest request);
}
使用的時候只需要在覆蓋initNetRequestModifiers然后調用addNetRequestModifiers加入自己的NetRequestModifiers就行了,這個可以功能可以實現給所以的網絡請求加統一的Token等等。
@Override
protected void initNetRequestModifiers() {
addNetRequestModifiers(new NetRequestModifiers() {
@Override
public NetRequest modifyNetRequest(NetRequest request) {
return request;
}
});
}
補充一下,要想刷新界面調用這個方法就行了
/**
* 加載頁面數據
* @param pageIndex
*/
protected void loadListData(int pageIndex) {
requestIndex = pageIndex;
isUpdate = pageIndex == FIRST_PAGE_INDEX;
NetRequest request = pagePolicy.getNetRequestByPageIndex(pageIndex);
request.setResultListener(this);
if (isUpdate && adapterManager.getDataSize() == 0) {
showLoading();
}
if (isUpdate) {
//緩存首頁
request.setCache(NetRequest.CACHE.FILE);
}
listDataRequest = request;
loadData(request);
}
要想加載自己的網絡請求,使用這個方法
/**
* 加載網絡請求
* @param netRequest
*/
protected void loadData(NetRequest netRequest) {
netRequest = modifyNetRequest(netRequest);
netRequestHandler.handleNetRequest(netRequest);
}
5.數據展示
數據的展示關鍵點是Adapter,目前用的還是ListView,不過需求都能滿足。
package com.zgh.smartlibrary.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by 朱國輝
* Date: 2015/12/5
* Time: 22:26
*
*/
public abstract class SmartAdapter<E> extends BaseAdapter {
protected Context mContext;
private List<E> data;
public Map<Integer, SmartViewHolder> holderMap = new HashMap<>();
public SmartAdapter(Context ctx, List<E> data,
SmartViewHolder... holders) {
this.mContext = ctx;
this.data = data;
if (holders != null && holders.length > 0) {
for (int i = 0; i < holders.length; i++) {
holders[i].setType(i);
holderMap.put(holders[i].getViewType(), holders[i]);
}
} else {
throw new IllegalArgumentException("SmartViewHolder 不能為空");
}
}
public SmartAdapter(Context ctx, List<E> data,
List<SmartViewHolder> holders) {
this.mContext = ctx;
this.data = data;
int i = 0;
if (holders != null && holders.size() > 0) {
for (SmartViewHolder holder : holders) {
holder.setType(i++);
holderMap.put(holder.getViewType(), holder);
}
} else {
throw new IllegalArgumentException("SmartViewHolder 不能為空");
}
}
@Override
public E getItem(int position) {
if (!isEmpty(data)) {
return data.get(position);
}
return null;
}
@Override
public int getViewTypeCount() {
return holderMap.size();
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getCount() {
if (!isEmpty(data)) {
return data.size();
}
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
SmartViewHolder holder;
if (convertView == null) {
int type = getItemViewType(position);
convertView = LayoutInflater.from(mContext).inflate(holderMap.get(type).getLayoutId(), parent, false);
holder = buildHolder(convertView, type);
convertView.setTag(holder);
} else {
holder = (SmartViewHolder) convertView.getTag();
}
// 避免Item在滾動中出現黑色背景
convertView.setDrawingCacheEnabled(false);
E item = getItem(position);
holder.setContentView(convertView);
holder.updateView(mContext, item);
return convertView;
}
/**
* 用于自動綁定view
* @param convertView
* @param type
* @return
*/
private SmartViewHolder buildHolder(View convertView, int type) {
SmartViewHolder holder;
try {
holder = holderMap.get(type).getClass().newInstance();
List<Field> fields = getViewFields(holder.getClass());
for (Field f : fields) {
String name = f.getName();
f.setAccessible(true);
// ViewHolder的屬性,不論類型都初始化賦值
f.set(holder, convertView.findViewById(getId(name)));
}
} catch (Exception e) {
throw new RuntimeException("holder初始化出錯 " + e);
}
return holder;
}
/**
* ViewHolder中只有是View的子類的成員變量才會被初始化
* @param clazz
* @return
*/
private List<Field> getViewFields(Class clazz) {
List<Field> fields = new ArrayList<>();
while (clazz != null && clazz != SmartViewHolder.class) {
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f : declaredFields) {
if (isViewField(f)) {
fields.add(f);
}
}
clazz = clazz.getSuperclass();
}
return fields;
}
private boolean isViewField(Field f) {
Class<?> fType = f.getType();
boolean isView = false;
Class sclass = fType;
while (sclass != null && sclass != View.class) {
sclass = sclass.getSuperclass();
}
if (sclass == View.class) {
isView = true;
}
return isView;
}
public void addItems(List<E> extras) {
if (isEmpty(extras)) {
return;
}
data.addAll(getCount(), extras);
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
Collection<SmartViewHolder> holders = holderMap.values();
for (SmartViewHolder holder : holders) {
if (holder.isMyType(data.get(position))) {
return holder.getViewType();
}
}
throw new RuntimeException("沒有對應的 SmartViewHolder position=" + position + " item=" + data.get(position));
}
/**
* Some General Functions
*/
private boolean isEmpty(List<?> list) {
return (list == null || list.size() == 0);
}
public int getId(String name) {
try {
return mContext.getResources().getIdentifier(name, "id", mContext.getPackageName());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static abstract class SmartViewHolder<E> {
int type;
private View contentView;
/**
* 獲取該VIewHolder對應的Type
*
* @return
*/
public final int getViewType() {
return type;
}
/**
* 判斷是否是自己處理的數據類型
*
* @param item
* @return
*/
public abstract boolean isMyType(E item);
public void setType(int type) {
this.type = type;
}
/**
* 獲取對應的布局id
*
* @return
*/
public abstract int getLayoutId();
public abstract void updateView(Context context, E item);
public void setContentView(View contentView) {
this.contentView = contentView;
}
public View getContentView() {
return this.contentView;
}
}
}
6.界面定制
很多時候一個界面中并不是只需要一個ListView就能解決了,還需要有一個其他的內容,為了偷懶,我就通過使用占位符的信息來實現對自定義界面的需求。下面是我的占位符:

這里寫圖片描述
然后覆蓋SmartListFragment這個方法,返回自定義布局的ID
/**
* 獲取自定義布局的layoutID
* @return
*/
protected int getLayoutID() {
return 0;
}
最后的處理在這里,不難就是需要慢慢寫。
package com.zgh.smartlibrary.util;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import com.zgh.smartlibrary.R;
import java.util.ArrayList;
import java.util.List;
/**
* Created by zhuguohui on 2016/9/6.
*/
public class LayoutUtil {
public static View getBaseView(Context context, int layoutID, View mView, ListView listView) {
if (layoutID != 0) {
View warpView = View.inflate(context, layoutID, null);
View holderView = warpView.findViewById(R.id.ListViewHolder);
//判斷是否是LinearLayout
if (holderView instanceof LinearLayout) {
LinearLayout holder = (LinearLayout) holderView;
//獲取id為ListViewContent的view,如果沒有表示,子view全部都要添加為heardView
View contentView = holder.findViewById(R.id.ListViewContent);
List<View> headerViews = new ArrayList<>();
List<View> footViews = new ArrayList<>();
List viewList = headerViews;
for (int i = 0; i < holder.getChildCount(); i++) {
View childView = holder.getChildAt(i);
if (childView == contentView) {
viewList = footViews;
continue;
}
viewList.add(childView);
}
handleHeaderAndFooterView(listView, context, headerViews, footViews);
}
ViewGroup parent = (ViewGroup) holderView.getParent();
if (parent != null) {
int index = 0;
for (int i = 0; i < parent.getChildCount(); i++) {
if (parent.getChildAt(i) == holderView) {
index = i;
break;
}
}
parent.removeView(holderView);
ViewGroup.LayoutParams params = holderView.getLayoutParams();
mView.setLayoutParams(params);
parent.addView(mView, index);
mView = parent;
}
}
return mView;
}
private static void handleHeaderAndFooterView(ListView listView, Context context, List<View> headerViews, List<View> footViews) {
for (View view : headerViews) {
LinearLayout ViewParent = new LinearLayout(context);
if (view.getParent() != null) {
ViewGroup group = (ViewGroup) view.getParent();
group.removeView(view);
}
ViewParent.addView(view);
listView.addHeaderView(ViewParent);
}
for (View view : footViews) {
LinearLayout ViewParent = new LinearLayout(context);
if (view.getParent() != null) {
ViewGroup group = (ViewGroup) view.getParent();
group.removeView(view);
}
ViewParent.addView(view);
listView.addFooterView(ViewParent);
}
}
}
當自定義布局填充好了以后,通過這個方法可以拿到View的引用
@Override
protected void onViewInit() {
//記得使用父類的findViewById方法。
findViewById(R.id.tv_head1).setOnClickListener(this);
findViewById(R.id.tv_head2).setOnClickListener(this);
findViewById(R.id.tv_footer1).setOnClickListener(this);
findViewById(R.id.tv_fixed_head).setOnClickListener(this);
findViewById(R.id.tv_fload_view).setOnClickListener(this);
}
7.數據修改
對于返回的數據在現實之前,是可以修改的,也是使用責任鏈的模式實現的。
interface DataModifiers {
List<Object> modifyData(List<Object> data, boolean update);
}
使用的時候這樣使用覆蓋SmartListFragment的getDataModifiers方法返回自己的數據。
@Override
protected AdapterManager.DataModifiers[] getDataModifiers() {
//過濾數據
return new AdapterManager.DataModifiers[]{new NewsDataModifiers()};
}
我的demo中有一個例子,因為第二頁數據中包含有,banner數據等不需要的東西,所以過濾了一下
/**
* Created by yuelin on 2016/9/6.
*/
public class NewsDataModifiers implements AdapterManager.DataModifiers {
@Override
public List<Object> modifyData(List<Object> data, boolean update) {
if (!update) {
List<Object> newsList = new ArrayList<>();
for (Object object : data) {
if (object instanceof NewsItem) {
newsList.add(object);
}
}
data.clear();
data.addAll(newsList);
}
return data;
}
}
總結
這個框架也許還有很多問題,但對于我來說確實是不小的提升,特別是用到許多學過的設計模式,也一直在思考怎么解耦,怎么對修改封閉,對拓展開放等原則。寫完這個框架大概才有一點點軟件設計師的感覺,終于是在設計一些東西了,前路怎樣我不知道,但是希望我能用心的做好每一件事,用心的寫程序,而不是為了圖完成工作,既然都看到這里了,去GitHub上給我來個start。
來自:http://www.jianshu.com/p/3f9daa32ad98