碰到的问题
不知道大家在平时有没有碰到这种情况:在项目中有很多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实战:夜间模式实现方法一