浅谈BaseActivity的进阶写法,基础框架的搭建,促使我们更高效便捷开发

@TOC.

浅谈BaseActivity的进阶写法,基础框架的搭建,促使我们更高效便捷开发

大家好!时隔许久没写过博客了,最近因疫情原因导致更换了公司,接收公司的项目代码,AB端外加两个基础打印项目共4个,4个项目用了3个不同的网络访问风格和代码风格,最让我哭笑不得的是只有一个界面的打印项目还运用的 MVP 写法,在看项目base基本无封装,无共用代码块,再改了几个需求终觉得不可忍受,就有了这次的基础框架搭建。(当然老菜鸟能力有限,大神请轻喷)

先看看现在项目的base吧

A端

public class BaseActivity extends RxAppCompatActivity {
    private static final String TAG = "BaseActivity";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        try {
            //Util.disabledDisplayDpiChange(this);
        } catch (Exception e) {
            Log.e(TAG, "disabledDisplayDpiChange failed!", e);
        }
    }
}

B端

public abstract class BaseActivity extends RxFragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getContentViewId());
        ButterKnife.bind(this);
        getWindow().setBackgroundDrawable(null);
        init();
    }

    protected abstract void init();

    protected abstract int getContentViewId();
}

看完是不是内心有种妈卖批的冲动,想改个title图标都得单个单个去修改,而且不同的界面Activity里相同代码一堆,简直无复用,要改某一个方法就得每个界面去修改,我太难了!说好的封装呢?

思考一下?

一个BaseActivity需要些什么呢?一般我们都会根据项目需求,原型来配置,当然也逃不掉一套基础配置,对于代码编写,每个人都有每个人的风格,这里就说说我的看法(轻喷)。以前我都是从封装共用代码的角度出发来搭建BaseActivity的,到后来我想精简我们每个功能性界面的时候才醒悟过来,从结果角度出发,思考怎么精简我们的业务逻辑界面,最好的一键生成,傻瓜式编写,这样也是不错的角度,通常业务界面需要的功能有哪些呢?我把他分为5类(分类根据个人和项目实际情况)

1、只需要提交数据的界面:如修改密码,昵称之类的个人设置
2、只需要拉取数据的非列表界面:如查看个人资料,商品信息等
3、既需要拉取数据又要提交数据界面:如修改店铺资料
4、列表界面(加头部加尾部,上拉加载下拉刷新)
5、复杂多列表界面(很多商城类APP首页都是)

那么我们应该怎么精简这些界面呢?我例举一个界面最少需要的东西(最少哈,业务不同的自行添加),顺序对应上面的5类

界面的Title,界面的xml,Url链接 这都是必须要的
1、 +提交的参数,提交完成的逻辑处理
2、+拉取数据成功的逻辑处理
3、+两个Url链接,拉取数据成功的逻辑处理,提交的参数,提交完成的逻辑处理
4、+界面的xml变item的,item的数据处理和业务逻辑处理,是否开启上拉加载,是否添加头尾等
5、 +内容xml+url链接(可能多个)+Binder的创建+获取数据源之后的逻辑处理

上面简单列一下用到的框架,不然有些看不懂

butterknife:用来实例化控件
retrofit2+rxjava2+rxlifecycle2+okhttp3+rxbinding3+rxpermissions:一套
bga-baseadapter:RecyclerView.Adapter的封装
multitype:RecyclerView 辅助 多列表,主要用于4.5
kprogresshud:加载框
SmartRefreshLayout,SmartRefreshHeader:上拉下滑 刷新布局的使用

下面看一下我根据上面的思想初步封装的base的使用

单纯的提交数据用例

public class TestUpDataActivity extends BaseUpDataActivity {

    @BindView(R.id.test_tv)
    TextView testTv;
    private String A, B;

    @Override
    protected String getTitles() {
        return "单纯的提交数据用例";
    }

    @Override
    public int getContentLayoutId() {
        return R.layout.activity_test;
    }

    @Override
    public String getUrl() {
        return Urls.URL_VERSION;
    }

    @Override
    public Map getMapHelper() {
        return new MapHelper().param("AAA", A).param("BBB", B).build();
    }

