RecyclerView初探

jopen 9年前發布 | 112K 次閱讀 Android開發 移動開發 RecyclerView

出自:http://blog.csdn.net/wanglu198506/article/details/43898131 ,這應該是初步了解RecyclerView全貌的最佳文章。

原文地址:http://www.grokkingandroid.com/first-glance-androids-recyclerview/ 
RecyclerView是去年谷歌I/O大會上隨Android L預覽一起發布的,接下來的幾篇譯文將會從各個方面對RecyclerView做一個全面的介紹:

本文示例代碼項目地址:https://github.com/writtmeyer/recyclerviewdemo

為什么叫RecyclerView?

谷歌在Android L預覽版API文檔中是這樣描述的: 
一個非常靈活的用于在有限的窗口范圍內顯示大量數據的控件。 
所以RecyclerView適用于那些有大量同類的View但是不能同時在屏幕中顯示的情況,比如聯系人、用戶列表、音樂文件列表等等。想看到更多信息需要滾動試圖,同時對離開屏幕的視圖進行回收和重用。

以下圖片解釋了這個過程:左邊是應用程序的初始狀態,當向上滾動視圖的時候,一些子View準備被回收,比如右邊的紅框部分中的兩個不可見的View。現在回收器可以把他們放入準備重用的View的列表以便在適當的時候重用。

RecyclerView初探


對View的回收重用是非常有用的,由于不用每次都重新填充布局,這樣可以節約CPU資源;同時由于不用保存大量的不可見的視圖,也有效的節省了內存資源。

看到這里可能有人要說,這個概念早就有了,ListView就是使用這樣的機制。是的沒錯,但是在ListView中,控件外觀、回收過程和其他操作是緊耦合的,缺乏靈活性,這就是為什么谷歌要開發RecyclerView來取代它的意義所在。

RecyclerView本身不關心視圖相關的問題

由于ListView的緊耦合問題,谷歌的改進就是RecyclerView本身不參與任何視圖相關的問題。它不關心如何將子View放在合適的位置,也不關心如何分割這些子View,更不關心每個子View各自的外觀。進一步來說,RecyclerView只負責回收和重用的工作,這就是它名字的由來。

所有關于布局、繪制和其他相關的問題,也就是跟數據展示相關的所有問題,都被委派給了一些”插件化”的類來處理。這使得RecyclerView的API變得非常靈活。你需要一個新的布局么?接入另一個LayoutManager就可以了!你想要不同的動畫么?接入一個新的ItemAnimator就可以了,諸如此類。

以下是RecyclerView中用于數據展示的一些重要的類,他們都是RecyclerView的內部類:

  • Adapter:包裝數據集合并且為每個條目創建視圖。

  • ViewHolder:保存用于顯示每個數據條目的子View。

  • LayoutManager:將每個條目的視圖放置于適當的位置。

  • ItemDecoration:在每個條目的視圖的周圍或上面繪制一些裝飾視圖。

  • ItemAnimator:在條目被添加、移除或者重排序時添加動畫效果。

下面將對這些類進行分別介紹:

RecyclerView.ViewHolder

ViewHolder的基本作用是緩存視圖對象,Android官方很早錢就推薦使用ViewHolder的編程模式,但是現在使用這種模式將是強制性的。

RecyclerView.ViewHolder的子類都可以通過ViewHolder的public的成員變量itemView來訪問每個條目的根視圖,所以ViewHolder子類中不需要在保存這個視圖。

以下是ViewHolder的示例代碼:

public final static class ListItemViewHolder extends RecyclerView.ViewHolder {
   TextView label;
   TextView dateTime;

   public ListItemViewHolder(View itemView) {
      super(itemView);
      label = (TextView)itemView.findViewById(R.id.txt_label_item);
      dateTime = (TextView)itemView.findViewById(R.id.txt_date_time);
   }
}

RecyclerView.Adapter

Adapter充當兩個角色:訪問數據集合以及負責為每個條目創建正確的視圖。Adapter在Android中經常出現,比如ListView、AutoCompleteTextView、Spinner等,他們都繼承自AdapterView,但是RecyclerView并沒有這樣做。

對于RecyclerView,谷歌決定使用新的RecyclerView.Adapter基類來取代舊的Adapter接口。所以,SimpleCursorAdapter、ArrayAdapter都將成為歷史,或者至少不會是他們現在的這種使用方式。

