RecyclerView使用進階

目前的項目中,基本已經(jīng)使用 RecyclerView 全面替換了ListView,GridView. 使用RecyclerView確實更加靈活,功能也更加強大. RecyclerView的基本套路應(yīng)該都很熟悉了,這里整理一下一些相對進階一點的知識點,方便隨時復(fù)習(xí).

分割線

雖然和ListView比較, RecyclerView 設(shè)置分割線麻煩了很多, 不過也更自由了,可以實現(xiàn)更多的效果.

RecyclerView 默認是沒有分割線的,需要通過下面這個方法添加

  public void addItemDecoration(ItemDecoration decor) {    
        addItemDecoration(decor, -1);
  }

那么 ItemDecoration 又是什么東西? ItemDecoration是 RecyclerView 的一個內(nèi)部抽象類,很明顯,這個東西是給我們實現(xiàn)的. 當我們實現(xiàn) ItemDecoration 的時候,只需要關(guān)注 3 個方法,說起來麻煩,直接看代碼和注釋.

  public class ItemDivider extends RecyclerView.ItemDecoration {   
      // 構(gòu)造方法,可以在這里做一些初始化,比如指定畫筆顏色什么的
      public ItemDivider() {       
      }    

      /**     
       * 指定item之間的間距(就是指定分割線的寬度)   回調(diào)順序 1     
       * @param outRect Rect to receive the output.      
       * @param view    The child view to decorate     
       * @param parent  RecyclerView this ItemDecoration is decorating     
       * @param state   The current state of RecyclerView.     
       */    
       @Override    
       public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {        
         super.getItemOffsets(outRect, view, parent, state);        
       }    

       /**     
        * 在item 繪制之前調(diào)用(就是繪制在 item 的底層)  回調(diào)順序 2     
        * 一般分割線在這里繪制     
        * 看到canvas,對自定義控件有一定了解的話,就能想到為什么說給RecyclerView設(shè)置分割線更靈活了
        * @param c      Canvas to draw into     
        * @param parent RecyclerView this ItemDecoration is drawing into     
        * @param state  The current state of RecyclerView     
        */    
        @Override    
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {        
            super.onDraw(c, parent, state);             
        }    

        /**     
         * 在item 繪制之后調(diào)用(就是繪制在 item 的上層)  回調(diào)順序 3     
         * 也可以在這里繪制分割線,和上面的方法 二選一     
         * @param c      Canvas to draw into     
         * @param parent RecyclerView this ItemDecoration is drawing into     
         * @param state  The current state of RecyclerView     
         */    
         @Override   
         public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {        
            super.onDrawOver(c, parent, state);    
         }
     }
  • getItemOffsets 指定item 之間的間距(默認為0),將來就是在這個間距內(nèi)繪制分割線
  • onDraw 在繪制 item之前執(zhí)行,也就是說,在這里繪制的圖形可能會被item遮蓋(所以需要指定item之間的間距)
  • onDrawOver 在繪制item之后執(zhí)行,在這里繪制的圖形,可能會遮住item(說以如果要在這里繪制分割線的話,也要找準位置)

PS:在 RecyclerView 25.0.0中,終于有了官方實現(xiàn)的分割線-DividerItemDecoration,可惜只支持 LinearLayoutManager ,感興趣的可以試試.

