編輯:關於Android編程
compile 'com.android.support:recyclerview-v7:23.2.1' compile 'com.android.support:appcompat-v7:23.3.0'二、定義數據源對象
public class Item { private String title; private int idSource; public String getTitle()..... }三、為Item創建一個布局文件 item.xml
四、創建一個繼承Recycler.Adapter的類和一個繼承Recycler.ViewHolder的類 一般情況自定義ViewHolder被客戶定義在自定義的Adapter類中
public class MAdapter extends RecyclerView.Adapter{ private ArrayList五、在Activity的布局文件中引入RecyclerView布局 activity_main.xml- items; private Context context; public MTest(ArrayList
- items, Context context) { this.items = items; this.context = context; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.item,parent,false); return new ItemViewHolder(view); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if(holder instanceof ItemViewHolder){ ItemViewHolder itemViewHolder= (ItemViewHolder)holder; itemViewHolder.imageView.setImageBitmap(...); itemViewHolder.textView.setText("test"); } } @Override public int getItemCount() { return items.size(); } class ItemViewHolder extends RecyclerView.ViewHolder{ private ImageView imageView; private TextView textView; public ItemViewHolder(View itemView) { super(itemView); initView(itemView); } private void initView(View view){ imageView = (ImageView)view.findViewById(R.id.item_imageView); textView = (TextView)view.findViewById(R.id.item_textView); } } }
六、在Activity中為RecyclerView進行初始化設置
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recycleView = (RecycleView)findViewById(R.id.recycleView); recycleView.addItemDecoration(...); recycleView.setItemAnimator(...); recycleView.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)); recycleView.setAdapter(....); //保證一定在setLayoutManager方法之後調用該方法!!尤其是你使用RecyclerView顯示Header視圖的時候。 }補充: 對於如何在RecyclerView中添加Header、Footer視圖;給RecyclerView設置下拉上拉刷新視圖等高級使用限於篇幅,而且相關的資料網上也很容易找到,這裡不再詳細介紹。下面給出實現的大體思想。 Header和Footer視圖的添加兩者原理一致,在listView中我們有addHeaderView和addFooterView兩個方法向ListView中添加Header和Footer視圖。如果我們的RecyclerView僅僅局限在垂直布局中顯示,即不使用瀑布流、網格等復雜布局,通過重寫Adapter的getItemViewType(int position)方法我們可以分分鐘就實現添加Header和Footer。但是既然使用了RecyclerView就不可能僅僅局限在使用垂直布局,因此下面給出一種通用的解決方案。重寫Adapter的如下兩個方法,具體內容如下:
對於上下拉加載的實現方法,網上有各種各樣的實現方法;但是最核心的東西都沒有變,下拉刷新都是通過重寫RecyclerView的onTouchEvent(MotionEvent e)方法,方法內部判斷當前RecyclerView的position為0的View是否處於顯示狀態,即視圖是否是最頂層,如果是則利用用戶在y軸的滑動距離改變下拉刷新視圖的顯示高度,最終顯示高度超過設定阈值則進入刷新狀態,直到調用相關方法才停止刷新操作。上拉刷新相對於下拉刷新簡單很多,上拉刷新視圖沒有下拉刷新視圖的狀態處理,可以通過重寫RecyclerView的onScrollStateChanged(int state)方法判斷當前RecyclerView是否已經顯示最後一個數據,如果是就調用加載方法,方法加載完畢通過Adapter添加Item數據,並notifyItem...通知刷新RecyclerView視圖,並隱藏FooterView。 當然還有一種就是使用RecyclerView + SwipeRefreshLayout的模式實現下拉刷新,不過個人覺得它太不美觀因此也就不推薦。感興趣可以看看這個文章/** * 對GridLayoutManager的處理;該方法會在RecyclerView.setAdapter()方法中被調用,因此前面建議保證一定在setLayoutManager方法之後調用該方法 * @param recyclerView */ @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if(layoutManager instanceof GridLayoutManager){ final GridLayoutManager gridLayoutManager = (GridLayoutManager)layoutManager; gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { if: positon是Header視圖或Footer視圖顯示的位置 return gridLayoutManager.getSpanCount(); return 1; } }); } } /** * 對StaggeredGridLayoutManager的處理 * @param holder */ @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { super.onViewAttachedToWindow(holder); ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams(); if(layoutParams!=null&& layoutParams instanceof StaggeredGridLayoutManager.LayoutParams ){ StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams) layoutParams; int position = holder.getLayoutPosition(); if: positon是Header視圖或Footer視圖顯示的位置 lp.setFullSpan(true); } }
private Adapter mAdapter;//與RecyclerView綁定的Adapter @VisibleForTesting LayoutManager mLayout; //與RecyclerView綁定的LayoutManager private final ArrayList往下就按照RecyclerView使用的流程為主線進行分析。即mItemDecorations = new ArrayList<>(); //存儲所有裝飾對象 ItemAnimator mItemAnimator = new DefaultItemAnimator(); //刪除、插入、添加等操作對應顯示的動畫 //OnScrollListener定義了onScrolled和onScrollStateChanged方法 private OnScrollListener mScrollListener; //監聽器 對應setOnScrollListener方法,該方法已經標注為已過時 private List mScrollListeners;//監聽器 對應addOnScrollListener方法 private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver(); //調用Adapter的notifyDataXXX()方法,實際上是在調用 mObserver的對應方法,作用就是從Adapter取出更新的數據 final Recycler mRecycler = new Recycler();//管理RecyclerView暫時不使用的ViewHolder對象 private final ViewFlinger mViewFlinger = new ViewFlinger();//實現根據用戶滑動的動作慣性的繼續執行一段滑動,
public void addItemDecoration(ItemDecoration decor) { addItemDecoration(decor, -1); } addItemDecoration()@RecyclerView.class public void addItemDecoration(ItemDecoration decor, int index) { if (mLayout != null) { mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or" + " layout"); } //如果當前Layout處於繪制狀態會報錯 ... if (index < 0) { mItemDecorations.add(decor); } //末尾添加 else {mItemDecorations.add(index, decor); }//指定位置添加 ..... requestLayout(); } requestLayout()@RecyclerView.class public void requestLayout() { if (mEatRequestLayout == 0 && !mLayoutFrozen) { super.requestLayout(); } else { mLayoutRequestEaten = true; } }該方法大體思路很簡單就是將參數ItemDecoration decor存入集合ArrayList
public void setItemAnimator(ItemAnimator animator) { if (mItemAnimator != null) { // mItemAnimator.endAnimations(); mItemAnimator.setListener(null); } mItemAnimator = animator; if (mItemAnimator != null) { mItemAnimator.setListener(mItemAnimatorListener); //note1 } }跟addItemDecoration()方法類似,只不過這裡是更新ItemAnimator mItemAnimator域 setLayoutManager()@RecyclerView.class
public void setLayoutManager(LayoutManager layout) { if (layout == mLayout) { return; } stopScroll(); if (mLayout != null) { //第一次調用該語句為假 if (mIsAttached) { mLayout.dispatchDetachedFromWindow(this, mRecycler); } mLayout.setRecyclerView(null); } mRecycler.clear(); //clear操作 ..... mLayout = layout; //更新mLayout域 if (layout != null) { if (layout.mRecyclerView != null) { throw ....} mLayout.setRecyclerView(this); //LayoutManager和當前RecyclerView對象 if (mIsAttached) { mLayout.dispatchAttachedToWindow(this); } } requestLayout(); //重繪 }進行的主要操作有:清空Recycler mRecycler中的數據、更新LayoutManager mLayout域、將當前RecyclerView和LayoutManager綁定; setAdapter()@RecyclerView.class
public void setAdapter(Adapter adapter) { setLayoutFrozen(false); setAdapterInternal(adapter, false, true); requestLayout(); //重繪 } setAdapterInternal()@RecyclerView.class private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) { if (mAdapter != null) { mAdapter.unregisterAdapterDataObserver(mObserver); mAdapter.onDetachedFromRecyclerView(this); } if (!compatibleWithPrevious || removeAndRecycleViews) {//對之前的View、動畫、裝飾等視圖進行回收 //一般情況會進到這裡 if (mItemAnimator != null) { mItemAnimator.endAnimations(); } if (mLayout != null) { mLayout.removeAndRecycleAllViews(mRecycler); mLayout.removeAndRecycleScrapInt(mRecycler); } mRecycler.clear(); } mAdapterHelper.reset(); final Adapter oldAdapter = mAdapter; mAdapter = adapter; //賦予新值 if (adapter != null) { adapter.registerAdapterDataObserver(mObserver); adapter.onAttachedToRecyclerView(this); //在方法內部針對GridLayoutManager布局進行的設置,使得可以顯示Header和Footer視圖 } if (mLayout != null) { mLayout.onAdapterChanged(oldAdapter, mAdapter); } mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); mState.mStructureChanged = true; ..... }該方法的主要動作是,移除LayoutManager中的視圖、清空Recycler mRecycler中的數據、更新Adapter mAdapter域、注冊數據觀察者、Adapter和RecyclerView綁定; 小結:addItemDecoration、setItemAnimator、setLayoutManager和setAdapter幾個方法相對簡單,大體都是更新RecyclerView中的相關域,方法的最後會申請requestLayout進行繪制。
public boolean onTouchEvent(MotionEvent e) { ...... if (dispatchOnItemTouch(e)) { //note1 cancelTouch(); return true; } if (mLayout == null) { return false; } ...... switch (action) { case MotionEvent.ACTION_DOWN: { //記錄當前按下的坐標值 ...... mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); ...... } break; case MotionEvent.ACTION_MOVE: { final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId); if (index < 0) { return false; } final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f); final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f); int dx = mLastTouchX - x; int dy = mLastTouchY - y; if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) { dx -= mScrollConsumed[0]; dy -= mScrollConsumed[1]; vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]); mNestedOffsets[0] += mScrollOffset[0]; mNestedOffsets[1] += mScrollOffset[1]; } if (mScrollState != SCROLL_STATE_DRAGGING) { //不是在拖動頁面 boolean startScroll = false; if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { if (dx > 0) { dx -= mTouchSlop; } else { dx += mTouchSlop;} startScroll = true; } if (canScrollVertically && Math.abs(dy) > mTouchSlop) { if (dy > 0) { dy -= mTouchSlop; } else { dy += mTouchSlop; } startScroll = true; } if (startScroll) { setScrollState(SCROLL_STATE_DRAGGING);//note2 } } if (mScrollState == SCROLL_STATE_DRAGGING) { mLastTouchX = x - mScrollOffset[0]; mLastTouchY = y - mScrollOffset[1]; if (scrollByInternal( canScrollHorizontally ? dx : 0, canScrollVertically ? dy : 0, vtev)) //note3 { getParent().requestDisallowInterceptTouchEvent(true); } } } break; ..... } ..... vtev.recycle(); return true; }1、分派當前事件給子View處理,如果沒有任何子View處理則進行後續操作 2、該方法內部如下
private void setScrollState(int state) { if (state == mScrollState) { return; } mScrollState = state; if (state != SCROLL_STATE_SETTLING) { stopScrollersInternal(); } dispatchOnScrollStateChanged(state); //note1 }
setScrollState處理的參數主要有SCROLL_STATE_IDLE表示當前並不處於滑動狀態、SCROLL_STATE_DRAGGING 表示當前RecyclerView處於滑動狀態(手指在屏幕上)
SCROLL_STATE_SETTLING 表示當前RecyclerView處於滑動狀態,(手已經離開屏幕)
1、該方法底層先後調用LayoutManager的onScrollStateChanged(state)方法、RecyclerView的onScrollStateChanged(state)方法,最後調用RecyclerView下所有的mScrollListener域的onScrollStateChanged(state) 方法。
3、該方法具體內容如下
小結:在事件分發中,首先將事件分派給子View去處理,如果子View沒有消耗當前事件,則事件才會交給RecyclerView執行。RecyclerView可能對事件再包裝交給自己的監聽器去處理也可能觸發刷新視圖的操作,具體通過調用LayoutManager的scrollXXBy方法。scrollByInternal()@RecyclerView.class boolean scrollByInternal(int x, int y, MotionEvent ev) { int unconsumedX = 0, unconsumedY = 0; int consumedX = 0, consumedY = 0; ..... if (mAdapter != null) { ...... if (x != 0) { consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState); //note1 unconsumedX = x - consumedX; } if (y != 0) { consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState); //note2 unconsumedY = y - consumedY; } ......... if (consumedX != 0 || consumedY != 0) { dispatchOnScrolled(consumedX, consumedY); //note3 } if (!awakenScrollBars()) { invalidate(); } return consumedX != 0 || consumedY != 0; }1-2、這裡調用LayoutManager的scrollXXBy方法,具體LayoutManager會根據參數進行具體的繪制,該部分我們暫時不細談,後面會講。 3、該方法底層先後調用RecyclerView的onScrolled(hresult, vresult);方法,然後調用RecyclerView下所有的mScrollListener域的onScrolled(this, hresult, vresult);方法
protected void onMeasure(int widthSpec, int heightSpec) { if (mLayout == null) { defaultOnMeasure(widthSpec, heightSpec);//note0 return; } if (mLayout.mAutoMeasure) { ...... mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);//note1 ...... mLayout.setMeasureSpecs(widthSpec, heightSpec); ..... mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);//note2 ...... } } else { if (mHasFixedSize) { mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); return; } ...... mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); .... } }0、方法實現如下
1、mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);一般情況下就等於defaultOnMeasure(widthSpec, heightSpec);; 2、mLayout.setMeasuredDimensionFromChildren顧名思義就是調用子View的onMeasure方法。 onMeasure暫時沒有我們太關心的東西,接著往下看。 onLayout()@RecyclerView.classvoid defaultOnMeasure(int widthSpec, int heightSpec) { final int width = LayoutManager.chooseSize(widthSpec, getPaddingLeft() + getPaddingRight(), ViewCompat.getMinimumWidth(this)); final int height = LayoutManager.chooseSize(heightSpec, getPaddingTop() + getPaddingBottom(), ViewCompat.getMinimumHeight(this)); setMeasuredDimension(width, height); //這個是View的方法,我們就不介紹了 }
protected void onLayout(boolean changed, int l, int t, int r, int b) { ..... dispatchLayout(); .... } void dispatchLayout() { if (mAdapter == null) { return; } if (mLayout == null) { return; } mState.mIsMeasuring = false; if (mState.mLayoutStep == State.STEP_START) { dispatchLayoutStep1(); mLayout.setExactMeasureSpecsFrom(this); dispatchLayoutStep2(); } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()) { mLayout.setExactMeasureSpecsFrom(this); dispatchLayoutStep2(); } else { mLayout.setExactMeasureSpecsFrom(this); } dispatchLayoutStep3(); }該方法主要是對LayoutManager的相關方法的調用,後面LayoutManager.class部分會再講。 draw()@RecyclerView.class
public void draw(Canvas c) { super.draw(c); //內部會調用執行完onDraw方法之後才返回 final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDrawOver(c, this, mState); //會在temDecoration的onDraw之後調用 } .... }給itemView繪制完後在其基礎上繪制一些裝飾圖案 onDraw()@RecyclerView.class
public void onDraw(Canvas c) { super.onDraw(c); final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDraw(c, this, mState); //會在temDecoration的onDrawOver之前調用 } }在itemView繪制之前繪制一些裝飾如背景、畫布形狀。 小結:本節分析的onMeasure和onLayout方法暫時沒有找到令我們興奮 的東西,但是在Draw和onDraw方法中我們找到了ItemDecoration的使用!在每次RecyclerView正式繪制之前都會先調用ItemDecorations集合中所有的ItemDecoration對象的onDraw方法,等ItemView繪制完畢,再調用ItemDecorations集合中所有的ItemDecoration對象的onDrawOver方法。 到此為止我們分析完了Part1、Part2、Part3三部分,中間遇到了如下的一些方法還沒有解釋清楚。
ChildHelper mChildHelper; //負責管理LayoutManager使用的ItemView RecyclerView mRecyclerView; //與之綁定的RecyclerViewsetRecyclerView()@LayoutManager.class
void setRecyclerView(RecyclerView recyclerView) { if (recyclerView == null) { mRecyclerView = null; mChildHelper = null; mWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY); mHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY); } else { mRecyclerView = recyclerView; mChildHelper = recyclerView.mChildHelper; mWidthSpec = MeasureSpec .makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY); mHeightSpec = MeasureSpec .makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY); } }對LayoutManager中的域完成初始化值的設置,如mChildHelper和mRecyclerView onAdapterChanged()@LayoutManager.class public void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) { } 空方法沒有子類實現它 dispatchDetachedFromWindow()@LayoutManager.class
void dispatchDetachedFromWindow(RecyclerView view, Recycler recycler) { mIsAttachedToWindow = false; onDetachedFromWindow(view, recycler); } onDetachedFromWindow()@LinearLayoutManager.class public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) { if (mRecycleChildrenOnDetach) { removeAndRecycleAllViews(recycler); recycler.clear(); } } removeAndRecycleAllViews()@LayoutManager.class public void removeAndRecycleAllViews(Recycler recycler) { for (int i = getChildCount() - 1; i >= 0; i--) { //內部調用mChildHelper.getChildCount() final View view = getChildAt(i); //內部調用mChildHelper.getChildAt(index) if (!getChildViewHolderInt(view).shouldIgnore()) { //note1 removeAndRecycleViewAt(i, recycler); } } }1、getChildViewHolderInt(view):通過當前view的LayoutParams值獲取ViewHolder、這裡的LayoutParams是RecyclerView的內部類,由當前View對應的ViewHolder
removeAndRecycleViewAt()@LayoutManager.class public void removeAndRecycleViewAt(int index, Recycler recycler) { final View view = getChildAt(index); //內部調用mChildHelper.getChildAt(index) removeViewAt(index); //內部調用mChildHelper.removeViewAt(index); recycler.recycleView(view); }小結:dispatchDetachedFromWindow()方法中主要是調用了removeViewAt(index)、recycler.recycleView(view)和recycler.clear()方法。removeViewAt(index)將RecyclerView不再使用的View移除;recycler.recycleView(view)用於回收該ViewHolder。LayoutManager使用中的View都是交給ChildHelper完成的,Recycler負責回收ViewHolder。 dispatchAttachedToWindow()@LayoutManager.class
void dispatchAttachedToWindow(RecyclerView view) { mIsAttachedToWindow = true; onAttachedToWindow(view);//note1 }1、該方法留給子類實現,但是子類基本都沒有重寫該方法 removeAndRecycleAllViews()@LayoutManager.class 該部分已經在dispatchDetachedFromWindow()@LayoutManager.class中介紹過 removeAndRecycleScrapInt()@LayoutManager.class
void removeAndRecycleScrapInt(Recycler recycler) { final int scrapCount = recycler.getScrapCount(); for (int i = scrapCount - 1; i >= 0; i--) { final View scrap = recycler.getScrapViewAt(i); final ViewHolder vh = getChildViewHolderInt(scrap); //note1 if (vh.shouldIgnore()) { continue; } vh.setIsRecyclable(false); //避免因關閉動畫而導致的重復回收 if (vh.isTmpDetached()) { mRecyclerView.removeDetachedView(scrap, false); } if (mRecyclerView.mItemAnimator != null) { mRecyclerView.mItemAnimator.endAnimation(vh); } //結束動畫 vh.setIsRecyclable(true); recycler.quickRecycleScrapView(scrap); } recycler.clearScrap(); if (scrapCount > 0) { mRecyclerView.invalidate(); } }1、getChildViewHolderInt(view):通過當前view的LayoutParams值獲取ViewHolder、這裡的LayoutParams是RecyclerView的內部類,由當前View對應的ViewHolder scrollHorizontallyBy()@LayoutManager.calss
public int scrollHorizontallyBy(int dx, Recycler recycler, State state) { return 0; } scrollHorizontallyBy()@LinearLayoutManager.calss public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) { if (mOrientation == VERTICAL) { return 0; } return scrollBy(dx, recycler, state); } scrollBy()@LinearLayoutManager.calss int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { if (getChildCount() == 0 || dy == 0) { //內部調用mChildHelper.getChildCount()方法 return 0; } mLayoutState.mRecycle = true; ensureLayoutState(); //mLayoutState和mOrientationHelper初始化設置 final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START; final int absDy = Math.abs(dy); updateLayoutState(layoutDirection, absDy, true, state); //..... final int consumed = mLayoutState.mScrollingOffset + fill(recycler, mLayoutState, state, false); //繪制肯定在fill方法內部 if (consumed < 0) { return 0; } final int scrolled = absDy > consumed ? layoutDirection * consumed : dy; mOrientationHelper.offsetChildren(-scrolled); mLayoutState.mLastScrollDelta = scrolled; return scrolled; } fill()@LinearLayoutManager.calss int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { final int start = layoutState.mAvailable; ...... int remainingSpace = layoutState.mAvailable + layoutState.mExtra; LayoutChunkResult layoutChunkResult = new LayoutChunkResult(); while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { layoutChunkResult.resetInternal(); //layoutChunkResult域初始化 layoutChunk(recycler, state, layoutState, layoutChunkResult); //note1 具體內容見後面 if (layoutChunkResult.mFinished) { break; } ........ } //end of While return start - layoutState.mAvailable; } layoutChunk()@LinearLayoutManager.calss void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { View view = layoutState.next(recycler); //note1 LayoutParams params = (LayoutParams) view.getLayoutParams(); if (layoutState.mScrapList == null) { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { addView(view); //note2 } else { addView(view, 0); } } else { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { addDisappearingView(view); } else { addDisappearingView(view, 0); } } measureChildWithMargins(view, 0, 0); //會調用childView.measure方法 ...... layoutDecorated(view, left + params.leftMargin, top + params.topMargin, right - params.rightMargin, bottom - params.bottomMargin); //會調用childView.layout方法 if (params.isItemRemoved() || params.isItemChanged()) { result.mIgnoreConsumed = true; } result.mFocusable = view.isFocusable(); } next()@ [email protected] List首先從自身的mScrapList集合中獲取一個View,命中則直接返回 利用recycler的getViewForPosition獲取一個View,該部分我們放到後面講 addView()@LayoutManager.calss 將View添加到RecyclerView中 scrollHorizontallyBy()@LayoutManager.calssmScrapList = null; View next(RecyclerView.Recycler recycler) { if (mScrapList != null) { return nextViewFromScrapList(); //note1 } final View view = recycler.getViewForPosition(mCurrentPosition); mCurrentPosition += mItemDirection; return view; }
public int scrollVerticallyBy(int dy, Recycler recycler, State state) { return 0; } scrollVerticallyBy()@LinearLayoutManager.calss public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { if (mOrientation == HORIZONTAL) { return 0; } return scrollBy(dy, recycler, state); //該方法在前面scrollHorizontallyBy()@LayoutManager.calss已經介紹過了 }LayoutManager負責測量和擺放RecyclerView中itemView,利用ChildHelper mChildHelper管理LayoutManager使用的ItemView,利用Recycler獲取回收View。簡單講就是從Recycler中取數據然後進行顯示。 上面分析完了Adapter.class和LayoutManager.class,接著我們分析RecyclerView中非常重要的資源回收部分,這部分的操作實體是Recycler.class,下面我們需要重點分析的方法:
屏幕內緩存 private ArrayListclearScrap()@Recycler.classmChangedScrap = null; final ArrayList mAttachedScrap = new ArrayList<>(); private final List mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); 屏幕外緩存 final ArrayList mCachedViews = new ArrayList ();//存儲最近剛被RecyclerView拋棄的ViewHolder,最多存2個 private static final int DEFAULT_CACHE_SIZE = 2; private int mViewCacheMax = DEFAULT_CACHE_SIZE; private RecycledViewPool mRecyclerPool = new RecycledViewPool() ; //裡面會給每個類型的ViewHolder建立一個集合,每個這樣的集合存儲的元素最多5個。 private ViewCacheExtension mViewCacheExtension;
void clearScrap() { mAttachedScrap.clear(); if (mChangedScrap != null) { mChangedScrap.clear(); } }getScrapViewAt()@Recycler.class
View getScrapViewAt(int index) { return mAttachedScrap.get(index).itemView; }clear()@Recycler.class
public void clear() { mAttachedScrap.clear(); recycleAndClearCachedViews(); } recycleAndClearCachedViews()@Recycler.class void recycleAndClearCachedViews() { final int count = mCachedViews.size(); for (int i = count - 1; i >= 0; i--) { recycleCachedViewAt(i); } mCachedViews.clear(); } recycleCachedViewAt()@Recycler.class void recycleCachedViewAt(int cachedViewIndex) { ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); addViewHolderToRecycledViewPool(viewHolder); mCachedViews.remove(cachedViewIndex); } addViewHolderToRecycledViewPool()@Recycler.class void addViewHolderToRecycledViewPool(ViewHolder holder) { ...... holder.mOwnerRecyclerView = null; getRecycledViewPool().putRecycledView(holder); //等於mRecyclerPool.putRecycledView(holder) }
private SparseArray> mScrap = new SparseArray>(); private SparseIntArray mMaxScrap = new SparseIntArray(); //存儲每個類型對應的能夠緩存的最大ViewHolder數量 private int mAttachCount = 0; private static final int DEFAULT_MAX_SCRAP = 5; //默認每個類型能夠5個ViewHolder public void putRecycledView(ViewHolder scrap) { final int viewType = scrap.getItemViewType(); //獲取ViewHolder的類型 final ArrayList scrapHeap = getScrapHeapForType(viewType); //從mScrap集合中獲取viewType類型的集合 if (mMaxScrap.get(viewType) <= scrapHeap.size()) { //緩存隊列已滿,直接返回 return; } scrap.resetInternal(); scrapHeap.add(scrap); } private ArrayList小結:將ArrayListgetScrapHeapForType(int viewType) { ArrayList scrap = mScrap.get(viewType); if (scrap == null) { //mScrap沒有當前類型的集合,創建一個集合添加進隊列 scrap = new ArrayList<>(); mScrap.put(viewType, scrap); if (mMaxScrap.indexOfKey(viewType) < 0) { //如果mMaxScrap中沒有當前類型,存入 mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP); } } return scrap; } public void setMaxRecycledViews(int viewType, int max) { mMaxScrap.put(viewType, max); final ArrayList scrapHeap = mScrap.get(viewType); if (scrapHeap != null) { while (scrapHeap.size() > max) { scrapHeap.remove(scrapHeap.size() - 1); } } }
public void recycleView(View view) { ViewHolder holder = getChildViewHolderInt(view); if (holder.isTmpDetached()) { removeDetachedView(view, false); } if (holder.isScrap()) { holder.unScrap(); } else if (holder.wasReturnedFromScrap()){ holder.clearReturnedFromScrapFlag(); } recycleViewHolderInternal(holder); } recycleViewHolderInternal()@Recycler.class void recycleViewHolderInternal(ViewHolder holder) { if (holder.isScrap() || holder.itemView.getParent() != null) { throw new IllegalArgumentException(...); } if (holder.isTmpDetached()) { throw new IllegalArgumentException("Tmp detached view should be removed " ...); } if (holder.shouldIgnore()) { throw new IllegalArgumentException("Trying to recycle an ignored view ...."); } ...... boolean cached = false; boolean recycled = false; if (forceRecycle || holder.isRecyclable()) { if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE)) { final int cachedViewSize = mCachedViews.size(); if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) { recycleCachedViewAt(0); } if (cachedViewSize < mViewCacheMax) { mCachedViews.add(holder); cached = true; } } if (!cached) { addViewHolderToRecycledViewPool(holder); recycled = true; } } ..... } void recycleCachedViewAt(int cachedViewIndex) { ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); addViewHolderToRecycledViewPool(viewHolder); //clear()@Recycler.class中已經介紹過,就是把數據存入到池中 mCachedViews.remove(cachedViewIndex); //當前位置的ViewHolder從Cache中移除出去 }小結:recycleView()方法會將參數存入到Recycler的 mCachedViews集合中。該集合默認只能存2個ViewHolder,如果當前集合中已經存有兩個ViewHolder,則在將參數ViewHolder存入到集合前,會將0位置ViewHolder放入到回收池中。 getViewForPosition()@Recycler.class
public View getViewForPosition(int position) { return getViewForPosition(position, false); } getViewForPosition()@Recycler.class View getViewForPosition(int position, boolean dryRun) { ...... boolean fromScrap = false; ViewHolder holder = null; // 0) If there is a changed scrap, try to find from there if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); //note1 fromScrap = holder != null; } // 1) Find from scrap by position if (holder == null) { holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun); //note2 if (holder != null) { if (!validateViewHolderForOffsetPosition(holder)) { //判斷得到的ViewHolder位置信息是否正確,不正確回收它 if (!dryRun) { holder.addFlags(ViewHolder.FLAG_INVALID); .... //使得當前View及其子View和Recycler脫離 recycleViewHolderInternal(holder);//該方法在 recycleView()@Recycler.class中已經介紹過 } //end of if (!dryRun) holder = null; }// end of if (!validateViewHolderForOffsetPosition(holder)) else { fromScrap = true; } } // end of if (holder != null) } // end of if (holder == null) if (holder == null) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); ...... final int type = mAdapter.getItemViewType(offsetPosition); //獲取該位置的ViewHolder所屬類型 // 2) Find from scrap via stable ids, if exists if (mAdapter.hasStableIds()) { holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); //note3 if (holder != null) { holder.mPosition = offsetPosition; fromScrap = true; } } if (holder == null && mViewCacheExtension != null) { // We are NOT sending the offsetPosition because LayoutManager does not // know it. final View view = mViewCacheExtension .getViewForPositionAndType(this, position, type); //note4 if (view != null) { holder = getChildViewHolder(view); if (holder == null) { throw ....} ..... } } if (holder == null) { // fallback to recycler holder = getRecycledViewPool().getRecycledView(type);//note5 if (holder != null) { holder.resetInternal(); ....... } } if (holder == null) { holder = mAdapter.createViewHolder(RecyclerView.this, type); //note6 } } //動畫相關的初始化設置 ...... if (mState.isPreLayout() && holder.isBound()) { holder.mPreLayoutPosition = position; } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); holder.mOwnerRecyclerView = RecyclerView.this; mAdapter.bindViewHolder(holder, offsetPosition);//note7 if (mState.isPreLayout()) { holder.mPreLayoutPosition = position; } } //ViewHolder的一些初始化設置 ....... return holder.itemView; }1、遍歷集合mChangedScrap中的所有ViewHolder,並根據holder.getLayoutPosition() == position是否為真判斷是否是預期的ViewHolder 2、遍歷集合mAttachedScrap中所有的ViewHolder,並根據holder.getLayoutPosition() == position是否為真判斷是否是預期的ViewHolder 3、根據id和type在mAttachedScrap和mCachedViews中嘗試獲取ViewHolder 4、根據id和type在mViewCacheExtension 中嘗試獲取view 5、根據type在RecycledViewPool中嘗試獲取ViewHolder 6、利用Adapter.createViewHolder(Recyclerview, Type)方法獲取一個ViewHolder 7、調用Adapter的bindViewholder進行一次綁定 quickRecycleScrapView()@Recycler.class
void quickRecycleScrapView(View view) { final ViewHolder holder = getChildViewHolderInt(view); holder.mScrapContainer = null; holder.mInChangeScrap = false; holder.clearReturnedFromScrapFlag(); recycleViewHolderInternal(holder); //該方法在 recycleView()@Recycler.class中已經介紹過 }
private ArrayListRecyclerView的RecycledViewPool采用SparseArray+ArrayList的形式:[] mScrapViews; public void setViewTypeCount(int viewTypeCount) { if (viewTypeCount < 1) { thow....} ArrayList[] scrapViews = new ArrayList[viewTypeCount]; for (int i = 0; i < viewTypeCount; i++) { scrapViews[i] = new ArrayList(); } mViewTypeCount = viewTypeCount; mCurrentScrap = scrapViews[0]; mScrapViews = scrapViews; }
Android快速入門 1. 搭建開發環境>解壓壓縮文件,得到:①Android SDK (類似於JDK)② Eclipse ③ADT>配置兩個pat
今天我將分享由BiliBili開源的Android彈幕框架(DanmakuFlameMaster)的學習經驗。我是將整個框架以model的形式引入項目中的,這樣更方便的觀
大家都發過微信小視頻到朋友圈,有時候,想看看以前自己發的小視頻,但是發現朋友圈的動態已經刪除了,那麼這樣怎麼去找回微信小視頻呢?微信小視頻保存在哪裡呢?下面
做Android應用中,最缺少不了的就是自定義Dialog,對於系統默認提供的Dialog樣式,一般都不復合我們應用的樣式。自定義Dialog需要3步驟即可:1、主要的重