    @Override
    public void initListener() {
        super.initListener();
        A = "aaa";
        B = "bbb";
    RxView.clicks(testTv).throttleFirst(Constant.CLICK_DELAY_TIME, TimeUnit.MILLISECONDS)
                .compose(bindToLifecycle())
                .subscribe(unit ->  intoHttp(TestUpDataActivity.this));
    }
}

测试用例:第1类,可以看到我的activity只有5个方法,是不是简单明了(由于是测试用例,所以写的比较简便但不影响阅读),(界面的Title,界面的xml,Url链接,提交的参数,提交完成的逻辑处理)正好就是这5个方法就完成了一个简单界面,当然getTitles,getUrl,getMapHelper,也可以换多种形式编写,例如base里定义变量,在提交之前赋值或者base定义方法set赋值,个人觉得这种不易被忽视忘记,并且方法自动生成,下面大家可能比较好奇intoHttp(),我们进去看一下

单纯的提交数据用例Base

public abstract class BaseUpDataActivity extends BaseHeaderActivity {

    public T t;
    
    public void intoHttp() {
        HttpClient.getInstance().doPostWithToken(this, getUrl(), getMapHelper(), new CallbackObserver() {
            @Override
            public void onSuccess(BaseResponse httpResult) {
                initSuccess(httpResult);
            }
        });
    }

    public void intoHttp(BaseActivity context) {
        HttpClient.getInstance().doPostWithToken(this, getUrl(), getMapHelper(), new CallbackObserver(context) {
            @Override
            public void onSuccess(BaseResponse httpResult) {
                initSuccess(httpResult);
            }
        });
    }
    
    public void initSuccess(BaseResponse response) {
        ToastUtil.showToastSuccess(response.getMsg());
        Type type = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        if (type != null) {
            t = GsonUtils.fromJson(GsonUtils.toJson(response.data), type);
        }
        initRefreshView();
    }

    /**
     * 默认提交数据完成的操作
     */
    public void initRefreshView() {
        toFinish();
    }
    
    public abstract String getUrl();

    public abstract Map getMapHelper();

}

可以看到,我在原本的base之上在加了一层,该层主要是为了精简访问网络,处理访问网络之后的默认代码,再在base里面就是大家所熟知的基础封装了,代码就不列了,简单说明一下

1、屏幕横竖屏切换,AndroidManifest中将不用再写android:screenOrientation=“portrait”(之前看到过一篇博客分析,推荐Manifest设置更优,但是要每个添加)
2、ButterKnife绑定页面和解绑
3、沉浸式实现以及顶部图片顶置等
4、activity普通跳转,携带数据的页面跳转
5、返传值跳转及setResult
5、返回键时间监听
6、标题栏统一实现(是否需要标题栏等)
7、仿IOS侧滑finish页面实现(继承SwipeBackActivity),默认关闭动画等,
8、Loading页面统一实现(kprogresshud)
9、AppManager管理类的add及remove
10、数据展示页面的State(各种状态自动封装,如无网络,无数据,服务器错误等)

基本上就是这些,当然一些跟项目相关的东西我就不例举了,下面看看第2类的界面代码吧

单纯的获取服务器数据展示用例

public class TestGetDataActivity extends BaseDataActivity {

    @BindView(R.id.test_tv)
    TextView testTv;

    @Override
    protected String getTitles() {
        return "获取服务器数据展示用例";
    }

    @Override
    public int getContentLayoutId() {
        return R.layout.activity_test;
    }

    @Override
    public String getUrl() {
        return Urls.URL_VERSION;
    }

    @Override
    public void initRefreshView() {
        testTv.setText(t.getDescribes());
    }
}

测试用例:第2类,可以看到我的activity只有4个方法(由于是测试用例,所以写的比较简便但不影响阅读),(界面的Title,界面的xml,Url链接,读取到数据之后的界面逻辑处理)正好就是这4个方法就完成了一个简单界面,当然getTitles,getUrl,跟上面讲的一样,实习方法多样化,但是的注意先赋值在访问,当然有些可以需要参数,那么像第一类一样加进去就好了,下面我们来看看他继承的类。

