Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android開發中RecyclerView組件使用的一些進階技講解

Android開發中RecyclerView組件使用的一些進階技講解

編輯:關於Android編程

RecyclerView的優勢:

  • 它自帶ViewHolder來實現View的復用機制,再也不用ListView那樣在getView()裡自己寫了
  • 使用LayoutManager可以實現ListView,GridView以及流式布局的列表效果
  • 通過setItemAnimator(ItemAnimator animator)可以實現增刪動畫(懶的話,可以使用默認的ItemAnimator對象,效果也不錯)
  • 控制item的間隔,可以使用addItemDecoration(ItemDecoration decor),不過裡邊的ItemDecoration是一個抽象類,需要自己去實現...

用法介紹:

導入RecyclerView的v7庫:
RecyclerView是一個android.support.v7庫裡的控件,因此在使用的時候我們需要在gradle配置文件裡加上compile 'com.android.support:recyclerview-v7:22.2.1'來引入google官方的這個庫
xml布局中,使用常規的控件引入方式,來引入RecyclerView,如下:

 <android.support.v7.widget.RecyclerView
 android:id="@+id/recyclerview_content"
 
 android:scrollbars="vertical"
 android:fadeScrollbars="true"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:layout_marginBottom="-55dp" />

代碼中的寫法基本和ListView相差無幾,但還是要重點說一下:

在實例化RecyclerView之後,我們需要使用setLayoutManager()給它設置布局管理器,其中的實參即就是LayoutManager,這裡總共有兩種LayoutManager:

1.StaggeredGridLayoutManager,是我們之前提到的流式布局:
它有一個構造方法StaggeredGridLayoutManager(int spanCount, int orientation),第一個是網格的列數,第二個參數是數據呈現的方向
(如果是豎直,那麼第一個參數的意義就是列數,反之為行數。而且第二個參數在StaggeredGridLayoutManager裡也有同樣名稱的常量,請同學們自行采納[這裡還是建議大家使用庫裡自帶的常量,因為他們一般都是整型值,這樣可以避免各個類裡所判別的常量值不一樣而導致的其他問題]);
2.GridLayoutManager,網格布局(流式布局應該是它的一個特殊情況):
GridLayoutManager(Context context, int spanCount)或
GridLayoutManager(Context context, int spanCount, int orientation,
 boolean reverseLayout)需要說明的是,最後一個參數表示的是是否逆向布局(意思是將數據反向顯示,原先從左向右,從上至下。設為true之後全部逆轉)。
小提示:在這兩個LayoutManager中,默認的orientation為vertical,reverseLayout為false。對應的參數在GridLayoutManager中都有對應的方法來進行補充設置。而在StaggeredGridLayoutManager中所有的方法都針對reverseLayout做了判斷,然而它並沒有給出這個參數設定值的api。

線性布局, LinearLayoutManager
LinearLayoutManager(Context context)構建一個默認布局方向為VERTICAL的RecyclerView,這種樣式也是ListView默認的。
LinearLayoutManager(Context context, int orientation, boolean reverseLayout)。發現沒有,線性布局和網格布局幾乎是一樣的。只是少了一個spanCount參數。

和ListView一樣,為RecyclerView設置Adapter

 class HomeAdapter extends RecyclerView.Adapter<HomeAdapter.MyViewHolder>
 {

 @Override
 public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
 {
   MyViewHolder holder = new MyViewHolder(LayoutInflater.from(
       HomeActivity.this).inflate(R.layout.item_home, parent,
       false));
   return holder;
 }

 @Override
 public void onBindViewHolder(MyViewHolder holder, int position)
 {
   holder.tv.setText(mDatas.get(position));
 }

 @Override
 public int getItemCount()
 {
   return mDatas.size();
 }

 class MyViewHolder extends ViewHolder
 {
   TextView tv;

   public MyViewHolder(View view)
   {
     super(view);
     tv = (TextView) view.findViewById(R.id.id_num);
   }
 }
}

