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设计模式——装饰者模式
前言:对于设计模式我们有时候在想是否有必要,因为实际开发中我们没有那么多闲工夫去套用这么多设计模式,也没有必要为了模式而模式. 通常这些模式会引入新的抽象层,增加代码的复杂度,但是当我们掌握了这些设计 ...
随机推荐
- 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数
关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...
- C语言字节数组转换为基本数据类型
首先,必须明确基本类型在不同环境下字节的大小 这里用到了unsigned char:1 int:4 double:8 自己主要想用的是将字节数组转换为int型,double型 具体来说,将接收到的 ...
- [译]如何在Web开发中使用Python
[译]如何在Web开发中使用Python 原文:HOWTO Use Python in the Web 摘要 这篇文档展示了Python如何融入到web中.它介绍了几种Python结合web服务器的方 ...
- Spring源码解析三:IOC容器的依赖注入
一般情况下,依赖注入的过程是发生在用户第一次向容器索要Bean是触发的,而触发依赖注入的地方就是BeanFactory的getBean方法. 这里以DefaultListableBeanFactory ...
- Error: Cannot find module 'gulp-clone'问题的解决
安装完gulp环境,并且配置好gulpfile.js,执行静态文件压缩和代码混淆时,出现如下错误: Error: Cannot find module 'gulp-clone' Error: Cann ...
- angular.js之作用域scope'@','=','&'
<!doctype html> <html ng-app='myApp'> <head> </head> <body> <script ...
- USB重定向
第一期中,我们一起简要的看了下传统PC和桌面云下USB重定向和USB设备重定向方式的差异,了解了桌面云下外设兼容性问题来源的根源-USB设备本身驱动不规范/不支持, 或者虚拟机驱动实现上与USB设备对 ...
- JS传值和传引用
之前看<javascript高级程序设计>时就遇到过一个问题,就是函数传参的问题,书上说是按值传递,而非按引用传递.很多人都会认为js里面有两种传值方式,一种是复制,另一种是数组.对象等按 ...
- perl open函数
open(DATA, "<file.txt"); # <只读打开file.txt ,DATA作为句柄 open(DATA, ">file.txt&quo ...
- JSP中列表展示,隔行变色以及S标签的使用
1.java代码 /** * 列表展示,隔行变色以及S标签的使用 * * @return */ public String list() { List<User> list = new A ...