单纯的获取服务器数据展示用例的base

public abstract class BaseGetDataActivity extends BaseHeaderActivity {

    public T t;

    @Override
    public void initData() {
        super.initData();
        if (isIntoHttp())
            intoHttp(this);
    }

    public void intoHttp() {
        HttpClient.getInstance().doGetWithToken(this, getUrl(), new CallbackObserver() {
            @Override
            public void onSuccess(BaseResponse httpResult) {
                initSuccess(httpResult);
            }
        });
    }

    public void intoHttp(BaseActivity context) {
        HttpClient.getInstance().doGetWithToken(this, getUrl(), new CallbackObserver(context) {
            @Override
            public void onSuccess(BaseResponse httpResult) {
                initSuccess(httpResult);
            }
        });
    }

    public abstract String getUrl();

    public abstract void initRefreshView();

    /**
     * 是否进来就加载
     *
     * @return
     */
    public boolean isIntoHttp() {
        return true;
    }

    public void initSuccess(BaseResponse response) {
            Type type = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
            t = GsonUtils.toJsonBean(response.data, type);
            if (t!=null){
                initRefreshView();
            }
    }
}

跟之前的差不多,该层也主要是为了精简访问网络,处理访问网络数据的默认代码,由于代码比较简单就没加什么注释,再在base里面就是上面所讲的那些了,这里不做解释。可以大家比较期待的还是列表类,复杂列表类,因为这些才是正真方便很多的,好了第四类我们就不说讲,基本跟1.2类似,我们之间跳到第4类。

列表类该如何封装

以前每次实现列表类的界面,都需要从之前写的列表界面拷贝许多代码过来,就会比较烦,后来我就想着要把这些代码都抽取出来封装好,我们先来想想实现一个列表界面最少最少需要几个方法

1、标题:setTitles,
2、item内容xml
3、拉取数据的url
4、获取服务器数据的参数
5、adapter里的item数据处理和代码逻辑

来看下我封装之后的简单列表实现吧,默认开启上拉下滑功能

单纯的列表用例,默认支持上拉加载下拉刷新

public class TestItemActivity extends BaseItemActivity {
    public int pageNo,pageSize;

    @Override
    protected String getTitles() {
        return "测试list";
    }

    @Override
    public int getItemLayoutId() {
        return R.layout.activity_item_test;
    }

    @Override
    public String getUrl() {
        return Urls.testList;
    }

    @Override
    public Map getMapHelper() {
        return new MapHelper().param("pageNo",pageNo+++"").param("pageSize",pageSize+"").param("AAA", "").param("BBB", "").build();
    }

    @Override
    public void itemCover(BGAViewHolderHelper helper, int position, TestListBean model) {
        helper.setText(R.id.item_test_tv, model.getGoodsName());
    }
}

测试用例:第4类,整好就是我说的那五个最少的方法,是不是觉得好简单,从此之后妈妈再也不用担心我写列表界面繁琐了,(由于是测试用例,所以写的比较简便但不影响阅读),getTitles,getUrl,getMapHelper,依旧是可以按照自己的写法搞,下面给大家看看BaseItemActivity里面的代码封装(由于更换了网络框架,之前用的okgo,尚未正式使用,可能在一些判断上还存在问题)

列表用例的BaseItemActivity代码

public abstract class BaseItemActivity extends BaseStateActivity implements AdapterCoverHelper {

    @BindView(R.id.mRecyclerView)
    public RecyclerView mRecyclerView;
    @BindView(R.id.mNestedScrollView)
    public NestedScrollView mNestedScrollView;
    @BindView(R.id.refreshLayout)
    public SmartRefreshLayout refreshLayout;

    public List items = new ArrayList<>();
    public BaseItemAdapter baseItemAdapter;
    public int pageNo=1,pageSize=20;
    @Override
    public int getContentLayoutId() {
        return R.layout.activity_baseitem;
    }

    @Override
    protected void initStateRefresh() {
        setState(Constant.STATE_LOADING);
    }

    @Override
    protected void initView() {
        initRecyclerView();
        initAdapterView();
    }