如上,即就是Adapter的寫法。其中的ViewHolder就是拿來負責View的回收和復用的,這樣就不需要我們自己寫完ViewHolder之後,還要在getView(int position, View convertView, ViewGroup parent)裡一頓判斷,一頓綁定,一頓find了。而且這裡的ViewHolder成為了RecyclerView中必須繼承的一部分,重寫完後就需要放入 RecyclerView.Adapter< >這裡來對基類的范型初始化。

在這裡,Recyclerview已經為你封裝好了:

  • getItemCount()就不必多說了,和ListView是一樣的
  • getItemViewType(int position)是用來根據position的不同來實現RecyclerView中對不同布局的要求。從這個方法中所返回的值會在onCreateViewHolder中用到。比如頭部,尾部,等等的特殊itemView(這裡說成ViewHolder比較好)都可以在這裡進行判斷。
  • onCreateViewHolder(ViewGroup parent, int viewType)是用來配合寫好的ViewHolder來返回一個ViewHolder對象。這裡也涉及到了條目布局的加載。viewType則表示需要給當前position生成的是哪一種ViewHolder,這個參數也說明了RecyclerView對多類型ItemView的支持。
  • onBindViewHolder(MyViewHolder holder, int position)專門用來綁定ViewHolder裡的控件和數據源中position位置的數據。

這裡,會有人問,那麼item的子控件findViewById 去哪兒了?我們把它交給了ViewHolder的構造方法(其他方法也可以),它的本質是在onCreateViewHolder方法裡生成ViewHolder的時候執行的。

提升:
1.代碼重構:
在上邊看了這麼多,有木有覺得,ViewHolder的功能並不是非常明確?它既負責了子控件的查詢,又負責了子控件的裝載工作。而布局加載和數據綁定卻交給了Adapter......
我們來看看掘金的做法:
首先,它把Adapter和ViewHolder的功能以一種較為低耦合的方式進行了職能分離,讓ViewHolder裡所有的邏輯代碼全部都只出現在ViewHolder中。我們現在就對前邊提到的代碼進行重構:

ViewHodler:

class MyViewHolder extends ViewHolder{
  TextView tv;    
  public MyViewHolder(Context context, View view){
     super(view);
     tv = (TextView) view.findViewById(R.id.id_num);

     //當然,我們也可以在這裡使用view來對RecyclerView的一個item進行事件監聽,也可以使用 
     //tv等子控件來實現item的子控件的事件監聽。這也是我之所以要傳context的原因之一呢~
     ......
  }

  public static MyViewHolder newInstance(Activity context, ViewGroup parent){
      View view = LayoutInflater.from(
       context).inflate(R.layout.item_home, parent,false);
     return new MyViewHolder(context, view);
  }
 }  

 //你沒看錯,數據綁定也被整合進來了, 
 //將adapter裡的數據根據position獲取到後傳進來。當然,也可以根據具體情況來做調整。
  public void onBinViewHolder(String data){   
    tv.setText(data);//既然這裡也有子控件,那麼這裡也可以做item子控件的事件監聽喽
}

RecyclerView:

class HomeAdapter extends RecyclerView.Adapter<MyViewHolder>{
 @Override
 public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType){//如需要,還要對viewType做判斷
   return MyViewHolder.newInstance(this, parent)
 }

 @Override
 public void onBindViewHolder(MyViewHolder holder, int position){
   holder.onBindViewHolder(mDatas.get(position));
 }

 @Override
 public int getItemCount(){
   return mDatas.size();
 }
}

抽取一個條目點擊事件,讓它更像ListView:

class HomeAdapter extends RecyclerView.Adapter<MyViewHolder>{
 private OnItemClickListener mOnItemClickListener;

 public void setOnItemClickLitener(OnItemClickLitener mOnItemClickLitener) 
 { 
   this.mOnItemClickLitener = mOnItemClickLitener; 
 } 