下面是我自己的實現(xiàn),適配 LinearLayoutManager 和 GridLayoutManager

  public class ItemDivider extends RecyclerView.ItemDecoration {   

      private int dividerWith = 1;
      private Paint paint;
      private RecyclerView.LayoutManager layoutManager;

      // 構(gòu)造方法,可以在這里做一些初始化,比如指定畫筆顏色什么的
      public ItemDivider() {  
          initPaint();
          paint.setColor(0xffff0000);     
      }   

      private void initPaint() {    
          if (paint == null) {        
             paint = new Paint(Paint.ANTI_ALIAS_FLAG);        
             paint.setStyle(Paint.Style.FILL);    
          }
      }

      public ItemDivider setDividerWith(int dividerWith) {    
         this.dividerWith = dividerWith;   
         return this;

      }

      public ItemDivider setDividerColor(int color) {    
          initPaint();    
          paint.setColor(color);    
          return this;
       } 

      /**     
       * 指定item之間的間距(就是指定分割線的寬度)   回調(diào)順序 1     
       * @param outRect Rect to receive the output.      
       * @param view    The child view to decorate     
       * @param parent  RecyclerView this ItemDecoration is decorating     
       * @param state   The current state of RecyclerView.     
       */    
       @Override    
       public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {        
           super.getItemOffsets(outRect, view, parent, state);    
            if (layoutManager == null) {    
               layoutManager = parent.getLayoutManager();
            }
            // 適用 LinearLayoutManager 和 GridLayoutManager
            if (layoutManager instanceof LinearLayoutManager) {    
               int orientation = ((LinearLayoutManager) layoutManager).getOrientation();    
               if (orientation == LinearLayoutManager.VERTICAL) {        
                   // 水平分割線將繪制在item底部        
                   outRect.bottom = dividerWith;    
               } else if (orientation == LinearLayoutManager.HORIZONTAL) {        
                   // 垂直分割線將繪制在item右側(cè)        
                   outRect.right = dividerWith;   
               }    
               if (layoutManager instanceof GridLayoutManager) {        
                   GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams) view.getLayoutParams();        
                   // 如果是 GridLayoutManager 則需要繪制另一個方向上的分割線       
                   if (orientation == LinearLayoutManager.VERTICAL && lp != null && lp.getSpanIndex() > 0) {            
                      // 如果列表是垂直方向,則最左邊的一列略過            
                      outRect.left = dividerWith;        
                   } else if (orientation == LinearLayoutManager.HORIZONTAL && lp != null && lp.getSpanIndex() > 0) {            
                      // 如果列表是水平方向,則最上邊的一列略過            
                      outRect.top = dividerWith;        
                   }    
               }
           }  
       }    

       /**     
        * 在item 繪制之前調(diào)用(就是繪制在 item 的底層)  回調(diào)順序 2     
        * 一般分割線在這里繪制     
        * 看到canvas,對自定義控件有一定了解的話,就能想到為什么說給RecyclerView設(shè)置分割線更靈活了
        * @param c      Canvas to draw into     
        * @param parent RecyclerView this ItemDecoration is drawing into     
        * @param state  The current state of RecyclerView     
        */    
        @Override    
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {        
            super.onDraw(c, parent, state);  
            // 這個值是為了補償橫豎方向上分割線交叉處間隙
            int offSet = (int) Math.ceil(dividerWith * 1f / 2);
            for (int i = 0; i < parent.getChildCount(); i++) {    
                View child = parent.getChildAt(i);    
                RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();   
                int left1 = child.getRight() + params.rightMargin;
                int right1 = left1 + dividerWith;
                int top1 = child.getTop() - offSet - params.topMargin;
                int bottom1 = child.getBottom() + offSet + params.bottomMargin;
                //繪制分割線(矩形)
                c.drawRect(left1, top1, right1, bottom1, paint);
                int left2 = child.getLeft() - offSet - params.leftMargin;
                int right2 = child.getRight() + offSet + params.rightMargin;
                int top2 = child.getBottom() + params.bottomMargin;
                int bottom2 = top2 + dividerWith;
                //繪制分割線(矩形)
                c.drawRect(left2, top2, right2, bottom2, paint);
             }         
        }    

        /**     
         * 在item 繪制之后調(diào)用(就是繪制在 item 的上層)  回調(diào)順序 3     
         * 也可以在這里繪制分割線,和上面的方法 二選一     
         * @param c      Canvas to draw into     
         * @param parent RecyclerView this ItemDecoration is drawing into     
         * @param state  The current state of RecyclerView     
         */    
         @Override   
         public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {        
            super.onDrawOver(c, parent, state);    
         }
     }

使用方式

recyclerView.addItemDecoration(new ItemDivider().setDividerWith(2).setDividerColor(Color.BLUE));

看看效果


LinearLayoutManager
GridLayoutManager

掌握了分割線的原理,還可以做很多有意思的事.比如像列表分欄,在IOS中很容易做到讓當前欄目懸停的效果. 而Android中的常規(guī)做法,就是布局嵌套,在屏幕上面單獨方一個文本,然后監(jiān)聽列表的滾動.....太麻煩了. 其實借助分割線的原理,可以更簡單實現(xiàn)這個效果.