    public void initRecyclerView() {
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        if (isDividerItemDecoration()) {
            DividerItemDecoration divider = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
            divider.setDrawable(ContextCompat.getDrawable(this, R.drawable.recommend_item_divider));
            mRecyclerView.addItemDecoration(divider);
        }
        mRecyclerView.setFocusable(false);
    }

    public void initAdapterView() {
        baseItemAdapter = new BaseItemAdapter(mRecyclerView, getItemLayoutId(), this);
        baseItemAdapter.setData(items);
        baseItemAdapter.notifyDataSetChanged();
        if (getHeaderView() != null) baseItemAdapter.addHeaderView(getHeaderView());
        if (addFooterView() != null) baseItemAdapter.addFooterView(addFooterView());
        mRecyclerView.setAdapter(baseItemAdapter);
    }

    @Override
    public void initListener() {
        refreshLayout.setOnRefreshListener(new OnRefreshListener() {
            @Override
            public void onRefresh(RefreshLayout refreshlayout) {
                isOpenLoadMore(true);
                pageNo=1;
                items.clear();
                initHttpData();
            }
        });
        refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore(RefreshLayout refreshlayout) {
                initHttpData();
            }
        });
    }

    @Override
    public void initHttpData() {
        if (isIntoHttp())
            HttpClient.getInstance().doGetWithToken(this, getUrl(),getMapHelper(), new DisposableObserver() {
                @Override
                public void onNext(ResponseBody t) {
                    try {
                        convert(t);
                    } catch (IOException e) {
                        e.printStackTrace();
                        initErrorFinishRefreshAndLoadMore();
                    }
                }
                @Override
                public void onError(Throwable e) {
                    initError(e);
                }

                @Override
                public void onComplete() {

                }
            });
    }

    public void convert(ResponseBody value) throws IOException {
        Type type = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        //Class tClass = (Class)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        Type types = new TypeToken>() {
        }.getType();
        BaseResponse baseResponse = GsonUtils.fromJson(value.string(), types);
        List list = GsonUtils.getObjectList(baseResponse.data, type);
        value.close();
        if (baseResponse.status == 1) {
            initSuccess(list);
        } else {
            if (!isSize(baseItemAdapter.getData()))
            initSetState(Constant.STATE_ERROR);
            initErrorFinishRefreshAndLoadMore();
        }
    }

    public abstract int getItemLayoutId();

    public abstract String getUrl();
    public abstract Map getMapHelper();
    public boolean isDividerItemDecoration() {
        return true;
    }

    /**
     * 是否进来就加载
     *
     * @return
     */
    public boolean isIntoHttp() {
        return true;
    }

    public View getHeaderView() {
        return null;
    }

    public View addFooterView() {
        return null;
    }

    public void initSuccess(BaseResponse> response) {
        if (response.status == 1) {
            if (isSize(response.data)) {
                if (!flStateContent.isSuccess())
                initSetState(Constant.STATE_SUCCESS);
                initSuccessFinishRefreshAndLoadMore();
                items.addAll(response.data);
                baseItemAdapter.setData(items);
            } else {
                if (items.size()==0)
                initSetState(Constant.STATE_EMPTY);
                initErrorFinishRefreshAndLoadMore();
                isOpenLoadMore(false);
            }
        } else {
            initSetState(Constant.STATE_ERROR);
            initErrorFinishRefreshAndLoadMore();
        }
        //if (refreshLayout == null) return;
        //refreshLayout.computeScroll();
    }

    public void initSuccess(List response) {
        if (isSize(response)) {
            if (!flStateContent.isSuccess())
            initSetState(Constant.STATE_SUCCESS);
            initSuccessFinishRefreshAndLoadMore();
            items.addAll(response);
            baseItemAdapter.setData(items);
        } else {
            if (items.size()==0)
            initSetState(Constant.STATE_EMPTY);
            initErrorFinishRefreshAndLoadMore();
            isOpenLoadMore(false);
        }
        //if (refreshLayout == null) return;
        //refreshLayout.computeScroll();
    }

    public void initError(Throwable response) {
        initSetState(Constant.STATE_ERROR);
        ToastUtil.showToastError(response.getMessage());
        initErrorFinishRefreshAndLoadMore();
    }

    public void initSetState(int state) {
        if (flStateContent.isSuccess()){
            if (!isSize(baseItemAdapter.getData()))
                setState(state);
        }else {
            setState(state);
        }
    }

    /**
     * 成功拉取服务器数据,刷新拉取时间
     *
     * @param isRefresh true 下拉刷新结束 false 上拉加载结束
     */
    private void initSuccessFinishRefreshAndLoadMore(boolean isRefresh) {
        if (refreshLayout == null) return;
        if (isRefresh) {
            refreshLayout.finishRefresh(true);
        } else {
            refreshLayout.finishLoadMore(true);
        }
    }

    /**
     * 访问服务器失败,不刷新拉取时间
     *
     * @param isRefresh true 下拉刷新结束 false 上拉加载结束
     */
    private void initErrorFinishRefreshAndLoadMore(boolean isRefresh) {
        if (refreshLayout == null) return;
        if (isRefresh) {
            refreshLayout.finishRefresh(false);
        } else {
            refreshLayout.finishLoadMore(false);
        }
    }

    /**
     * 是否开启上拉加载,未满一屏则默认不可下拉加载
     *
     * @param isOpen
     */
    private void isOpenLoadMore(boolean isOpen) {
        if (refreshLayout == null) return;
        refreshLayout.setEnableLoadMore(isOpen);
    }

    private void initErrorFinishRefreshAndLoadMore() {
        if (refreshLayout == null) return;
        switch (refreshLayout.getState()) {
            case Refreshing:
                refreshLayout.finishRefresh(false);
                break;
            case Loading:
                refreshLayout.finishLoadMore(false);
                break;
            case None:
                break;
            default:
                refreshLayout.setNoMoreData(false);
        }
    }

    private void initSuccessFinishRefreshAndLoadMore() {
        if (refreshLayout == null) return;
        switch (refreshLayout.getState()) {
            case Refreshing:
                refreshLayout.finishRefresh(true);
                break;
            case Loading:
                refreshLayout.finishLoadMore(true);
                break;
            case None:
                break;
            default:
                refreshLayout.setNoMoreData(true);
        }
    }
}