 @Override
 public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType){//如需要,還要對viewType做判斷
   return MyViewHolder.newInstance(this, parent)
 }

 @Override
 public void onBindViewHolder(MyViewHolder holder, int position){
   holder.onBindViewHolder(mDatas.get(position));

   //如果設置了回調,則設置點擊事件 
   if (mOnItemClickLitener != null) { 
     viewHolder.itemView.setOnClickListener(new OnClickListener() { 
       @Override 
       public void onClick(View v) { 
         mOnItemClickLitener.onItemClick(viewHolder.itemView, i); 
       } 
     }); 
   } 
 }

 @Override
 public int getItemCount(){
   return mDatas.size();
 }

 public interface OnItemClickLitener { 
   void onItemClick(View view, int position); 
 } 
}

接口調用

 mAdapter.setOnItemClickLitener(new OnItemClickLitener() { 
 @Override 
 public void onItemClick(View view, int position) { 
   ......
 } 
});

2.External
上邊提到了

控制item的間隔,可以使用addItemDecoration(ItemDecoration decor),不過裡邊的ItemDecoration是一個抽象類,需要自己去實現...
這個問題,那我們來實際解決一下:

這是羊神實現的一個子類

package com.zhy.sample.demo_recyclerview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.State;
import android.util.Log;
import android.view.View;

public class DividerItemDecoration extends RecyclerView.ItemDecoration {

  private static final int[] ATTRS = new int[]{
      android.R.attr.listDivider
  };

  public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;

  public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;

  private Drawable mDivider;

  private int mOrientation;

  public DividerItemDecoration(Context context, int orientation) {
    final TypedArray a = context.obtainStyledAttributes(ATTRS);
    mDivider = a.getDrawable(0);
    a.recycle();
    setOrientation(orientation);
  }

  public void setOrientation(int orientation) {
    if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
      throw new IllegalArgumentException("invalid orientation");
    }
    mOrientation = orientation;
  }

  @Override
  public void onDraw(Canvas c, RecyclerView parent) {
    Log.v("recyclerview - itemdecoration", "onDraw()");

    if (mOrientation == VERTICAL_LIST) {
      drawVertical(c, parent);
    } else {
      drawHorizontal(c, parent);
    }

  }


  public void drawVertical(Canvas c, RecyclerView parent) {
    final int left = parent.getPaddingLeft();
    final int right = parent.getWidth() - parent.getPaddingRight();

    final int childCount = parent.getChildCount();
    for (int i = 0; i < childCount; i++) {
      final View child = parent.getChildAt(i);
      android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
      final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
          .getLayoutParams();
      final int top = child.getBottom() + params.bottomMargin;
      final int bottom = top + mDivider.getIntrinsicHeight();
      mDivider.setBounds(left, top, right, bottom);
      mDivider.draw(c);
    }
  }

  public void drawHorizontal(Canvas c, RecyclerView parent) {
    final int top = parent.getPaddingTop();
    final int bottom = parent.getHeight() - parent.getPaddingBottom();

    final int childCount = parent.getChildCount();
    for (int i = 0; i < childCount; i++) {
      final View child = parent.getChildAt(i);
      final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
          .getLayoutParams();
      final int left = child.getRight() + params.rightMargin;
      final int right = left + mDivider.getIntrinsicHeight();
      mDivider.setBounds(left, top, right, bottom);
      mDivider.draw(c);
    }
  }

  @Override
  public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
    if (mOrientation == VERTICAL_LIST) {
      outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
    } else {
      outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
    }
  }
}

然後就是為我們的RecyclerView實例添加分割線

addItemDecoration(new DividerItemDecoration(this,
DividerItemDecoration.VERTICAL_LIST));

系統默認的分割線往往達不到我們產品和設計師的要求,怎麼辦呢?

<item name="android:listDivider">@drawable/your_custom_divider</item>

通過以上xml屬性,將系統的listDivider設為自己畫出來的分割線,只需要放在你對應activity的主題下即可。

技巧:RecyclerView 滾動條的顯示與隱藏

<android.support.v7.widget.RecyclerView
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:scrollbars="vertical"
      >

    </android.support.v7.widget.RecyclerView>

縱向顯示:

android:scrollbars="vertical"

橫向顯示:

android:scrollbars="horizontal"

隱藏:

android:scrollbars="none"

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved