RecyclerView中装饰者模式应用
近段时间一直在加班,在赶一个项目,现在项目接近尾声,那么需要对过去一段时间工作内容进行复盘,总结下比较好的解决方案,积累一些经验,我认为的学习方式,是「理论—实践—总结—分享」,这一种很好的沉淀方式。
在之前项目中,有个需求是这样的,要显示书的阅读足迹列表,具体要求是显示最近30天阅读情况,布局是用列表项布局,然后如果有更早的书,则显示更早的阅读情况,布局是用网格布局,如图所示:
要是放在之前的做法,一般都是ListView,再对特殊item样式进行单独处理,后来Android在5.0的时候出了一个RecyclerView组件,简单介绍下RecyclerView,一句话:只管回收与复用View,其他的你可以自己去设置,有着高度的解耦,充分的扩展性。至于用法,大家可以去官网查看文档即可,网上也很多文章介绍如何使用,这里不多说。想讲的重点是关于装饰者模式如何在RecyclerView中应用,如下:
- 装饰者模式介绍
- RecyclerView中应用
- 小结
装饰者模式介绍
定义:Decorator模式(别名Wrapper),动态将职责附加到对象上,若要扩展功能,装饰者提供了比继承更具弹性的代替方案。
也就是说动态地给一个对象添加一些额外的职责,比如你可以增加功能,相比继承来说,有些父类的功能我是不需要的,我可能只用到某部分功能,那么我就可以自由组合,这样就显得灵活点,而不是那么冗余。
有几个要点:
- 多用组合,少用继承。利用继承在设计子类的行为,在编译时静态决定的,而且所有子类都会继承相同的行为,然而,如果用到组合,则可以在运行时动态地进行扩展,对一些对象做一些改变。
- 类应该对扩展开发,对修改关闭。
- 装饰者和被装饰对象有相同的超类型。
- 可以用一个或多个装饰者包装一个对象
- 装饰者可以在所委托被装饰者的行为之前或之后,加上自己的行为,以达到特定的目的。
- 对象可以在任何时候被装饰,所以可以在运行时动态的,不限量的用你喜欢的装饰者来装饰对象。
- 装饰模式中使用继承的关键是想达到装饰者和被装饰对象的类型匹配,而不是获得其行为。
- 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。在实际项目中可以根据需要为装饰者添加新的行为,做到“半透明”装饰者。
- 适配器模式的用意是改变对象的接口而不一定改变对象的性能,而装饰模式的用意是保持接口并增加对象的职责。
UML图:
RecyclerView中应用
我们既然知道了装饰者和被装饰对象有相同的超类型,在做书的阅读足迹这个页面的时候,整个页面外部是一个RecyclerView,比如这样:
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<com.dracom.android.sfreader.widget.recyclerview.FeedRootRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawSelectorOnTop="true"/>
</android.support.v4.widget.SwipeRefreshLayout>
同时每个Item项里面又嵌套一个RecyclerView,但外部只有2个Item项,一个Item项代表最近30天要显示的书的内容,一个Item项是显示更早书的内容。其中因为涉及到RecyclerView嵌套的问题,所以需要做滑动冲突的相关处理。所以这里用到自定义扩展的RecyclerView,具体解决滑动冲突代码如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
final int action = MotionEventCompat.getActionMasked(e);
final int actionIndex = MotionEventCompat.getActionIndex(e);
switch (action) {
case MotionEvent.ACTION_DOWN:
mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
mInitialTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = (int) (e.getY() + 0.5f);
return super.onInterceptTouchEvent(e);
case MotionEventCompat.ACTION_POINTER_DOWN:
mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);
mInitialTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);
mInitialTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);
return super.onInterceptTouchEvent(e);
case MotionEvent.ACTION_MOVE: {
final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
if (index < 0) {
return false;
}
final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
if (getScrollState() != SCROLL_STATE_DRAGGING) {
final int dx = x - mInitialTouchX;
final int dy = y - mInitialTouchY;
final boolean canScrollHorizontally = getLayoutManager().canScrollHorizontally();
final boolean canScrollVertically = getLayoutManager().canScrollVertically();
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop && (Math.abs(dx) >= Math.abs(dy) || canScrollVertically)) {
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop && (Math.abs(dy) >= Math.abs(dx) || canScrollHorizontally)) {
startScroll = true;
}
return startScroll && super.onInterceptTouchEvent(e);
}
return super.onInterceptTouchEvent(e);
}
default:
return super.onInterceptTouchEvent(e);
}
}
按照思路就是内部拦截法,也就是RecyclerView自己处理,默认是不拦截,如果滑动距离超过所规定距离,我们就拦截自己处理,设置是可滚动的状态。
解决完滑动冲突之后,具体看看item项中的布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="4dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="4dp">
<LinearLayout
android:id="@+id/head_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:textColor="@color/black_alpha"
android:textSize="16sp"
android:text="@string/zq_account_read_footprint_recent_month"/>
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginTop="8dp"
android:background="@android:drawable/divider_horizontal_bright"/>
</LinearLayout>
<com.dracom.android.sfreader.widget.recyclerview.BetterRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:layout_marginTop="8dp"
android:orientation="vertical"
android:scrollbars="none"/>
</LinearLayout>
可以看到,每个Item项目一个头部,一个RecyclerView。然后是Adapter的适配,这里就是常用的RecyclerView Adapter的方式,要继承RecyclerView.Adapter<RecyclerView.ViewHolder>方法,同时要实现onCreateViewHolder、onBindViewHolder、getItemCount、getItemViewType方法,具体代码如下:
public class ReadFootPrintAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final static int RECENT_MONTH = 1002; //最近三十天的Item
private final static int MORE_EARLY = 1003; //更早的Item
private Context mContext;
private List<ReadBookColumnInfo> mColumns;
private RecentReadAdapter mRecentReadAdapter;
private MoreEarlyAdapter mMoreEarlyAdapter;
public ReadFootPrintAdapter(Context context){
this.mContext = context;
mColumns = new ArrayList<ReadBookColumnInfo>();
mRecentReadAdapter = new RecentReadAdapter(context);
mMoreEarlyAdapter = new MoreEarlyAdapter(context);
}
public void setLoadEnable(boolean loadEnable) {
mIsLoadEnable = loadEnable;
}
public void setColumns(List<ReadBookColumnInfo> columns) {
this.mColumns = columns;
notifyDataSetChanged();
}
public void setRecentReadListener(OnOpenBookListener onOpenBookListener){
mRecentReadAdapter.setOnListener(onOpenBookListener);
}
public void setMoreEarlyListener(OnOpenBookListener onOpenBookListener){
mMoreEarlyAdapter.setOnListener(onOpenBookListener);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType == RECENT_MONTH){
View view = LayoutInflater.from(mContext).inflate(R.layout.recycler_read_footprint_recent_month,parent,false);
return new ColumnViewHolder1(view);
} if(viewType == MORE_EARLY){
View view = LayoutInflater.from(mContext).inflate(R.layout.recycler_read_footprint_more_early,parent,false);
return new ColumnViewHolder2(view);
}
else{
return null;
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if(holder instanceof ColumnViewHolder){
ColumnViewHolder columnViewHolder = (ColumnViewHolder) holder;
ReadBookColumnInfo readBookColumnInfo = mColumns.get(position);
if(readBookColumnInfo.getReadBookInfos().size() > 0){
columnViewHolder.headLayout.setVisibility(View.VISIBLE);
}
else{
columnViewHolder.headLayout.setVisibility(View.GONE);
}
columnViewHolder.loadData(readBookColumnInfo);
}
}
@Override
public int getItemCount() {
return mColumns.size();
}
@Override
public int getItemViewType(int position) {
if (position == 0 )
return RECENT_MONTH;
else
return MORE_EARLY;
}
private abstract class ColumnViewHolder extends RecyclerView.ViewHolder {
View headLayout;
RecyclerView recyclerView;
public ColumnViewHolder(View itemView) {
super(itemView);
headLayout = itemView.findViewById(R.id.head_layout);
recyclerView = (RecyclerView) itemView.findViewById(R.id.recycler_view);
}
abstract void loadData(ReadBookColumnInfo readBookColumnInfo);
}
private class ColumnViewHolder1 extends ColumnViewHolder {
public ColumnViewHolder1(View itemView) {
super(itemView);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mContext);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(mRecentReadAdapter);
}
@Override
void loadData(ReadBookColumnInfo readBookColumnInfo) {
mRecentReadAdapter.setData(readBookColumnInfo.getReadBookInfos());
}
}
private class ColumnViewHolder2 extends ColumnViewHolder {
public ColumnViewHolder2(View itemView) {
super(itemView);
GridLayoutManager gridLayoutManager = new GridLayoutManager(mContext,3);
gridLayoutManager.setOrientation(GridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(gridLayoutManager);
recyclerView.setAdapter(mMoreEarlyAdapter);
}
@Override
void loadData(ReadBookColumnInfo readBookColumnInfo) {
mMoreEarlyAdapter.setData(readBookColumnInfo.getReadBookInfos());
}
}
}
本来到这里,基本功能是完成了,可后来产品说要加个上下刷新,加载更多的操作。需求是随时可变的, 我们能不变的就是修改的心,那应该怎么做合适呢,是再增加itemType类型,加个加载更多的item项,那样修改的点会更多,此时想到了装饰者模式,是不是可以有个装饰类对这个adapter类进行组合呢,这样不需要修改原来的代码,只要扩展出去,况且我们知道都需要继承RecyclerView.Adapter,那么就可以把ReadFootPrintAdapter当做一个内部成员设置进入。我们来看下装饰者类:
public class ReadFootPrintAdapterWrapper extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final static int LOADMORE = 1001;
private final static int NORMAL = 1002;
private RecyclerView.Adapter internalAdapter;
private View mFooterView;
public ReadFootPrintAdapterWrapper(RecyclerView.Adapter adapter){
this.internalAdapter = adapter;
this.mFooterView = null;
}
public void addFooterView(View footView) {
mFooterView = footView;
}
public void notifyDataChanged() {
internalAdapter.notifyDataSetChanged();
notifyDataSetChanged();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType == LOADMORE){
return new LoadMoreViewHolder(mFooterView);
}
return internalAdapter.createViewHolder(parent,viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if(holder instanceof LoadMoreViewHolder){
return;
}else{
internalAdapter.onBindViewHolder(holder,position);
}
}
@Override
public int getItemCount() {
int count = internalAdapter.getItemCount();
if (mFooterView != null && count != 0) count++;
return count;
}
@Override
public int getItemViewType(int position) {
if(mFooterView != null && getItemCount() - 1 == position)
return LOADMORE;
return NORMAL;
}
public class LoadMoreViewHolder extends RecyclerView.ViewHolder {
public LoadMoreViewHolder(View itemView) {
super(itemView);
}
}
}
这个Wrapper类就是装饰类,里面包含了一个RecyclerView.Adapter类型的成员,一个底部View,到时候在外部调用的时候,只需要传递一个RecyclerView.Adapter类型的参数进去即可,这样就形成了组合的关系。具体使用如下:
mFooterView = LayoutInflater.from(mContext).inflate(R.layout.refresh_loadmore_layout, mRecyclerView, false);
mReadFootPrintAdapterWrapper = new ReadFootPrintAdapterWrapper(mReadFootPrintAdapter);
mReadFootPrintAdapterWrapper.addFooterView(mFooterView);
mRecyclerView.setAdapter(mReadFootPrintAdapterWrapper);
这样即达到需求要求,又能对原来已有的代码不进行修改,只进行扩展,何乐而不为。
小结
这虽然是工作中一个应用点,但我想在开发过程中还有很多应用点,用上设计模式。日常开发中基本都强调设计模式的重要性,或许你对23种设计模式都很熟悉,都了解到它们各自的定义,可是等真正应用了,却发现没有踪迹可寻,写代码也是按照以前老的思路去做,那样就变成了知道是知道,却不会用的尴尬局面。如何突破呢,我觉得事后复盘和重构很有必要,就是利用项目尾声阶段,空的时候去review下自己写过的代码,反思是否有更简洁的写法,还有可以参考优秀代码,它们是怎么写,这样给自己找找灵感,再去结合自己已有的知识存储,说不定就能走上理论和实践相结合道路上。
阅读扩展
源于对掌握的Android开发基础点进行整理,罗列下已经总结的文章,从中可以看到技术积累的过程。
1,Android系统简介
2,ProGuard代码混淆
3,讲讲Handler+Looper+MessageQueue关系
4,Android图片加载库理解
5,谈谈Android运行时权限理解
6,EventBus初理解
7,Android 常见工具类
8,对于Fragment的一些理解
9,Android 四大组件之 " Activity "
10,Android 四大组件之" Service "
11,Android 四大组件之“ BroadcastReceiver "
12,Android 四大组件之" ContentProvider "
13,讲讲 Android 事件拦截机制
14,Android 动画的理解
15,Android 生命周期和启动模式
16,Android IPC 机制
17,View 的事件体系
18,View 的工作原理
19,理解 Window 和 WindowManager
20,Activity 启动过程分析
21,Service 启动过程分析
22,Android 性能优化
23,Android 消息机制
24,Android Bitmap相关
25,Android 线程和线程池
26,Android 中的 Drawable 和动画
27,RecylerView 中的装饰者模式
28,Android 触摸事件机制
29,Android 事件机制应用
30,Cordova 框架的一些理解
31,有关 Android 插件化思考
32,开发人员必备技能——单元测试
RecyclerView中装饰者模式应用的更多相关文章
- 设计模式(九)装饰者模式(Decorator Pattern)
一.引言 在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).A ...
- C#设计模式-装饰者模式
在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).Access ...
- 设计模式学习之装饰者模式(Decorator,结构型模式)(16)
参考地址:http://www.cnblogs.com/zhili/p/DecoratorPattern.html 一.定义:装饰者模式以对客户透明的方式动态地给一个对象附加上更多的责任,装饰者模式相 ...
- C#设计模式(9)——装饰者模式(Decorator Pattern)
一.引言 在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).A ...
- Java IO流以及装饰器模式在其上的运用
流概述 Java中,流是一种有序的字节序列,可以有任意的长度.从应用流向目的地称为输出流,从目的地流向应用称为输入流. Java的流族谱 Java的 java.io 包中囊括了整个流的家族,输出流和输 ...
- Head First设计模式之装饰者模式
一.定义 装饰者模式,英文叫Decorator Pattern,在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对象,也就是装饰来包裹真实的对象. 动态将职责附加到 ...
- 结合JDK源码看设计模式——装饰者模式
定义 在不改变原有对象的基础之上,将功能附加到对象上 适用场景 扩展一个类的功能 动态的给对象增加功能,当功能不需要的时候能够动态删除 详解 在看到定义的时候,可能很多人会想,这不就是继承吗?的确很像 ...
- C#设计模式(9)——装饰者模式(Decorator Pattern)(转)
一.引言 在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).A ...
- Head First设计模式——装饰者模式
前言:对于设计模式我们有时候在想是否有必要,因为实际开发中我们没有那么多闲工夫去套用这么多设计模式,也没有必要为了模式而模式. 通常这些模式会引入新的抽象层,增加代码的复杂度,但是当我们掌握了这些设计 ...
随机推荐
- Pycharm安装及遇到的问题
趁寒假想自学一下python语言,有人推荐eclipse+pydev但是因为java编程用了eclipse,不太想一个IDE用于多种语言开发(个人喜好),于是就下载了Pycharm,之间安装了也遇到了 ...
- request和response对象的具体介绍
Request 对象 - request 对象表示 HTTP 请求,包含了请求查询字符串,参数,内容,HTTP 头部等属性.常见属性有: req.app:当callback为外部文件时,用req ...
- use vue vuex vue-router, not use webpack
vue,vuex,vue-router放在一起能做什么?不用webpack之类的打包工具使用他们是否可行?各位道友在初学vue时是否有这样的困惑.因为现代构建前端项目的一般模式是: 安装webapck ...
- .CN根域名被攻击至瘫痪,谁之过?
2013-10-08 09:19 佚名 新浪科技 2013年8月25日凌晨,.CN域名凌晨出现大范围解析故障,经分析.CN的根域授权DNS全线故障,导致大面积.CN域名无法解析.事故造成大量以.cn和 ...
- 获取CPU和内存呢信息
#include <stdio.h> #include <stdlib.h> #include <winsock.h> #pragma comment(lib, & ...
- 【Eclipse】web项目部署新手篇
本文属于新手篇章,记录了Eclipse中部署web项目的步骤 1 . 添加Tomcat服务器 右键项目属性-->Run as-->Run on server弹出以下界面 点击finish之 ...
- 每天一个Linux命令(06)--rmdir命令
终于忙完了公司的事,可以安静的充充电了. 今天学习一下Linux中命令:rmdir 命令,rmdir是常用的命令,该命令的功能是删除空目录,一个目录被删除之前必须是空的.(注意,rm -r dir 命 ...
- 《类型编程晋级——shapeless类库使用指南》前言及第一章翻译
从年初开始进行此项工作,我和合作伙伴包亮付出了大量而艰辛的劳动,现基本翻译完毕,有出版意向,如果有意向欢迎联系,不甚感激!也欢迎各位博友对此翻译提出意见建议以及指导如何出版,在此谢过! 前言 时间回到 ...
- Java XML DOM解析(xPath)
(一) XML概念 在电子计算机中,标记指计算机所能理解的信息符号,通过此种标记,计算机之间可以处理包含各种的信息比如文章等.它可以用来标记数据.定义数据类型,是一种允许用户对自己的标记语言进行定义的 ...
- mysql数据库开始——增删改
注释语法:#注释语 一.T-SQL语句注意:1.语句写完后用“分号:”代表这一句结束2.列结束用逗号,最后一列写完不用写逗号3.符号一定是英文的 关键字:主键:primary key外键:foreig ...