目前RecyclerView.Adapter還沒有默認實現,以后可能會添加。由于RecyclerView.Adapter是一個抽象類,所以必須要實現以下三個方法:

  • public VH onCreateViewHolder(ViewGroup parent, int viewType)

  • public void onBindViewHolder(VH holder, int position)

  • public int getItemCount()

其中VH是泛型類,當繼承RecyclerView.Adapter時需要使用具體的類來替換。Adapter的示例代碼如下:

public class RecyclerViewDemoAdapter extends RecyclerView.Adapter <RecyclerViewDemoAdapter.ListItemViewHolder> {

    private List<DemoModel> items;

    RecyclerViewDemoAdapter(List<DemoModel> modelData) {
        if (modelData == null) {
            throw new IllegalArgumentException(
                  "modelData must not be null");
        }
        this.items = modelData;
    }

    @Override
    public ListItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        View itemView = LayoutInflater.
                from(viewGroup.getContext()).
                inflate(R.layout.item_demo_01, 
                        viewGroup,
                        false);
        return new ListItemViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(ListItemViewHolder viewHolder, int position) {
        DemoModel model = items.get(position);
        viewHolder.label.setText(model.label);
        String dateStr = DateUtils.formatDateTime(
                viewHolder.label.getContext(),
                model.dateTime.getTime(),
                DateUtils.FORMAT_ABBREV_ALL);
        viewHolder.dateTime.setText(dateStr);
    }

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

    public final static class ListItemViewHolder 
           extends RecyclerView.ViewHolder {
        // ... shown above in the ViewHolder section
    }
}

RecyclerView.LayoutManager

LayoutManager可能是RecyclerView中最有趣的部分了,該類負責放置所有的子View到適當位置。該類有一個默認的實現:LinearLayoutManager,當要實現水平或垂直列表時可以使用該類。

更正,作者寫這篇文章的時候確實只有LinearLayoutManager, 現在又增加了StaggeredGridLayoutManager。

必須要給RecyclerView設置一個LayoutManager,否則會拋出異常:

08-01 05:00:00.000  2453  2453 E AndroidRuntime: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v7.widget.RecyclerView$LayoutManager.onMeasure(android.support.v7.widget.RecyclerView$Recycler, android.support.v7.widget.RecyclerView$State, int, int)' on a null object reference
08-01 05:00:00.000  2453  2453 E AndroidRuntime:    at android.support.v7.widget.RecyclerView.onMeasure(RecyclerView.java:1310)

目前LayoutManager只有一個抽象方法:

  • public LayoutParams generateDefaultLayoutParams()

但是還有一個方法需要重寫,因為這個方法很快就要變成抽象的了:

public void scrollToPosition(int position) {
   if (DEBUG) {
      Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract");
   }
}

但是只重寫以上兩個方法還遠遠不夠,畢竟LayoutManager的責任是放置所有子View到適當的位置上,所以還需要重寫onLayoutChildren()方法:

public void onLayoutChildren(Recycler recycler, State state) {
    Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}

LinearlayoutManager

LinearLayoutManager是目前LayoutManager唯一的默認實現,可以用來創建水平或者垂直列表。

LinearLayout非常復雜,這里只簡單談談其中一些關鍵部分。LinearLayoutManager的用法是非常簡單的:初始化它,然后設置一個方向(水平/垂直)就可以了:

LinearLayoutManager layoutManager = new LinearLayoutManager(context); 
layoutManager.setOrientation(LinearLayoutManager.VERTICAL); 
layoutManager.scrollToPosition(currPos); 
recyclerView.setLayoutManager(layoutManager);

LinearLayoutManager還提供了一些方法用來查找當前屏幕上的第一個和最后一個條目:

  • findFirstVisibleItemPosition()

  • findFirstCompletelyVisibleItemPosition()

  • findLastVisibleItemPosition()

  • findLastCompletelyVisibleItemPosition()

RecyclerView.ItemDecoration

使用ItemDecoration可以給每個條目添加偏移量、可以給條目間添加間距或者高亮條目,添加其他修飾效果等等。 
ItemDecoration并不是必須使用的,例如可以使用CardView作為每個條目的視圖。

另外,可以添加任意數量的ItemDecoration,RecyclerView會遍歷所有的ItemDecoration然后按適當順序調用每一個的繪制方法

ItemDecoration虛基類包含以下三個方法:

  • public void onDraw(Canvas c, RecyclerView parent)

  • public void onDrawOver(Canvas c, RecyclerView parent)

  • public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)

