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设计模式——装饰者模式
前言:对于设计模式我们有时候在想是否有必要,因为实际开发中我们没有那么多闲工夫去套用这么多设计模式,也没有必要为了模式而模式. 通常这些模式会引入新的抽象层,增加代码的复杂度,但是当我们掌握了这些设计 ...
随机推荐
- C语言 一维数组叠加为二维数组样例
这里参看memcpy的用法,将一个一维整型数组不停的叠加为二维数组 使用宏定义来控制二维数组的行列 代码如下: #include <stdio.h> #include <stdlib ...
- php小测试,难点与分享
B/S(网页程序) 网页结构,依托游览器 C/S(客户端程序) 单引号和双引号包含字符串的区别: 双引号里面可以解析变量,比如: $a=555; echo "你好{$a}"; 输出 ...
- JAVA函数的参数传递
JAVA开发过程中写函数都是不可避免的于是乎参数传递经常会困扰我们,特别是有C/C++功底的童鞋总会纠结于"java到底是值传递还是引用传递?" 先来一段代码(和程序员交流最好的果 ...
- 2017年2月16日-----------乱码新手自学.net 之MVC模型
第二篇博文,最近学习的内容还是回到了正题:ASP.NET MVC5之上.虽然EF学了个一知半解,但是用这点知识,看MVC5的MODEL部分应该还是够了.尽管周末还要恶补一下EF才行. (一)MVC简述 ...
- 解决CSS中float:left后需要clear:both清空
现在,大部分的横排导航都是通过 ul -> li *n -> a 来实现的.具我所知,要达到这种效果,有几种方法可以实现. 1.传统处理方式: li {float:left;}/*这样,对 ...
- devexpress表格控件gridcontrol实现纵向标头
1.devexpress控件gridcontrol中的标头默认是横向的,如果要实现纵向标头应该怎么做呢.通过官网的资料整理了一个简单的案例,给大家分享一下.运行效果图如下: 2.数据绑定代码如下: D ...
- Web前端浏览器兼容问题
所谓的浏览器兼容性问题,是指因为不同的浏览器对同一段代码有不同的解析,造成页面显示效果不统一的情况.在大多数情况下,我们的需求是,无论用户用什么浏览器来查看我们的网站或者登陆我们的系统,都应该是统一的 ...
- c++ 调用dl里的导出类
来源:http://blog.csdn.net/yysdsyl/article/details/2626033 动态dll的类导出:CPPDll2->test.h #pragma once // ...
- 规范 : loading display & ui-view loading
angular 没有compile 完成的接口,最像的接口是$viewContentLoaded(router ui),但是一开始会开始跑,因为有ui-view 暂时解决方法是在body的loadin ...
- H5缓存-Manifest
在app中更新h5页面一直有缓存问题.默认什么都不做的情况下,app有一定的空间缓存页面.一开始更新之后会马上加载,等到app缓存空间上来之后更新就无法下载了.安卓能够清理缓存空间,ios就只能卸载重 ...