关于新闻类应用快速开发框架的思考
<h2><strong>流程</strong></h2> <p>目前我主要开发的是新闻资讯类的应用,所以这个框架也主要是针对这类应用设计的,在这一类应用中,最重要的就是内容展示,一般都是以列表的形式展示数据,比如下图:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/989745677f34c8ddd08b9564acaa67fe.png"></p> <p style="text-align:center">这里写图片描述</p> <p>而这类页面还要有下拉刷新,上滑加载下一页,加载进度,错误重试等功能,但是这些功能都是公有的,而每个页面所不同的就是展示的内容的不同(废话),而这些内容又是以不同的item为基础的。例如下面不同的item:</p> <p><strong>Item1</strong></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/eff2fa33cd60575268e9b5b6c8a1eafc.png"></p> <p style="text-align:center">这里写图片描述</p> <p><strong>item2</strong></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/0d26b4c3fa8750c676274f5d52f5c7aa.png"></p> <p style="text-align:center">这里写图片描述</p> <p>而在软件开发中,所不同的就是JSON数据的不同,JavaBean的不同,Layout的不同。大体的对应关系如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3eca6dc67e15146c21503793d5a237b2.png"></p> <p style="text-align:center">这里写图片描述</p> <p>因此在实际开发中,最小的粒度就是一个Object。我的框架工作流程就是找到这些JsonObject然后转化为对应的JavaBean传统,实例对应的ViewHolder,然后将数据传递给对应的ViewHolder,ViewHolder绑定数据,最终一个新闻列表就显示出来了,大概的流程如下:</p> <p><img src="https://simg.open-open.com/show/dcfb5c5a599e72855309bfdf0b92db04.png"></p> <p style="text-align:center">这里写图片描述</p> <h2><strong>实现</strong></h2> <h3><strong>1. JSON数据的解析</strong></h3> <p>在一般的开发流程中,我们会将服务器返回的Json数据直接转换成一个JavaBean,然后再操作。在界面不是很复杂的情况下这么做无可厚非,但是在多个界面有相同的Item,又有部分不同的Item。这就会造成,每一个界面的数据都要有一个对应的JavaBean,而且这些解析都会固化在类中,或许可以通过继承实现部分的复用,但是对于扩展性并不优化,三大设计原则中就有,组合优于继承,因此我所做的就是降低粒度,即针对JsonObject,而不是全部的数据。对于如何定位,我自己设计了一种定位的描述符:</p> <p>比如要定位到下面这个数组:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/2593fb737ca994998274e6b627cab74a.png"></p> <p style="text-align:center">这里写图片描述</p> <p>我在配置文件中是这么写的</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/d0a64adae16ed4cc49cec13e0c4e4522.png"></p> <p style="text-align:center">这里写图片描述</p> <p>大框中括起来的最终会转换为JsonConfig对象的素组,交给JsonAnalysisEngine 处理,而JsonAnalysisEngine 会循环遍历然后处理每一个JsonConfig,首先会更具jsonLocation的位置对原来的数据进行过滤,找到需要的数据,最后自动转化为对应的Javabean,关于使用大家看注释吧,目前来看可能性能还不是很好,我在后续的版本可能会更换实现方式。</p> <pre> <code class="language-java">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> <h3><strong>2.下拉刷新,上拉加载更多</strong></h3> <p>为了以后的可扩展性,我把下拉刷新,上拉加载更多抽象成一个接口</p> <pre> <code class="language-java">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> <p>有一个默认的实现</p> <pre> <code class="language-java">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> <p>大家可以通过重写SmartListFragment中的这个方法实现自己的替换。</p> <pre> <code class="language-java">/** * 获取具有下拉刷新,上拉加载更多功能的接口 * @param context * @return */ protected ListViewUpdateManger getUpdateManager(Context context){ return new PullToRefreshManger(context); }</code></pre> <p>上述的接口只是提供上拉加载更多的功能,而具体的逻辑每一个应用都各有不同为此,我使用这个接口实现对分页逻辑的解耦</p> <pre> <code class="language-java">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 } }</code></pre> <p>我的实闲是这样的</p> <pre> <code class="language-java">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; } }</code></pre> <p>我的分页信息在这里</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/10504d12b7195efa2e33b943c5e45ebf.png"></p> <p style="text-align:center">这里写图片描述</p> <p>大家更具自己的项目情况来实现自己的分页策略,只需要覆盖SmartListFragment中的这个方法就行了</p> <pre> <code class="language-java">/** * 获取分页策略 * @return */ protected abstract IPagePolicy getPagePolicy();</code></pre> <h3><strong>3.状态切换</strong></h3> <p>此处使用的是张鸿洋的LoadingAndRetryManager,具体的用法如下参考这里 <a href="/misc/goto?guid=4959716681551279701" rel="nofollow,noindex">LoadingAndRetryManager</a> 。</p> <p>我简单说一下用法,在Application的Oncreate中设置默认的样式</p> <pre> <code class="language-java">/** * 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; } }</code></pre> <p>如果某个Fragment有特殊需求,覆盖这个方法就行了</p> <pre> <code class="language-java">protected OnLoadingAndRetryListener createLoadingAndRetryListener() { return null; }</code></pre> <h3><strong>4.网络请求</strong></h3> <p>目前的网络框架有很多,但是我们不应该依赖于具体的某一个框架,而是采用面向接口编程的实现,把网络请求这块与具体的实现分开。于是我定义了两个接口,一个用于网络请求,一个用于网络请求的处理:</p> <pre> <code class="language-java">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); } }</code></pre> <p>一个用于处理请求:</p> <pre> <code class="language-java">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); }</code></pre> <p>有一个采用张鸿洋的OkHttpUtil的默认实现</p> <pre> <code class="language-java">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) { } }</code></pre> <p>如果以后有其他的网络框架出来了,大家可以实现自己的NetRequestHandler 并替换默的,覆盖这个方法就行了SmartListFragment。</p> <pre> <code class="language-java">/** * 获取网络请求处理器 * @param context * @return */ protected NetRequestHandler getNetRequestHandler(Context context) { return new SmartNetRequestHandler(context); }</code></pre> <p>另外,为了方便大家对网络请求的统一修改,我才用责任链的方式实现了一个网络请求修改器。</p> <pre> <code class="language-java">package com.zgh.smartlibrary.net; /** * 用于对网络请求就行修改 * Created by zhuguohui on 2016/9/6. */ public interface NetRequestModifiers { NetRequest modifyNetRequest(NetRequest request); }</code></pre> <p>使用的时候只需要在覆盖initNetRequestModifiers然后调用addNetRequestModifiers加入自己的NetRequestModifiers就行了,这个可以功能可以实现给所以的网络请求加统一的Token等等。</p> <pre> <code class="language-java">@Override protected void initNetRequestModifiers() { addNetRequestModifiers(new NetRequestModifiers() { @Override public NetRequest modifyNetRequest(NetRequest request) { return request; } }); }</code></pre> <p>补充一下,要想刷新界面调用这个方法就行了</p> <pre> <code class="language-java">/** * 加载页面数据 * @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); }</code></pre> <p>要想加载自己的网络请求,使用这个方法</p> <pre> <code class="language-java">/** * 加载网络请求 * @param netRequest */ protected void loadData(NetRequest netRequest) { netRequest = modifyNetRequest(netRequest); netRequestHandler.handleNetRequest(netRequest); }</code></pre> <h3><strong>5.数据展示</strong></h3> <p>数据的展示关键点是Adapter,目前用的还是ListView,不过需求都能满足。</p> <pre> <code class="language-java">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; } } }</code></pre> <h3><strong>6.界面定制</strong></h3> <p>很多时候一个界面中并不是只需要一个ListView就能解决了,还需要有一个其他的内容,为了偷懒,我就通过使用占位符的信息来实现对自定义界面的需求。下面是我的占位符:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/610cf68c852579a9263051a24057702b.png"></p> <p style="text-align:center">这里写图片描述</p> <p>然后覆盖SmartListFragment这个方法,返回自定义布局的ID</p> <pre> <code class="language-java">/** * 获取自定义布局的layoutID * @return */ protected int getLayoutID() { return 0; }</code></pre> <p>最后的处理在这里,不难就是需要慢慢写。</p> <pre> <code class="language-java">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); } } }</code></pre> <p>当自定义布局填充好了以后,通过这个方法可以拿到View的引用</p> <pre> <code class="language-java">@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); }</code></pre> <h3><strong>7.数据修改</strong></h3> <p>对于返回的数据在现实之前,是可以修改的,也是使用责任链的模式实现的。</p> <pre> <code class="language-java">interface DataModifiers { List<Object> modifyData(List<Object> data, boolean update); }</code></pre> <p>使用的时候这样使用覆盖SmartListFragment的getDataModifiers方法返回自己的数据。</p> <pre> <code class="language-java">@Override protected AdapterManager.DataModifiers[] getDataModifiers() { //过滤数据 return new AdapterManager.DataModifiers[]{new NewsDataModifiers()}; }</code></pre> <p>我的demo中有一个例子,因为第二页数据中包含有,banner数据等不需要的东西,所以过滤了一下</p> <pre> <code class="language-java">/** * 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; } }</code></pre> <h2><strong>总结</strong></h2> <p>这个框架也许还有很多问题,但对于我来说确实是不小的提升,特别是用到许多学过的设计模式,也一直在思考怎么解耦,怎么对修改封闭,对拓展开放等原则。写完这个框架大概才有一点点软件设计师的感觉,终于是在设计一些东西了,前路怎样我不知道,但是希望我能用心的做好每一件事,用心的写程序,而不是为了图完成工作,既然都看到这里了,去GitHub上给我来个start。</p> <p> </p> <p> </p> <p>来自:http://www.jianshu.com/p/3f9daa32ad98</p> <p> </p>
本文由用户 hrlj1816 自行上传分享,仅供网友学习交流。所有权归原作者,若您的权利被侵害,请联系管理员。
转载本站原创文章,请注明出处,并保留原始链接、图片水印。
本站是一个以用户分享为主的开源技术平台,欢迎各类分享!