关于新闻类应用快速开发框架的思考

序言

在上一篇博客 10分钟开发网易新闻首页的框架,我把我正在使用的框架拿出来分享了一下,但是部分同学告诉我源码看不懂,我觉得也有必要说说我对这个框架的思考过程。

流程

目前我主要开发的是新闻资讯类的应用,所以这个框架也主要是针对这类应用设计的,在这一类应用中,最重要的就是内容展示,一般都是以列表的形式展示数据,比如下图:

这里写图片描述

而这类页面还要有下拉刷新,上滑加载下一页,加载进度,错误重试等功能,但是这些功能都是公有的,而每个页面所不同的就是展示的内容的不同(废话),而这些内容又是以不同的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 "";
    }


}

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);

}

有一个默认的实现

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);
    }


}

大家可以通过重写SmartListFragment中的这个方法实现自己的替换。

   /**
     * 获取具有下拉刷新,上拉加载更多功能的接口
     * @param context
     * @return
     */
    protected  ListViewUpdateManger getUpdateManager(Context context){
        return new PullToRefreshManger(context);
    }

上述的接口只是提供上拉加载更多的功能,而具体的逻辑每一个应用都各有不同为此,我使用这个接口实现对分页逻辑的解耦

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。zhuguohui/SmartDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容