基于組件化的思想,可以將這個功能封裝為一個單獨的控件

 public class StickyRecyclerView extends RecyclerView {    

     private int lineHeight,titleHeight;    
     private int lineColor,titleColor,titleTextColor;    

     public StickyRecyclerView(Context context) {        
         this(context,null);    
     }    

     public StickyRecyclerView(Context context, @Nullable AttributeSet attrs) {       
         this(context, attrs,0);    
     }   

     public StickyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {        
          super(context, attrs, defStyle);        
          TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.StickyRecyclerView);        
          // 分割線的高度       
          lineHeight = array.getDimensionPixelOffset(R.styleable.StickyRecyclerView_dividerHeight,1);       
           // 分欄的高度       
          titleHeight = array.getDimensionPixelOffset(R.styleable.StickyRecyclerView_titleHeight,dip2px(context,35));       
           // 分割線顏色        
          lineColor = array.getColor(R.styleable.StickyRecyclerView_dividerColor,Color.LTGRAY);            
          // 分欄背景色        
          titleColor = array.getColor(R.styleable.StickyRecyclerView_titleColor,Color.LTGRAY);        
          // 分欄文字顏色       
          titleTextColor = array.getColor(R.styleable.StickyRecyclerView_titleTextColor,Color.BLUE);        
          array.recycle();        
          // 不用說,肯定是線性布局了,默認就實現(xiàn)        
          setLayoutManager(new LinearLayoutManager(context));    
       }    

       @Deprecated    
       @Override    
       public void setAdapter(Adapter adapter) {        
           super.setAdapter(adapter);   
       }    

       // 讓 adapter 必須繼承 StickyAdapter    
       public void setAdapter(@NonNull StickyAdapter stickyAdapter){        
          addItemDecoration(new StickyDivider(stickyAdapter));        
          super.setAdapter(stickyAdapter);    
        }    

        /**     
         * 自定義分割線,通過分割線繪制title    
         */    
         private class StickyDivider extends ItemDecoration{        
         private StickyAdapter adapter;        
         private Paint paint;        

         StickyDivider(@NonNull StickyAdapter adapter) {            
              super();            
              this.adapter = adapter;            
              paint = new Paint(Paint.ANTI_ALIAS_FLAG);            
              paint.setStyle(Paint.Style.FILL);            
              paint.setTextSize(titleHeight * 0.5f);        
         }        

         /**        
          * 計算 item間間隙(是普通分割線 ,還是title)        
          */        
          @Override        
          public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {            
               super.getItemOffsets(outRect, view, parent, state);            
               if(!adapter.needTitle(((LayoutParams) view.getLayoutParams()).getViewLayoutPosition())){                
                    outRect.top = lineHeight;           
               }else {                
                    outRect.top = titleHeight;           
               }        
          }       

          /**         
           * 底層繪制,繪制分欄title        
           */        
           @Override        
           public void onDraw(Canvas c, RecyclerView parent, State state) {            
                 super.onDraw(c, parent, state);            
                 int left = parent.getPaddingLeft();            
                 int right = parent.getMeasuredWidth() - parent.getPaddingRight();            
                 final int childCount = parent.getChildCount();            
                 for (int i = 0; i < childCount; i++) {                
                     final View child = parent.getChildAt(i);                
                     int position = ((LayoutParams) child.getLayoutParams()).getViewLayoutPosition();                
                     int bottom = child.getTop() - ((LayoutParams) child.getLayoutParams()).topMargin;                
                     if(!adapter.needTitle(position)){                    
                          // 畫分割線                    
                          int top = bottom - lineHeight;                    
                          paint.setColor(lineColor);                    
                          c.drawRect(left, top, right, bottom, paint);                
                     }else {                    
                           //畫TITLE                    
                           int top = bottom - titleHeight;                    
                           paint.setColor(titleColor);                    
                           c.drawRect(left, top, right, bottom, paint);                    
                           drawText(c,adapter.getItemViewTitle(position),left + titleHeight * 0.25f,bottom - titleHeight * 0.25f);                
                     }            
                 }        
            }        

            /**         
             * 上層繪制,繪制頂部懸停title        
             */        
             @Override        
             public void onDrawOver(Canvas c, RecyclerView parent, State state) {            
                     super.onDrawOver(c, parent, state);            
                     // 懸停title            
                     int left = parent.getPaddingLeft();           
                     int right = parent.getMeasuredWidth() - parent.getPaddingRight();            
                     int top = parent.getPaddingTop();           
                     int bottom = top + titleHeight;            
                     paint.setColor(titleColor);            
                     c.drawRect(left,top,right,bottom,paint);           
                     int pos = ((LinearLayoutManager)(parent.getLayoutManager())).findFirstVisibleItemPosition();            
                     drawText(c,adapter.getItemViewTitle(pos),left + titleHeight * 0.25f,bottom - titleHeight * 0.25f);        
             }        

             void drawText(Canvas c, String itemViewTitle, float x, float y){                                        
                  if(!TextUtils.isEmpty(itemViewTitle)){                
                      paint.setColor(titleTextColor);                
                      //paint.getTextBounds(itemViewTitle, 0, itemViewTitle.length(), mBounds);                
                      c.drawText(itemViewTitle, x,y, paint);            
                   }       
             }    
         } 

         public static abstract class StickyAdapter extends Adapter{        

              // 獲取當前 item 的標題        
              public abstract String getItemViewTitle(int position);        
              // 如果標題和前面的item的標題一樣,就不需要繪制        
              boolean needTitle(int position){            
                  return position > -1 && (position == 0 || !getItemViewTitle(position).equals(getItemViewTitle(position - 1)));        
              }    
         }    

         public int dip2px(Context context, float dpValue) {       
              final float scale = context.getResources().getDisplayMetrics().density;        
              return (int) (dpValue * scale + 0.5f);    
         }
     }

