1. 程式人生 > >集下拉重新整理、自動載入和側滑選單的RecyclerView基本實現原理

集下拉重新整理、自動載入和側滑選單的RecyclerView基本實現原理

目錄

前言

  現在這個功能的框架也挺多的了。之所以要寫是因為這個框架是自己親手實現的。說起來有點小激動,這是我正經寫出來的第一個框架。對於”不要重複造輪子”這句話,我一直不是太認同,得從不同的維度看。如果從使用上來看,當然沒必要重複造輪子,白白費時費力不划算。但是如果從個人學習的角度來看的話,重複造輪子不但應該去做,而且很有必要。只會使用輪子對個人的成長幫助不大。你得知道它是怎麼工作的,它為什麼能夠這樣工作,然後更進一步的話,看看我還能不能改進它?而學習輪子效果最好的方法,我認為就是自己再造一個輪子。說白了你來山寨一個,如果可以,就改進它!

正文

專案地址

效果

  和常見的側滑以及下拉重新整理效果一樣,見下圖:
側滑選單效果:
側滑效果]![側滑選單

下拉重新整理效果:
下拉重新整理

上拉載入效果:
載入更多

效果圖就是這樣,基本使用在上面原始碼地址中都有,步驟非常簡便。下面主要想說的,是它實現的基本原理。

1.側滑原理

  側滑的主要實現,靠的是一個自定義的佈局容器。專案中類名為:SwipeMenuLayout,繼承FrameLayout.

public class SwipeMenuLayout extends FrameLayout 

  它有兩個成員:

private View contentView;
private
LinearLayout menuView;

  一目瞭然,一個是內容,一個是選單。內容自然就是RecyclerView條目中的佈局內容,選單則是自定義的選單佈局。它作為一個容器,包含了這兩個子佈局。
  重點是,怎麼讓兩個子佈局歸位到自己的初始位置呢?內容佈局鋪滿整個寬度,選單佈局放在螢幕外邊。簡單看看下面的程式碼:

//init()方法中執行下面三句
LayoutParams contentParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
contentView.setLayoutParams(contentParams);
menuView.setLayoutParams(new
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); //重寫onLayout()方法 @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { Log.d(TAG, "contentView.getWidth() = " + contentView.getWidth() + "contentView.getHeight() = " + contentView.getHeight()); super.onLayout(changed, left, top, right, bottom); int contentViewWidth = contentView.getWidth(); int contentViewHeight = contentView.getHeight(); int menuViewWidth = menuView.getWidth(); if (contentView != null && contentViewWidth != 0 && contentViewHeight != 0) { contentView.layout(0, 0, contentView.getWidth(), contentView.getHeight()); if (menuView != null) { menuView.layout(contentViewWidth, 0, contentViewWidth + menuViewWidth, contentViewHeight); } } }

  首先對設定進來的內容佈局和選單佈局設定寬高,內容佈局寬度鋪滿整個螢幕。選單佈局的寬度為包裹內容。然後,決定子控制元件的位置就是在onLayout()方法中進行的,所以重寫onLayout()方法,根據內容佈局和選單佈局的寬和高來執行它們的layout()方法。可以看到,內容佈局的寬是鋪滿整個螢幕的,選單佈局的寬度範圍是contentViewWidth到contentViewWidth+menuViewWidth,也就是從內容佈局的寬度終點位置到這個位置加上自己寬度的位置,就剛好在螢幕外面了。
  初始化位置搞定之後,就要開始處理它的滑動事件了,要把選單側滑出來,重寫onTouchEvent()方法。這裡需要自己來實現smoothScroll()等功能,細節上要處理控制具體可滑動方向,選單自動開啟、自動關閉等問題,具體實現請參考程式碼。整個SwipeMenuLayout也就兩三百行程式碼,並不複雜。
  處理完側滑選單的具體實現之後,就要考慮把它放到RecyclerView裡面去,作為預設的ItemView。當使用者設定他自己的ItemView時,將其作為SwipeMenuLayout的內容佈局,然後加上構造的選單佈局(使用者自定義的),返回SwipeMenuLayout作為新的ItemView,這樣,每一個Item就都具備側滑選單的效果了。
  要做以上的事情,不可避免的,需要重寫Adapter,這裡承擔這個角色的是SwipeMenuAdapter,繼承RecyclerView.Adapter。

public abstract class SwipeMenuAdapter<V extends PtrSwipeMenuRecyclerView.ViewHolder> extends RecyclerView.Adapter 

  細心的同學會發現這裡ViewHolder和預設的不一樣,確實,ViewHolder也重寫了,主要是為了設定選單的點選事件監聽,這裡先不討論它。

 @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        menuView = createMenuView(parent, viewType);
        contentView = createContentView(parent, viewType);
        SwipeMenuLayout swipeMenuLayout = new SwipeMenuLayout(parent.getContext(), contentView, menuView);
        return onCreateThisViewHolder(swipeMenuLayout, viewType);
    }

    /**
     * 建立item內容的view佈局
     *
     * @param parent
     * @param viewType
     * @return
     */
    protected abstract View createContentView(ViewGroup parent, int viewType);

    /**
     * 建立選單view的佈局
     *
     * @return
     */
    protected abstract LinearLayout createMenuView(ViewGroup parent, int viewType);

    /**
     * 建立ViewHolder
     *
     * @param contentView 已經在createContentView()中建立好,然後經過再次包裹了側滑選單佈局的itemview
     * @param viewType
     * @return
     */
    public abstract RecyclerView.ViewHolder onCreateThisViewHolder(ViewGroup contentView, int viewType);

  這裡有三個抽象方法,createMenuView()建立一個選單佈局,由使用者自己實現,createContentView()建立一個內容佈局,同樣由使用者來實現。onCreateThisViewHolder則是替代了原來的onCreateViewHolder()方法,用來返回一個ViewHolder,但是在這裡返回的ViewHolder,其實已經是item被包裹了SwipeMenuLayout的item了,實現了側滑選單的功能。
  到這裡,側滑選單的主幹實現原理就大致說完了。下面看下拉重新整理和自動載入。
  

