App架构设计实战二:基于MVP模式的相似UI界面复用问题解决方案

碰到的问题

不知道大家在平时有没有碰到这种情况:在项目中有很多UI界面相似的页面。比如:

图1图2
这里写图片描述这里写图片描述

在项目中一开始看到类似这样的页面我是很高兴的,以为可以复用了。氮素,后来稍微深入发现有几个问题:

1.项目中每个页面的接口肯定是不一样的;

2.每个接口返回的数据也是不一致的,如下:

图1图2
这里写图片描述这里写图片描述

因为这种界面都要用到recyclerview的适配器adapter,这种数据完全没法复用嘛。一般的MVP写法就是两个activity,两个view加上两个adapter。如果这种页面有10个呢?那要写10个嘛,那个太难受了,必须要复用啊。

怎么复用啊

首先要知道我们可以复用什么,我们要知道的是我们重复写了

1.Activity

2.View

3.Adapter

一开始我的想法是我定义一个通用的实体类UniversalEntity

/**
 * <pre>
 *     作者   : 肖坤
 *     时间   : 2018/05/03
 *     描述   : 通用的实体类
 *     版本   : 1.0
 * </pre>
 */
public class UniversalEntity
{
    private String text1;
    private String text2;
    private String text3;

    public String getText1()
    {
        return text1;
    }

    public void setText1(String text1)
    {
        this.text1 = text1;
    }

    public String getText2()
    {
        return text2;
    }

    public void setText2(String text2)
    {
        this.text2 = text2;
    }

    public String getText3()
    {
        return text3;
    }

    public void setText3(String text3)
    {
        this.text3 = text3;
    }
}

这样有什么好处呢,对的,可以复用Adapter,如下:

/**
 * <pre>
 *     作者   : 肖坤
 *     时间   : 2018/05/03
 *     描述   : 通用的页面的列表适配器
 *     版本   : 1.0
 * </pre>
 */
public class UniversalRvAdapter extends BaseQuickAdapter<UniversalEntity, BaseViewHolder>
{
    public UniversalRvAdapter(int layoutResId)
    {
        super(layoutResId);
    }