大致流程就是通過底層分割線繪制各個分欄,通過頂層分割線繪制頂部懸停的那一欄,具體可以看下注釋.
使用方式和普通RecyclerView 差不多:

stickyRecyclerView.setAdapter(myAdapter);

//關(guān)鍵一:繼承關(guān)系
private class MyAdapter extends StickyRecyclerView.StickyAdapter {
    .....

   //關(guān)鍵二:重寫該方法,返回當前item的標題
  @Override
  public String getItemViewTitle(int position) {    
     return String.valueOf(datas.get(position).shuruma.charAt(0));
  }
}

其中,分欄背景色,高度,文字顏色,以及分割線顏色和高度都是可以通過自定義屬性設(shè)置的.

不規(guī)則布局

網(wǎng)格布局很常見,但是不規(guī)則的網(wǎng)格布局也不少見.比如要實現(xiàn)下面這個效果

上面是網(wǎng)格,下面又變成列表,在以前的做法可能是給ListView添加一個頭部,頭部里面放GridView,甚至是ScrollView嵌套等等.做過的同學(xué)肯定知道有多少坑在里面. 而使用 RecyclerView ,可以做很大程度的簡化,并且很容易就能實現(xiàn)更復(fù)雜的布局.

RecyclerView 可以通過 GridLayoutManager 實現(xiàn)網(wǎng)格布局.而要實現(xiàn)上面的效果,關(guān)鍵就在 GridLayoutManager上, GridLayoutManager 可以設(shè)置網(wǎng)格的列數(shù),而通過下面的方法,可以指定每一個item占據(jù)的列數(shù).

 gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {    
    @Override    
    public int getSpanSize(int position) {    
       // 這里的返回值,表示下標為position的item 占據(jù)多少列
        return 1;       
    }    
 });

通過下面這個例子看起來更加直觀:

gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {    
    @Override    
    public int getSpanSize(int position) {  
       //這里只是一個例子,實際中要根據(jù)需求來設(shè)置  
       if(position % 5 == 0){    
          return 4;
       }else if(position % 5 == 1){    
          return 3;
       }else if(position % 5 == 2){    
          return 1;
       }else{    
          return 2;
       }     
    }    
 });
不規(guī)則布局

關(guān)于不規(guī)則布局的內(nèi)容不多,這里再補充一個例子. RecyclerView分頁加載, Google官方以及一些第三方的下拉刷新控件都不支持分頁功能,因為分頁功能應(yīng)該讓列表自己去實現(xiàn). 而目前的列表基本都可以使用RecyclerView完成,所以如果能做個統(tǒng)一封裝就方便多了(這里就和 SwipeRefreshLayout封裝在一起了,順便解決 SwipeRefreshLayout 的坑).

public class SuperRefreshLayout extends SwipeRefreshLayout {    

    private static OnRefreshHandler onRefreshHandler;    
    private static boolean isRefresh = false;    
    private Adapter adapter;    
    private int mTouchSlop;    
    private float mPrevX;   

    public SuperRefreshLayout(Context context) {        
        this(context, null);    
    }    

