原文地址 · Frank-Zhu http://frank-zhu.github.io/android/2015/02/26/android-recyclerview-part-3/?utm_source=tuicool&utm_medium=referraljava
在上一篇(RecyclerView使用詳解(二))文章中介紹了RecyclerView的多Item佈局實現,接下來要來說講RecyclerView的Cursor實現,相較於以前的實現,Cursor有更多的使用場景,也更加的經常使用,特別是配合LoaderManager和CursorLoader進行數據的緩存及加載顯示,基於此咱們來重點看看RecyclerView的CursorAdapter具體要怎麼實現。android
1、CursorAdapter實現(配合LoaderManager和CursorLoader)git
若是以前你用過ListView實現過此功能(CursorAdapter),那麼你必定對下面這兩個方法並不陌生github
1 @Override 2 public View newView(Context context, Cursor cursor, ViewGroup parent) { 3 return null; 4 } 5 6 @Override 7 public void bindView(View view, Context context, Cursor cursor) { 8 9 }
其中newView方法是用來建立Item佈局的,bindView 方法是用來綁定當前View數據的,就至關於以前的getView方法拆成了兩個方法實現。數據庫
若是你用RecyclerView,你會發現CursorAdapter這個類沒有了,既然沒有了,那咱們就本身仿照着ListView的CursorAdapter類來實現,具體的代碼沒什麼大的出入,無非就是註冊兩個觀察者去監聽數據庫數據的變化,可是有兩個地方須要注意一下,一個就是hasStableIds() 這個方法RecyclerView.Adapter中不能複寫父類的方法,須要在初始化的時候調用setHasStableIds(true); 來完成相同功能,第二個就是notifyDataSetInvalidated() 這個方法沒有,統一修改爲notifyDataSetChanged() 方法便可。緩存
1 void init(Context context, Cursor c, int flags) { 2 boolean cursorPresent = c != null; 3 mCursor = c; 4 mDataValid = cursorPresent; 5 mContext = context; 6 mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1; 7 if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) { 8 mChangeObserver = new ChangeObserver(); 9 mDataSetObserver = new MyDataSetObserver(); 10 } else { 11 mChangeObserver = null; 12 mDataSetObserver = null; 13 } 14 15 if (cursorPresent) { 16 if (mChangeObserver != null) c.registerContentObserver(mChangeObserver); 17 if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver); 18 } 19 20 setHasStableIds(true);//這個地方要注意一下,須要將表關聯ID設置爲true 21 } 22 23 //************// 24 public Cursor swapCursor(Cursor newCursor) { 25 if (newCursor == mCursor) { 26 return null; 27 } 28 Cursor oldCursor = mCursor; 29 if (oldCursor != null) { 30 if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver); 31 if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver); 32 } 33 mCursor = newCursor; 34 if (newCursor != null) { 35 if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver); 36 if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver); 37 mRowIDColumn = newCursor.getColumnIndexOrThrow("_id"); 38 mDataValid = true; 39 // notify the observers about the new cursor 40 notifyDataSetChanged(); 41 } else { 42 mRowIDColumn = -1; 43 mDataValid = false; 44 // notify the observers about the lack of a data set 45 //There is no notifyDataSetInvalidated() method in RecyclerView.Adapter 46 notifyDataSetChanged();//注意此處 47 } 48 return oldCursor; 49 } 50 //************// 51 private class MyDataSetObserver extends DataSetObserver { 52 @Override 53 public void onChanged() { 54 mDataValid = true; 55 notifyDataSetChanged(); 56 } 57 58 @Override 59 public void onInvalidated() { 60 mDataValid = false; 61 //There is no notifyDataSetInvalidated() method in RecyclerView.Adapter 62 notifyDataSetChanged();//注意此處 63 } 64 }
怎麼樣,是否是很簡單,沒錯,就是這麼簡單,這裏是完整的BaseAbstractRecycleCursorAdapter代碼,用法和ListView的CursorAdapter用法一致,具體的能夠看看個人Recyclerview LoaderManager Providerapp
2、Item的動畫實現 RecyclerView自己就已經實現了ITEM的動畫,只須要調用如下幾個函數來增刪Item便可出現默認動畫。ide
1 notifyItemChanged(int) 2 notifyItemInserted(int) 3 notifyItemRemoved(int) 4 notifyItemRangeChanged(int, int) 5 notifyItemRangeInserted(int, int) 6 notifyItemRangeRemoved(int, int)
怎麼樣,是否是很輕鬆,若是你不知足系統默認動畫,那麼你能夠自定義實現RecyclerView.ItemAnimator的接口方法,實現代碼能夠參考DefaultItemAnimator.固然,若是你不想本身實現,那麼也不要緊,這裏有人已經寫了開源庫,你能夠去看看recyclerview-animators,這裏給出默認動畫實現方式代碼AnimFragment函數
3、嵌套RecycleView佈局
通常是不推薦使用嵌套RecycleView的,和ListView是相似的,遇到這種須要嵌套的View通常都是想別的辦法來規避,好比動態AddView,或者經過RecycleView的MultipleItemAdapter來實現,經過設置不一樣的ItemType佈局不一樣的View,可是有時候會閒麻煩,想直接就用嵌套的方式來作,那麼和ListView實現方式不一樣的是,ListView的實現通常都是繼承ListView而後複寫onMeasure方法,以下所示:
1 @Override 2 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 3 int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); 4 super.onMeasure(widthMeasureSpec, expandSpec); 5 }
可是須要特別注意的一點,RecycleView的實現方式再也不是繼承RecycleView來作,而是經過修改LayoutManager的方式,即經過繼承LinearLayoutManager GridLayoutManagerStaggeredGridLayoutManager來修改子控件的測量,下面給出主要代碼:
1 private int[] mMeasuredDimension = new int[2]; 2 3 @Override 4 public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, 5 int widthSpec, int heightSpec) { 6 7 final int widthMode = View.MeasureSpec.getMode(widthSpec); 8 final int heightMode = View.MeasureSpec.getMode(heightSpec); 9 final int widthSize = View.MeasureSpec.getSize(widthSpec); 10 final int heightSize = View.MeasureSpec.getSize(heightSpec); 11 12 Log.i(TAG, "onMeasure called. \nwidthMode " + widthMode 13 + " \nheightMode " + heightSpec 14 + " \nwidthSize " + widthSize 15 + " \nheightSize " + heightSize 16 + " \ngetItemCount() " + getItemCount()); 17 18 int width = 0; 19 int height = 0; 20 for (int i = 0; i < getItemCount(); i++) { 21 measureScrapChild(recycler, i, 22 View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), 23 View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), 24 mMeasuredDimension); 25 26 if (getOrientation() == HORIZONTAL) { 27 width = width + mMeasuredDimension[0]; 28 if (i == 0) { 29 height = mMeasuredDimension[1]; 30 } 31 } else { 32 height = height + mMeasuredDimension[1]; 33 if (i == 0) { 34 width = mMeasuredDimension[0]; 35 } 36 } 37 } 38 switch (widthMode) { 39 case View.MeasureSpec.EXACTLY: 40 width = widthSize; 41 case View.MeasureSpec.AT_MOST: 42 case View.MeasureSpec.UNSPECIFIED: 43 } 44 45 switch (heightMode) { 46 case View.MeasureSpec.EXACTLY: 47 height = heightSize; 48 case View.MeasureSpec.AT_MOST: 49 case View.MeasureSpec.UNSPECIFIED: 50 } 51 52 setMeasuredDimension(width, height); 53 } 54 55 private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, 56 int heightSpec, int[] measuredDimension) { 57 try { 58 View view = recycler.getViewForPosition(0);//fix 動態添加時報IndexOutOfBoundsException 59 60 if (view != null) { 61 RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams(); 62 63 int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, 64 getPaddingLeft() + getPaddingRight(), p.width); 65 66 int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, 67 getPaddingTop() + getPaddingBottom(), p.height); 68 69 view.measure(childWidthSpec, childHeightSpec); 70 measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin; 71 measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin; 72 recycler.recycleView(view); 73 } 74 } catch (Exception e) { 75 e.printStackTrace(); 76 } finally { 77 } 78 }
1 private int[] mMeasuredDimension = new int[2]; 2 3 @Override 4 public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) { 5 final int widthMode = View.MeasureSpec.getMode(widthSpec); 6 final int heightMode = View.MeasureSpec.getMode(heightSpec); 7 final int widthSize = View.MeasureSpec.getSize(widthSpec); 8 final int heightSize = View.MeasureSpec.getSize(heightSpec); 9 10 int width = 0; 11 int height = 0; 12 int count = getItemCount(); 13 int span = getSpanCount(); 14 for (int i = 0; i < count; i++) { 15 measureScrapChild(recycler, i, 16 View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), 17 View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), 18 mMeasuredDimension); 19 20 if (getOrientation() == HORIZONTAL) { 21 if (i % span == 0) { 22 width = width + mMeasuredDimension[0]; 23 } 24 if (i == 0) { 25 height = mMeasuredDimension[1]; 26 } 27 } else { 28 if (i % span == 0) { 29 height = height + mMeasuredDimension[1]; 30 } 31 if (i == 0) { 32 width = mMeasuredDimension[0]; 33 } 34 } 35 } 36 37 switch (widthMode) { 38 case View.MeasureSpec.EXACTLY: 39 width = widthSize; 40 case View.MeasureSpec.AT_MOST: 41 case View.MeasureSpec.UNSPECIFIED: 42 } 43 44 switch (heightMode) { 45 case View.MeasureSpec.EXACTLY: 46 height = heightSize; 47 case View.MeasureSpec.AT_MOST: 48 case View.MeasureSpec.UNSPECIFIED: 49 } 50 51 setMeasuredDimension(width, height); 52 } 53 54 private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, 55 int heightSpec, int[] measuredDimension) { 56 if (position < getItemCount()) { 57 try { 58 View view = recycler.getViewForPosition(0);//fix 動態添加時報IndexOutOfBoundsException 59 if (view != null) { 60 RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams(); 61 int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, 62 getPaddingLeft() + getPaddingRight(), p.width); 63 int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, 64 getPaddingTop() + getPaddingBottom(), p.height); 65 view.measure(childWidthSpec, childHeightSpec); 66 measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin; 67 measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin; 68 recycler.recycleView(view); 69 } 70 } catch (Exception e) { 71 e.printStackTrace(); 72 } 73 } 74 }
##4、效果圖以下:
Item默認動畫效果
嵌套ScrollView效果