RecyclerView中装饰者模式应用

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

在以前项目中,有个需求是这样的,要显示书的阅读足迹列表,具体要求是显示最近30天阅读状况,布局是用列表项布局,而后若是有更早的书,则显示更早的阅读状况,布局是用网格布局,如图所示:java

显示效果

要是放在以前的作法,通常都是ListView,再对特殊item样式进行单独处理,后来Android在5.0的时候出了一个RecyclerView组件,简单介绍下RecyclerView,一句话:只管回收与复用View,其余的你能够本身去设置,有着高度的解耦,充分的扩展性。至于用法,你们能够去官网查看文档便可,网上也不少文章介绍如何使用,这里很少说。想讲的重点是关于装饰者模式如何在RecyclerView中应用,以下:android

  • 装饰者模式介绍
  • RecyclerView中应用
  • 小结

装饰者模式介绍

定义:Decorator模式(别名Wrapper),动态将职责附加到对象上,若要扩展功能,装饰者提供了比继承更具弹性的代替方案。设计模式

也就是说动态地给一个对象添加一些额外的职责,好比你能够增长功能,相比继承来讲,有些父类的功能我是不须要的,我可能只用到某部分功能,那么我就能够自由组合,这样就显得灵活点,而不是那么冗余。性能优化

有几个要点:app

  • 多用组合,少用继承。利用继承在设计子类的行为,在编译时静态决定的,并且全部子类都会继承相同的行为,然而,若是用到组合,则能够在运行时动态地进行扩展,对一些对象作一些改变。
  • 类应该对扩展开发,对修改关闭。
  • 装饰者和被装饰对象有相同的超类型。
  • 能够用一个或多个装饰者包装一个对象
  • 装饰者能够在所委托被装饰者的行为以前或以后,加上本身的行为,以达到特定的目的。
  • 对象能够在任什么时候候被装饰,因此能够在运行时动态的,不限量的用你喜欢的装饰者来装饰对象。
  • 装饰模式中使用继承的关键是想达到装饰者和被装饰对象的类型匹配,而不是得到其行为。
  • 装饰者通常对组件的客户是透明的,除非客户程序依赖于组件的具体类型。在实际项目中能够根据须要为装饰者添加新的行为,作到“半透明”装饰者。
  • 适配器模式的用意是改变对象的接口而不必定改变对象的性能,而装饰模式的用意是保持接口并增长对象的职责。

UML图:框架

装饰者模式UML

RecyclerView中应用

咱们既然知道了装饰者和被装饰对象有相同的超类型,在作书的阅读足迹这个页面的时候,整个页面外部是一个RecyclerView,好比这样:ide

<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本身处理,默认是不拦截,若是滑动距离超过所规定距离,咱们就拦截本身处理,设置是可滚动的状态。oop

解决完滑动冲突以后,具体看看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,开发人员必备技能——单元测试

相关文章
相关标签/搜索