    public SuperRefreshLayout(Context context, AttributeSet attrs) {        
        super(context, attrs);        
        setColorSchemeColors(0xff3b93eb);        
        setProgressBackgroundColorSchemeColor(0xffffffff);        
        float scale = context.getResources().getDisplayMetrics().density;        
        setProgressViewEndTarget(true, (int) (64 * scale + 0.5f));        
        //refreshLayout.setProgressViewOffset(false,dip2px(this,-40),dip2px(this,64));        
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();   
     }   

     /**     
      * 監(jiān)聽器    
      */    
      public void setOnRefreshHandler(OnRefreshHandler handler) {        
         onRefreshHandler = handler;        
         super.setOnRefreshListener(new OnRefreshCallBack());    
      }    

      /**     
       * 自動刷新,原生不支持,通過反射修改字段屬性     
       */    
       public void autoRefresh() {        
           try {            
               setRefreshing(true);            
               Field field = SwipeRefreshLayout.class.getDeclaredField("mNotify");            
               field.setAccessible(true);            
               field.set(this, true);        
           } catch (Exception e) {            
               if(onRefreshHandler != null){                
                  onRefreshHandler.refresh();            
               }        
           }    
        }    

        @Override    
        public void setRefreshing(boolean refreshing) {        
            super.setRefreshing(refreshing);        
            isRefresh = isRefreshing();    
        }    
 
        /**     
         * 加載完畢     
         * @param hasMore 是否還有下一頁     
         */    
         public void loadComplete(boolean hasMore){        
             if(adapter == null){            
                 throw new RuntimeException("must call method setAdapter to bind data");        
             }        
             adapter.setState(hasMore ? Adapter.STATE_MORE : Adapter.STATE_END);    
          }    

          /**     
           * 加載出錯     
           */    
           public void loadError(){        
              if(adapter == null){            
                  throw new RuntimeException("must call method setAdapter to bind data");       
              }        
              adapter.setState(Adapter.STATE_ERROR);    
           }    