    @Override
    protected void convert(BaseViewHolder helper, UniversalEntity item)
    {
        String publishedAt = item.getText2();
        publishedAt = publishedAt.substring(0, 10);
        helper.setText(R.id.category_desc, item.getText1());
        helper.setText(R.id.category_author, item.getText3());
        helper.setText(R.id.category_date, publishedAt);

        CardView cardView = (CardView) helper.getView(R.id.night_cv);
        cardView.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                Toast.makeText(mContext, "点击", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

我们在每一个数据返回后将其转换成通用实体类,如下:

List<UniversalEntity> universalEntities = new ArrayList<>();
for (XmNeswResEntity.DataBean dataBean : dataBeans)
{
    universalEntities.add(dataBean);
}

然后再将其设置到adapter中,这样我们就可以只用一个adapter,也就是说我们复用了adapter。

大概图示如下:

这里写图片描述

这样比之前好多了,有同学说我们用一个Activiyt来实现所有的View嘛,大概就是下面这个图示:

这里写图片描述

这样虽然复用了Activity,但是会造成这个Activity里的重写接口的方法会非常多,一旦接口超过3个,那这个方法就不太好了。那该怎么办呢?

其实仔细点注意可以发现这些数据返回的结构是一样的,毕竟UI的界面是一样的,数据也是为了页面服务的嘛。所以我们可以用接口来抽象哦。因为说到底像这种页面就只要3个String数据嘛对不对,所以我就定义一个通用的接口类:

/**
 * <pre>
 *     作者   : 肖坤
 *     时间   : 2018/05/03
 *     描述   : 通用接口,表示通用界面中只需要这三个数据
 *     版本   : 1.0
 * </pre>
 */
public interface UniversalResEntity
{
    String getText1();
    String getText2();
    String getText3();
}

然后呢,让每个真是返回的数据来实现这个接口比如:

public static class DataBean implements UniversalResEntity
{
    /**
     * desc : MusicLibrary-一个丰富的音频播放SDK
     * publishedAt : 2018-03-12T08:44:50.326Z
     * who : lizixian
     */
    private String desc;
    private String publishedAt;
    private String who;

    //省略大段get和set

    @Override
    public String getText1()
    {
        return desc;
    }

    @Override
    public String getText2()
    {
        return publishedAt;
    }

    @Override
    public String getText3()
    {
        return who;
    }
}

如果你还没看我的App架构设计实战一:初步引入MVP 的话,你可以先看看那个,本篇基于MVP模式来做的复用。那样的话ApiService中还是照常:

//gson并不能解析一个接口,所以必须采用明确的实体类
@GET("tools/mockapi/440/fake_gank")
Observable<BaseResponse<List<GankResEntity.DataBean>>> getGankData();

@GET("tools/mockapi/440/fake_xiaomi")
Observable<BaseResponse<List<XmNeswResEntity.DataBean>>> getXmNews();

然后MainModel中还是照常添加:

//获取gank
public Observable<List<GankResEntity.DataBean>> getGankData()
{
    return apiService.getGankData()
            .map(new HttpResultFunc<List<GankResEntity.DataBean>>())
            .compose(RxSchedulers.<List<GankResEntity.DataBean>>io_main());
}

//获取小米新闻
public Observable<List<XmNeswResEntity.DataBean>> getXmData()
{
    return apiService.getXmNews()
            .map(new HttpResultFunc<List<XmNeswResEntity.DataBean>>())
            .compose(RxSchedulers.<List<XmNeswResEntity.DataBean>>io_main());
}

然后写通用的View:

/**
 * <pre>
 *     作者   : 肖坤
 *     时间   : 2018/05/03
 *     描述   :
 *     版本   : 1.0
 * </pre>
 */
public interface UniversalView extends BaseView
{
    void getUniversalSuc(List<UniversalResEntity> entity);
    void getUniversalFailed(String errorMsg);
}

然后对于presenter我的建议还是一个模块用一个比较好,然后一个模块用一个BaseView,所以MainPresenter中的View换成BaseView。添加如下:

public void getGankData()
{
    if (getView() instanceof UniversalView)
    {
        BaseObserver<List<GankResEntity.DataBean>> observer = new BaseObserver<List<GankResEntity.DataBean>>(rxManager)
        {
            @Override
            public void onErrorMsg(String msg)
            {
                ((UniversalView) getView()).getUniversalFailed(msg);
            }

            @Override
            public void onNext(List<GankResEntity.DataBean> dataBeans)
            {
                //这里需要做一个转换处理
                List<UniversalResEntity> universalResEntities = new ArrayList<>();
                for (GankResEntity.DataBean dataBean : dataBeans)
                {
                    universalResEntities.add(dataBean);
                }
                ((UniversalView) getView()).getUniversalSuc(universalResEntities);
            }
        };
        mainModel.getGankData().subscribe(observer);
    }
}

public void getXmNewsData()
{
    if (getView() instanceof UniversalView)
    {
        BaseObserver<List<XmNeswResEntity.DataBean>> observer = new BaseObserver<List<XmNeswResEntity.DataBean>>(rxManager)
        {
            @Override
            public void onErrorMsg(String msg)
            {
                ((UniversalView) getView()).getUniversalFailed(msg);
            }

            @Override
            public void onNext(List<XmNeswResEntity.DataBean> dataBeans)
            {
                //这里需要做一个转换处理
                List<UniversalResEntity> universalResEntities = new ArrayList<>();
                for (XmNeswResEntity.DataBean dataBean : dataBeans)
                {
                    universalResEntities.add(dataBean);
                }
                ((UniversalView) getView()).getUniversalSuc(universalResEntities);
            }
        };
        mainModel.getXmData().subscribe(observer);
    }
}

注意需要做一个转换处理,然后再写一个通用的Activity:

/**
 * <pre>
 *     作者   : 肖坤
 *     时间   : 2018/05/03
 *     描述   : 数据流符合UniveralResEntity的通用页面
 *     版本   : 1.0
 * </pre>
 */
public class UniversalActivity extends AppCompatActivity implements UniversalView
{

    private RecyclerView mUniversalRv;
    private UniversalRvAdapter mAdapter;
    private RxManager rxManager;
    private MainPresenter mainPresenter;

    private static final String KEY_PAGE_TYPE = "PAGE_TYPE";
    //1表示请求gank-Android文章 2表示请求小米新闻
    private int pageType;

    public static void startUniversalActivity(Activity activity, int pageType)
    {
        Bundle bundle = new Bundle();
        bundle.putInt(KEY_PAGE_TYPE, pageType);
        Intent intent = new Intent(activity, UniversalActivity.class);
        intent.putExtras(bundle);
        ActivityCompat.startActivity(activity, intent, null);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState)
    {
        setTheme(R.style.DayTheme);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gank);

        initData();
        initView();
        initRv();
        getHttpData();
    }

    private void initData()
    {
        Bundle bundle = getIntent().getExtras();
        pageType = bundle.getInt(KEY_PAGE_TYPE, 0);
    }

    private void initView()
    {
        mUniversalRv = (RecyclerView) findViewById(R.id.universal_rv);
        rxManager = new RxManager();
        mainPresenter = new MainPresenter(this, rxManager);
    }

    private void initRv()
    {
        final int spacing = getResources().getDimensionPixelSize(R.dimen.dimen_2dp);
        mUniversalRv.addItemDecoration(new OffsetDecoration(spacing));
        LinearLayoutManager manager = new LinearLayoutManager(this);
        //这是通用的Adapter
        mAdapter = new UniversalRvAdapter(R.layout.item_other);
        mUniversalRv.setLayoutManager(manager);
        mUniversalRv.setAdapter(mAdapter);
    }

    private void getHttpData()
    {
        switch (pageType)
        {
            case 1:
                mainPresenter.getGankData();
                break;
            case 2:
                mainPresenter.getXmNewsData();
                break;
            default:

                break;
        }
    }

    @Override
    public void getUniversalSuc(List<UniversalResEntity> entity)
    {
        mAdapter.setNewData(entity);
    }

    @Override
    public void getUniversalFailed(String errorMsg)
    {
        Toast.makeText(UniversalActivity.this, errorMsg, Toast.LENGTH_SHORT).show();
    }
}

这个页面是所有返回的数据流符合UniveralResEntity而且UI界面相同的一个通用Activity,当出现十几个这样的页面时,我们可以只通过pageType来进行区分请求接口,美滋滋啊。差点忘记说了,还有一个通用的Adapter,其实跟之前实体类那个完全一样吧。看到木有,我管你啥字段哦,我统统用接口的text1和text2和text3来表示,不服来战啊。

最后图示如下:

这里写图片描述

岂不是美吱吱!

总结

这里面其实还是用到了接口的扩展性,接口就是用来扩展的,我也不知道这是不是面向接口编程。

这里写图片描述

Github:Demo

怎么没人给我点赞哇,兄dei!

上一篇博客:App实战:动态申请权限以及为所欲为之终极扩展
下一篇博客:App实战:夜间模式实现方法一

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值