BaseItemAdapter代码

public class BaseItemAdapter extends BGARecyclerViewAdapter {

    private AdapterCoverHelper coverHelper;
    public BaseItemAdapter(RecyclerView recyclerView, int itemLayoutId, AdapterCoverHelper coverHelper) {
        super(recyclerView, itemLayoutId);
        this.coverHelper = coverHelper;
    }

    @Override
    protected void fillData(BGAViewHolderHelper helper, int position, T model) {
        coverHelper.itemCover(helper, position, model);
    }
}

AdapterCoverHelper代码

public interface AdapterCoverHelper {
    void itemCover(BGAViewHolderHelper helper, int position, T model);
}

BaseItemActivity引用的xml



    

        
    

BaseStateActivity代码是我在BaseActivity基础上加了状态界面的封装代码,这里就不展示了,比较简单(状态:加载中,空界面,错误界面,无网络界面)

可以看到,我把RecyclerView,SmartRefreshLayout的默认配置,以及数据的获取,解析,和对网络获取数据成功失败的判断 操作都放在了BaseItemActivity中,大大减少了我们在编写列表是所需要的代码,本来数据解析是可以更简单一些的,可以由于泛型传达的擦除,导致需要重新解析一次,不过还是可以,毕竟以后书写的代码量少很香的,逻辑也相当的清晰,哈哈。

结尾

由于篇幅限制,第五种复杂列表封装在这里就先不记录了,毕竟用到的也比较少,一般主页大家也都不会用多列表来实现,而会选择碎片化的fragment,我也是因为项目需要自己写一个APP聊天系统,需要写聊天界面,所以才封装了这么一个复杂列表实现基类,下次有时间在记录吧。

老菜鸟水平不高,有大神浏览到还希望多给给意见,指出指出错误哈,不胜感激。

你可能感兴趣的:(平时笔记)