           /**    
            * 只支持 RecyclerView 加載更多,且需要通過此方法設(shè)置適配器     
            */    
            public void setAdapter(@NonNull RecyclerView recyclerView,@NonNull SuperRefreshLayout.Adapter mAdapter) {        
                adapter = mAdapter;        
                recyclerView.setAdapter(adapter);        
                recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {            
                   @Override            
                   public void onScrollStateChanged(RecyclerView recyclerView, int newState) {                
                       super.onScrollStateChanged(recyclerView, newState);                
                       if (onRefreshHandler != null 
                               && !isRefreshing()                        
                               && (adapter.getState() == Adapter.STATE_MORE || adapter.getState() == Adapter.STATE_ERROR)                        
                               && newState == RecyclerView.SCROLL_STATE_IDLE                        
                               && !ViewCompat.canScrollVertically(recyclerView, 1)                        
                               ) {                    
                           adapter.setState(Adapter.STATE_LOAIND);                    
                           onRefreshHandler.loadMore();                
                        }           
                   }       
               });    
           }    

           /**     
            * 如果滑動控件嵌套過深,可通過該方法控制是否可以下拉     
            */    
            public void setRefreshEnable(boolean enable){       
               // boolean e = !ViewCompat.canScrollVertically(scrollView,-1);        
               if(isEnabled() && !enable){            
                   setEnabled(false);        
               }else if(!isEnabled() && enable){            
                   setEnabled(true);        
               }    
            }    

            /**     
             * 解決水平滑動沖突     
             */
             @Override    
             public boolean onInterceptTouchEvent(MotionEvent event) {        
                 switch (event.getAction()) {            
                    case MotionEvent.ACTION_DOWN:               
                        mPrevX = MotionEvent.obtain(event).getX();                
                        break;            
                     case MotionEvent.ACTION_MOVE:                
                         final float eventX = event.getX();                
                         float xDiff = Math.abs(eventX - mPrevX);               
                         if (xDiff > mTouchSlop) {                   
                             return false;               
                         }        
                  }        
                  return super.onInterceptTouchEvent(event);   
              }    

              private class OnRefreshCallBack implements OnRefreshListener {        
                   @Override        
                   public void onRefresh() {            
                       if(adapter != null && adapter.getState() != Adapter.STATE_MORE){                 
                           adapter.setState(Adapter.STATE_MORE);            
                       }            
                       if(onRefreshHandler != null){                
                           onRefreshHandler.refresh();            
                       }        
                  }    
              }   

              public static abstract class OnRefreshHandler{        
                   public abstract void refresh();        
                   public void loadMore() {        
                   }   
              }    

               /**     
                * 支持加載更多的適配器    
                */    
                public static abstract class Adapter extends RecyclerView.Adapter {        
                    static final int STATE_MORE = 0, STATE_LOAIND = 1, STATE_END = 2, STATE_ERROR = 3;        
                    int state = STATE_MORE;        

                    public void setState(int state) {           
                        if (this.state != state) {                
                            this.state = state;                
                            notifyItemChanged(getItemCount() - 1);           
                        }       
                    }       

                    public int getState() {           
                         return state;       
                    }        

                    @Override        
                    public int getItemViewType(int position) {           
                       if (position == getItemCount() - 1) {                
                           return -99;           
                       }            
                       return getItemType(position);        
                    }        

                    @Override        
                    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {            
                        if (viewType == -99) {                
                           return new RecyclerView.ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.loadmore_default_footer, parent, false)) {};            
                        } else {               
                           return onCreateItemHolder(parent, viewType);            
                        }       
                    }        

                    @Override        
                    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {            
                        if (getItemViewType(position) == -99) {                
                             ProgressBar progressBar = (ProgressBar) holder.itemView.findViewById(R.id.loadmore_default_footer_progressbar);                
                             TextView textView = (TextView) holder.itemView.findViewById(R.id.loadmore_default_footer_tv);                
                              if (state == STATE_END) {                    
                                  progressBar.setVisibility(View.GONE);                    
                                  textView.setText("沒有更多了");                
                              } else if (state == STATE_MORE) {                    
                                  progressBar.setVisibility(View.GONE);                    
                                  textView.setText("點擊加載");                
                              } else if (state == STATE_LOAIND) {                    
                                  progressBar.setVisibility(View.VISIBLE);                    
                                  textView.setText("加載中...");               
                              } else if (state == STATE_ERROR) {                    
                                  progressBar.setVisibility(View.GONE);                    
                                  textView.setText("加載失敗,點擊重新加載");                
                              }                
                              holder.itemView.setOnClickListener(new OnClickListener() {                    
                                  @Override                    
                                  public void onClick(View view) {                       
                                     if (onRefreshHandler != null && !isRefresh && (state == STATE_MORE || state == STATE_ERROR)) {                            
                                        setState(STATE_LOAIND);                            
                                        onRefreshHandler.loadMore();                        
                                     }                    
                                  }                
                              });            
                         } else {                
                             onBindItemHolder(holder,position);            
                         }        
                    }        

                    @Override        
                    public int getItemCount() {            
                          return getCount() == 0 ? 0 : getCount() + 1;        
                    }        

                    public int getItemType(int position){           
                        return super.getItemViewType(position);       
                    }        

                    public abstract RecyclerView.ViewHolder onCreateItemHolder(ViewGroup parent, int viewType);        

                    public abstract void onBindItemHolder(RecyclerView.ViewHolder holder, int position);        

                    public abstract int getCount();        

                    @Override        
                    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {            
                       // 處理瀑布流模式 最后的 item 占整行            
                       if (holder.getLayoutPosition() == getItemCount() - 1) {                
                          LayoutParams lp = holder.itemView.getLayoutParams();                                      
                          if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) {                    
                              StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;                    
                              p.setFullSpan(true);               
                          }            
                       }        
                    }        

                    @Override        
                    public void onAttachedToRecyclerView(RecyclerView recyclerView) {            
                       // 處理網(wǎng)格布局模式 最后的 item 占整行            
                       final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();           
                       if (layoutManager instanceof GridLayoutManager) {                
                           GridLayoutManager gridManager = ((GridLayoutManager) layoutManager);                
                           final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridManager.getSpanSizeLookup();               
                           final int lastSpanCount = gridManager.getSpanCount();                
                           gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {                    
                               @Override                    
                               public int getSpanSize(int position) {                        
                                   return position == getItemCount() - 1 ? lastSpanCount :                                
                                           (spanSizeLookup == null ? 1 : spanSizeLookup.getSpanSize(position));                   
                                }                
                           });            
                      }       
                 }   
             }
        }

整體思路就是給RecyclerView在末尾添加了一個item,并且必要保證這個item占據(jù)整行. 所以需要處理兩種情況:

  • StaggeredGridLayoutManager

       StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; 
       //設(shè)置為占滿整行
       p.setFullSpan(true);
    
  • GridLayoutManager

      gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 
           @Override 
           public int getSpanSize(int position) { 
               return position == getItemCount() - 1 ? lastSpanCount : 
                     (spanSizeLookup == null ? 1 : spanSizeLookup.getSpanSize(position)); 
           } 
      });
    

