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,開發人員必備技能——單元測試

相關文章
相關標籤/搜索