2.下拉重新整理及自動載入原理

  下拉重新整理效果總體的流程就是:控制touch事件,根據手指滑動動態的改變頭部HeaderView的高度和其內部View的狀態,達到好像控制元件被拉下來觸發重新整理的效果。(當然也有根據手指滑動往下滾動View的實現方法不是這裡用的不去多講)
  以前ListView做下拉重新整理的時候,在頂部會增加一個Header作為下拉重新整理頭,而ListView也已經封裝了setHeader()方法,十分方便。但是RecyclerView沒有,所以實現下拉重新整理的第一個任務就是給RecyclerView增加一個HeaderView作為下拉重新整理頭。
  增加HeaderView,其實就是在RecyclerView的第0個位置放上自己特定的一個View,用來實現下拉重新整理的效果。首先我們封裝一個HeaderView,相當於一個自定義佈局,方便下拉重新整理效果變化的管理(程式碼略,請參考原始碼)。
  要增加HeaderView,又得去重寫Adapter了,好在上面做側滑選單的時候已經重寫了,所以把SwipeMenuAdapter拿出來,繼續新增程式碼。主要是onCreateViewHolder()方法,然後牽涉到getItemCount()和getItemViewType()等方法。由於自動載入更多所新增的FooterView與HeaderView是同樣的原理,所以就一併說吧。先看程式碼:

public class HeaderFooterViewHolder extends RecyclerView.ViewHolder {
        public HeaderFooterViewHolder(View itemView) {
            super(itemView);
        }
    }

    @Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == HeaderType) {
            headerViewHolder = new HeaderFooterViewHolder(new HeaderView(parent.getContext()));
            return headerViewHolder;
        }
        if (viewType == FooterType) {
            footerViewHolder = new HeaderFooterViewHolder(new FooterView(parent.getContext()));
            if(!footerViewEnable) { //不允許上拉載入更多,隱藏FooterView
                FooterView footerView = (FooterView) footerViewHolder.itemView;
                footerView.setNowState(FooterView.STATE.HIND);
            }
            return footerViewHolder;
        }
        ...
        ... 
    }

@Override
    public int getItemCount() {
        //新增Header和Footer的數目
        return getThisItemCount() + 2;
    }

    /**
     * 此方法執行RecyclerView.Adapter中getItemCount()的邏輯
     *
     * @return
     */
    public abstract int getThisItemCount();

    /**
     * 重寫此方法時請注意保留父類方法的邏輯,否則導致header計數混亂,下拉刷新出錯
     * 使用position時注意減1(減去header的位置)
     *
     * @param position
     * @return
     */
    @Override
    public int getItemViewType(int position) {
        if (position == 0)
            return HeaderType;
        if (position == getThisItemCount() + 1)
            return FooterType;
        return super.getItemViewType(position - 1);//減1去掉herder的位置
    }

  首先是getItemCount()方法,加上HeaderView和FooterView的位置,也就是在原有的數目上加2,原有的數目由getThisItemCount()獲取,由使用者自己實現。然後在特定的位置返回特定的型別,position為0時,返回HeaderType,position在最後時,返回FooterType。然後,在onCreateViewHolder()中根據viewType返回特定的ViewHolder型別。這樣,就把HeaderView和FooterView都增加進去了。
  接下來的步驟就是控制touch事件動態設定HeaderView的高度及控制元件來實現下拉重新整理的效果了。當RecyclerView滑動到頂部時,繼續往下拉觸發下拉重新整理。當滑到底部時,自動觸發載入更多。然後設定好相關的介面回撥,就基本完成。這裡面許多細節,一篇文章很難講完了,基本可以另開新篇。涉及很多基本知識和細節邏輯。大家真的願意瞭解的話。原始碼連結在下方,可以作為參考。

結尾