所以利用不規(guī)則布局就可以讓RecyclerView支持分頁功能了.


拖動排序和滑動刪除

RecyclerView的拖動拍和滑動刪除需要靠 ItemTouchHelper 這個類來支持, ItemTouchHelper 有個內(nèi)部抽象類 Callback ,實現(xiàn)這個類可以讓我們定義相關(guān)規(guī)則,以及處理回調(diào)事件.直接看代碼,每個方法都有注釋:

  public class MyItemTouchHandler extends ItemTouchHelper.Callback {    
      ItemTouchAdapterImpl adapter;    

      public MyItemTouchHandler(@NonNull ItemTouchAdapterImpl adapter) {        
          this.adapter = adapter;    
      }    

      /**     
       * 設(shè)置 允許拖拽和滑動刪除的方向     
       */    
       @Override    
       public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {        
          // 指定可 拖拽方向 和 滑動消失的方向        
          int dragFlags,swipeFlags;        
          RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();        
          if (manager instanceof GridLayoutManager || manager instanceof StaggeredGridLayoutManager) {           
               // 上下左右都可以拖動            
               dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;       
           } else {            
              // 可以上下拖動            
              dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;        
           }        
           // 可以左右方向滑動消失        
           swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;        
           // 如果某個值傳 0 , 表示不支持該功能        
           return makeMovementFlags(dragFlags, swipeFlags);    
        }    

        /**     
         * 拖拽后回調(diào),一般通過接口暴露給adapter, 讓adapter去處理數(shù)據(jù)的交換     
         */    
         @Override    
         public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {        
             // 相同 viewType 之間才能拖動交換        
             if (viewHolder.getItemViewType() == target.getItemViewType()) {            
                 int fromPosition = viewHolder.getAdapterPosition();
                 int toPosition = target.getAdapterPosition();
                 if (fromPosition < toPosition) {    
                    //途中所有的item位置都要移動    
                    for (int i = fromPosition; i < toPosition; i++) {        
                        adapter.onItemMove(i, i + 1);    
                    }
                  } else {    
                    for (int i = fromPosition; i > toPosition; i--) {        
                        adapter.onItemMove(i, i - 1);    
                    }
                  }
                  adapter.notifyItemMoved(fromPosition, toPosition);
                  return true;    
             }        
             return false;    
          }    

          /**     
           * 滑動刪除后回調(diào),一般通過接口暴露給adapter, 讓adapter去刪除該條數(shù)據(jù)     
           */    
           @Override    
           public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {        
              // 刪除數(shù)據(jù)        
              adapter.onItemRemove(viewHolder.getAdapterPosition());        
              // adapter 刷新        
              adapter.notifyItemRemoved(viewHolder.getAdapterPosition());    
           }    

           @Override    
           public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {        
               super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);       
               if(actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {            
                    //滑動時改變Item的透明度           
                    final float alpha = 1 - Math.abs(dX) / (float)viewHolder.itemView.getWidth();            
                    viewHolder.itemView.setAlpha(alpha);            
                    viewHolder.itemView.setTranslationX(dX);       
               }    
           }   

           /**     
            * item被選中(長按)     
            * 這里改變了 item的背景色, 也可以通過接口暴露, 讓adapter去處理邏輯     
            */    
            @Override    
            public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {        
                if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {            
                   // 拖拽狀態(tài)            
                   viewHolder.itemView.setBackgroundColor(Color.BLUE);        
                }else if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {            
                   // 滑動刪除狀態(tài)            
                   viewHolder.itemView.setBackgroundColor(Color.RED);       
                }       
                super.onSelectedChanged(viewHolder, actionState);    
            }   

            /**     
             * item取消選中(取消長按)     
             * 這里改變了 item的背景色, 也可以通過接口暴露, 讓adapter去處理邏輯     
             */    
             @Override    
             public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {        
                viewHolder.itemView.setBackgroundColor(Color.TRANSPARENT);        
                super.clearView(recyclerView, viewHolder);    
             }    

             /**     
              * 是否支持長按開始拖拽,默認開啟     * 可以不開啟,然后在長按 item 的時候,手動 調(diào)用 mItemTouchHelper.startDrag(myHolder) 開啟,更加靈活     
              */    
              @Override    
              public boolean isLongPressDragEnabled() {        
                 return adapter.autoOpenDrag();    
              }    

              /**     
               * 是否支持滑動刪除,默認開啟     * 可以不開啟,然后在長按 item 的時候,手動 調(diào)用 mItemTouchHelper.startSwipe(myHolder) 開啟,更加靈活     
               */    
               @Override    
               public boolean isItemViewSwipeEnabled() {        
                    return adapter.autoOpenSwipe();    
               }    

               // 建議讓 adapter 實現(xiàn)該接口    
               public static abstract class ItemTouchAdapterImpl extends RecyclerView.Adapter{        
                    public abstract void onItemMove(int fromPosition, int toPosition);       
                    public abstract void onItemRemove(int position);       
                    // 是否自動開啟拖拽        
                    protected boolean autoOpenDrag(){            
                         return true;       
                    }        
                    // 是否自動開啟滑動刪除        
                    protected boolean autoOpenSwipe(){            
                         return true;        
                    }    
               }
    }

