近段时间一直在加班,在赶一个项目,现在项目接近尾声,那么需要对过去一段时间工作内容进行复盘,总结下比较好的解决方案,积累一些经验,我认为的学习方式,是「理论—实践—总结—分享」,这一种很好的沉淀方式。

在之前项目中,有个需求是这样的,要显示书的阅读足迹列表,具体要求是显示最近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中装饰者模式应用的更多相关文章

  1. 设计模式(九)装饰者模式(Decorator Pattern)

    一.引言 在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).A ...

  2. C#设计模式-装饰者模式

    在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).Access ...

  3. 设计模式学习之装饰者模式(Decorator,结构型模式)(16)

    参考地址:http://www.cnblogs.com/zhili/p/DecoratorPattern.html 一.定义:装饰者模式以对客户透明的方式动态地给一个对象附加上更多的责任,装饰者模式相 ...

  4. C#设计模式(9)——装饰者模式(Decorator Pattern)

    一.引言 在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).A ...

  5. Java IO流以及装饰器模式在其上的运用

    流概述 Java中,流是一种有序的字节序列,可以有任意的长度.从应用流向目的地称为输出流,从目的地流向应用称为输入流. Java的流族谱 Java的 java.io 包中囊括了整个流的家族,输出流和输 ...

  6. Head First设计模式之装饰者模式

    一.定义 装饰者模式,英文叫Decorator Pattern,在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对象,也就是装饰来包裹真实的对象. 动态将职责附加到 ...

  7. 结合JDK源码看设计模式——装饰者模式

    定义 在不改变原有对象的基础之上,将功能附加到对象上 适用场景 扩展一个类的功能 动态的给对象增加功能,当功能不需要的时候能够动态删除 详解 在看到定义的时候,可能很多人会想,这不就是继承吗?的确很像 ...

  8. C#设计模式(9)——装饰者模式(Decorator Pattern)(转)

    一.引言 在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).A ...

  9. Head First设计模式——装饰者模式

    前言:对于设计模式我们有时候在想是否有必要,因为实际开发中我们没有那么多闲工夫去套用这么多设计模式,也没有必要为了模式而模式. 通常这些模式会引入新的抽象层,增加代码的复杂度,但是当我们掌握了这些设计 ...

随机推荐

  1. jQuery的拾色器

    代码如下 1.js <link href="css/farbtastic.css" rel="stylesheet" /> <script t ...

  2. js加载XML文件

    // XML文件 <?xml version="1.0" encoding="gb2312"?> <root> <father n ...

  3. 初学jQuery之jQuery事件与动画

    今天我们就谈谈jquery中的事件和简单动画吧,它们毕竟基础是进阶华丽的根本!! 1.事件 1.window事件 ready   准备就绪 2.鼠标事件 方法                      ...

  4. 如何发布第一个WP8.1程序(VisualStudio2015)

    学习WP开发有一段时间了,近一个月开始着手开发程序,并在开发程序中不断地学习(有一定的基础后,边开发程序,边学习是很好的,能练习运用所学的知识,并能在遇到问题后上网上资料不断地学习,很有效果,因为老是 ...

  5. XML入门知识

    什么是XML? 答:指可扩展标记语言(eXtensible Markup Language),被设计用来传输和存储数据:标签没有被预定义.您需要自行定义标签:被设计为具有自我描述性. XML和HTML ...

  6. LAMP部署

    各组件版本:Linux:CentOS 7 1511 64位 (提前下载)Apache:Apache 2Mysql:MariaDB 5.5.52Php 5.4.16 VMware Workstation ...

  7. 极光推送助推视频App,打造最活跃手机新媒体平台

    移动应用能够帮助吸引更多的新用户,增加用户互动和对话.但你得让用户想起你,如何在一部手机上数十个App中脱颖而出,是考验App运营的关键之处.为了打造一个成功的App,开发者需要着眼长远,不应局限于其 ...

  8. Swift 包管理器命令行使用

    1.swift -version //swift 版本查看 2.swift build //swift工程编译 3.swift package generate-xcodeproj //创建Xcode ...

  9. MyBatis一个常见的错误

    最近在建一 个MyBatis项目的时候,觉得配置Spring和 MyBatis 的文件很复杂,所以就把以前的项目重新整理一下配置不改变,只修改ctr层和Mapper  .我把mapper 层和Ctr ...

  10. RxSwift 入坑好多天 - 终于有了一点理解

    一.前言 江湖上都在说现在就要赶紧学 swift 了,即将是 swift 的天下了.在 api 变化不大的情况下,swift 作为一门新的语言,集众家之所长,普通编码确实比 oc 要好用的多了 老早就 ...