onDraw中繪制的內容可能會被條目內容所覆蓋,如果要在條目上繪制內容需要使用onDrawOver()方法。如果需要條目間距來繪制分割線,那么使用以上兩個方法的差別不大,但是如果真的要添加裝飾效果,還是需要使用onDrawOver()。

LayoutManager在測量階段會調用getItemOffset()方法來正確計算每個條目視圖的大小。outRect參數可能第一眼看來有些奇怪,為什么不使用一個返回值呢?但是這樣的參數設置確實是有意義的,它使得對于所有的子視圖RecyclerView可以重用同一個Rect對象,節省了資源開銷。這并不是必須的,但確實是很有效率的。

RecyclerView.ItemAnimator

ItemAnimator可以為每個條目添加動畫效果,它主要處理以下三種事件:

  • An item gets added to the data set

  • An item gets removed from the data set

  • An item moves as a result of one or more of the previous two operations

ItemAnimator有一個默認實現:DefaultItemAnimator,如果沒有設置自定義的ItemAnimator,RecyclerView就會使用默認的DefaultItemAnimator。

顯然如果要讓動畫生效,需要知道數據集合的變化,這需要Adapter的幫助。在之前的版本中,當變化發生改變時,需要調用notifyDataSetChanged()方法。但這是不合適的,因為這個方法會觸發對于所有可視子View的重繪,而且沒有動畫效果。要看到動畫效果需要使用更加特殊的方法:

RecyclerView.Adapter類包含很多notifyXXX()類型的方法,兩個最常用的是:

  • public final void notifyItemInserted(int position)

  • public final void notifyItemRemoved(int position)

監聽器

RecyclerVIew提供了一些非常通用的監聽器。之前常見的那些使用方式現在都不適用了,RecyclerView沒有提供OnItemClickListener或者OnItemLongClickListener。但是可以使用RecyclerView.OnItemTouchListener結合手勢監聽來處理這些事件。需要的編碼工作必之前達到同樣的效果要多。

把以上所有類結合起來

布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".RecyclerViewDemoActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity"
        tools:listitem="@layout/item_demo_01"
        />

    <ImageButton
        android:id="@+id/fab_add"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:layout_width="@dimen/fab_size"
        android:layout_height="@dimen/fab_size"
        android:layout_gravity="bottom|right"
        android:layout_marginRight="16dp"
        android:layout_marginBottom="16dp"
        android:background="@drawable/ripple"
        android:stateListAnimator="@anim/anim"
        android:src="@drawable/ic_action_add"
        android:elevation="1dp"
        />
</RelativeLayout>

RecyclerView的AttributeSet會在generateLayoutParams()方法中使用:

@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
   if (mLayout == null) {
      throw new IllegalStateException("RecyclerView has no LayoutManager");
   }
   return mLayout.generateLayoutParams(getContext(), attrs);
}

程序代碼部分很簡單:

setContentView(R.layout.activity_recyclerview_demo);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);

LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
layoutManager.scrollToPosition(0);
recyclerView.setLayoutManager(layoutManager);

// allows for optimizations if all item views are of the same size:
recyclerView.setHasFixedSize(true);

// For the sake of simplicity I misused the Application subclass as a DAO
List<DemoModel> items = RecyclerViewDemoApp.getDemoData();
adapter = new RecyclerViewDemoAdapter(items);
recyclerView.setAdapter(adapter);

RecyclerView.ItemDecoration itemDecoration =
        new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST);
recyclerView.addItemDecoration(itemDecoration);

// this is the default;
// this call is actually only necessary with custom ItemAnimators
recyclerView.setItemAnimator(new DefaultItemAnimator());

// onClickDetection is done in this Activity’s OnItemTouchListener
// with the help of a GestureDetector;
// Tip by Ian Lake on G+ in a comment to this post:
// https://plus.google.com/+LucasRocha/posts/37U8GWtYxDE
recyclerView.addOnItemTouchListener(this);
gesturedetector =
        new GestureDetectorCompat(this, new RecyclerViewDemoOnGestureListener());

主要步驟如下:

  1. 獲取RecyclerView的引用

  2. 創建一個LayoutManager并添加

  3. 創建一個Adapter并添加

  4. 如果需要的話,創建1個或多個ItemDecoration并添加

  5. 如果需要的話,創建ItemAnimator并添加

  6. 如果需要的話,創建1個或多個監聽器并添加

當然這只是初步的代碼,真正有趣的部分在于RecyclerView有很多內部類,可以用來繼承并且實現各種自定義的效果,這才是真正需要投入大量工作的地方。


 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!