使用方式

 new ItemTouchHelper(new MyItemTouchHandler(myAdapter)).attachToRecyclerView(recyclerView);

 ...

 private class MyAdapter extends MyItemTouchHandler.ItemTouchAdapterImpl{
   ...

     @Override
     public void onItemMove(int fromPosition, int toPosition) {   
         // 拖動排序的回調(diào),這里交換集合中數(shù)據(jù)的位置 
         Collections.swap(str, fromPosition, toPosition);
     }

     @Override
     public void onItemRemove(int position) {
           // 滑動刪除的回調(diào),這里刪除指定的數(shù)據(jù)
     }
 }
拖動排序
滑動刪除

本文Demo

另外還有個使用RecyclerView模仿ViewPager的例子,在這里

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌轩触,老刑警劉巖吴攒,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掺出,死亡現(xiàn)場離奇詭異看杭,居然都是意外死亡,警方通過查閱死者的電腦和手機掉冶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脐雪,“玉大人郭蕉,你說我怎么就攤上這事∥菇” “怎么了召锈?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵撑柔,是天一觀的道長嫂伞。 經(jīng)常有香客問我,道長冯凹,這世上最難降的妖魔是什么吉嚣? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任梢薪,我火速辦了婚禮,結(jié)果婚禮上尝哆,老公的妹妹穿的比我還像新娘秉撇。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布琐馆。 她就那樣靜靜地躺著规阀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瘦麸。 梳的紋絲不亂的頭發(fā)上谁撼,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音滋饲,去河邊找鬼厉碟。 笑死,一個胖子當著我的面吹牛屠缭,可吹牛的內(nèi)容都是我干的箍鼓。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼呵曹,長吁一口氣:“原來是場噩夢啊……” “哼款咖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逢并,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤之剧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后砍聊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體背稼,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年玻蝌,在試婚紗的時候發(fā)現(xiàn)自己被綠了蟹肘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡俯树,死狀恐怖帘腹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情许饿,我是刑警寧澤阳欲,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站陋率,受9級特大地震影響球化,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瓦糟,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一筒愚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧菩浙,春花似錦巢掺、人聲如沸句伶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽考余。三九已至,卻和暖如春倔约,著一層夾襖步出監(jiān)牢的瞬間秃殉,已是汗流浹背坝初。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工浸剩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鳄袍。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓绢要,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拗小。 傳聞我的和親對象是個殘疾皇子重罪,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,498評論 25 707
  • 特別聲明: 本文轉(zhuǎn)發(fā)自:【江清清的博客】http://blog.csdn.net/developer_jiangq...
    _猜火車_閱讀 37,392評論 11 70
  • 冬月橘洲,夕陽西下。暖冬中的植被哀九,沒有了“看萬山紅遍,層林盡染”的繁茂,但秋日的蔥郁仍在,秋日的風(fēng)貌仍在剿配。 ...
    fymp_123方一鳴閱讀 398評論 0 1
  • 擦不干的眼淚就讓它肆意劃破我僵硬冰冷的臉龐 關(guān)不上的心門就任它受盡冷嘲熱諷嘗遍孤獨折磨 看不透的人心就隨他絕情離去...
    孔三四閱讀 341評論 0 0
  • 感恩父母生養(yǎng)之恩,讓我可以體會美好的事物阅束,感恩家公家婆辛苦照顧三個孩子呼胚,讓我可以安心的工作,感恩兒女讓我體會做媽媽...
    黃巧珍閱讀 198評論 0 0