做爲一個Android開發,RecyclerView必定是不陌生的,其優秀的代碼設計和豐富的功能實現,能夠幫助咱們迅速的實現咱們平常的一些業務需求,同時其內部的緩存設計也很好的提高了咱們的App流暢度。可是不少時候,RecyclerView默認的實現並不可以充分的知足咱們的需求,對於一些複雜的視覺效果的實現上,還須要咱們在其基礎上進行一些自定義。最近在作幾個與RecyclerView相關的需求,藉此機會來對於RecyclerView進行進一步的學習。數組
在經過這幾個部分對於RecyclerView的學習以後,除了對RecyclerView有了進一步的瞭解以後,對於Android中的其它View的學習和自定義View的實現問題也會有更深入理解。緩存
RecyclerView由layoutManager,Adapter,ItemAnimator,ItemDecoration,ViewHolder五大核心組件。五個組件分別負責不一樣的功能,組合成爲功能強大拓展性強的RecyclerView。ide
Adapter 負責數據和視圖的綁定,LayoutManager負責測量和佈局, ViewHolder 是視圖的載體,ItemAnimator來負責Item View的動畫(包括移除,增長,改變等),ItemDecoration負責Item View的間距控制和裝飾。函數
如下是一個簡單的Adapter和ViewHolder建立實例源碼分析
public class DataAdapter extends RecyclerView.Adapter<DataAdapter.ViewHolder> { private List<Integer> images; public DataAdapter(List<Integer> images) { this.images = images; } @Override public DataAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_image, parent, false)); } @Override public void onBindViewHolder(DataAdapter.ViewHolder holder, int position) { holder.imageView.setImageResource(images.get(position)); holder.imageView.setTag(position); } @Override public int getItemCount() { return images == null ? 0 : images.size(); } static class ViewHolder extends RecyclerView.ViewHolder { ImageView imageView; ViewHolder(View itemView) { super(itemView); imageView = itemView.findViewById(R.id.image); imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(v.getContext(), "clicked:" + v.getTag(), Toast.LENGTH_SHORT).show(); } }); } } }
在Adapter中有三個須要咱們實現的抽象方法。分別爲
onCreateViewHolder,onBindViewHolder,getItemCount,這三個方法分別負責ViewHolder的建立,View和數據的綁定,肯定Item的數量。對於Adapter的源碼分析,咱們從設置部分開始。佈局
public void setAdapter(Adapter adapter) { // bail out if layout is frozen setLayoutFrozen(false); setAdapterInternal(adapter, false, true); requestLayout(); }
setAdapter方法核心實如今setAdapterInternal中,在設置上Adapter以後調用requestLayout來進行從新佈局。
如下是setAdapterInternal的方法實現。post
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) { //將原來的Adapter反註冊 if (mAdapter != null) { mAdapter.unregisterAdapterDataObserver(mObserver); mAdapter.onDetachedFromRecyclerView(this); } if (!compatibleWithPrevious || removeAndRecycleViews) { removeAndRecycleViews(); } mAdapterHelper.reset(); final Adapter oldAdapter = mAdapter; mAdapter = adapter; //將當前的RecyclerView做爲一個觀察者註冊到Adapter if (adapter != null) { adapter.registerAdapterDataObserver(mObserver); adapter.onAttachedToRecyclerView(this); } if (mLayout != null) { mLayout.onAdapterChanged(oldAdapter, mAdapter); } mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); mState.mStructureChanged = true; setDataSetChangedAfterLayout(); }
其調用的removeAndRecyclerViews方法會終止當前的動畫,而後調用LayoutManager的removeAndRecycleAllViews和removeAndRecyclerScrapInt方法,最後調用Recycler的clear方法,主要是來將當前展現的View移除掉,同時對ViewHolder進行回收處理,將其加入到緩存中。學習
RecyclerView在綁定Adapter的時候,RecyclerView會做爲一個觀察者被註冊進來,而後其會被調用,當Adapter其中的一些Item發生變化的時候,就會被回調到觀察者。RecyclerView內部有一個RecyclerViewDataObserver,在setAdapter的時候,會做爲觀察者被註冊進來,當數據集發生變化的時候,會經過一個AdapterHelper來進行處理,會經過隊列的方式來維護一系列的更新事件,而後fetch
此外在Adapter中對於Adapter的一些狀態和對於ViewHolder的一些回收策略的狀態控制,Adapter提供了一系列的回調。動畫
public void onAttachedToRecyclerView(RecyclerView recyclerView) { } public void onDetachedFromRecyclerView(RecyclerView recyclerView) { }
public void onViewRecycled(VH holder) { }
在數據集發生變化,有插入,刪除,變化等操做的時候,在Adapter相應的方法被調用以後,其觀察者將會被調用。
@Override public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { assertNotInLayoutOrScroll(null); if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) { triggerUpdateProcessor(); } }
調用AdapterHelper的onItemRangeChanged的方法,返回true,將會再執行triggerUpdateProcessor
回調到AdapterHelper中,而後調用triggerUpdateProcessor。這個時候會進行RequestLayout或者調用ViewCompat的postAnimation。在AdapterHelper中回調每個觀察者的對應的數據變化的回調。
public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) { mObservable.notifyItemRangeChanged(positionStart, itemCount, payload); } public final void notifyItemInserted(int position) { mObservable.notifyItemRangeInserted(position, 1); } public final void notifyItemMoved(int fromPosition, int toPosition) { mObservable.notifyItemMoved(fromPosition, toPosition); } public final void notifyItemRangeInserted(int positionStart, int itemCount) { mObservable.notifyItemRangeInserted(positionStart, itemCount); }
ItemDecoration的源碼分析從addItemDecoration方法入手。
public void addItemDecoration(ItemDecoration decor, int index) { if (mItemDecorations.isEmpty()) { setWillNotDraw(false); } if (index < 0) { mItemDecorations.add(decor); } else { mItemDecorations.add(index, decor); } markItemDecorInsetsDirty(); requestLayout(); }
在RecyclerView的內部維護了一個ItemDecoration的列表,咱們能夠經過add方法爲其添加多個ItemDecoration。
ArrayList<ItemDecoration> mItemDecorations = new ArrayList<>();
void markItemDecorInsetsDirty() { final int childCount = mChildHelper.getUnfilteredChildCount(); for (int i = 0; i < childCount; i++) { final View child = mChildHelper.getUnfilteredChildAt(i); ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; } mRecycler.markItemDecorInsetsDirty(); }
對於其中的每個child進行標記爲其插入爲爲髒,也就是表示不爲空。而後將Recycler中緩存的View該字段也置爲true。而後調用requestLayout方法進行從新測量,佈局,繪製。
public void onDraw(Canvas c, RecyclerView parent, State state) { onDraw(c, parent); }
onDraw方法可能會繪製在子View的底部,而onDrawOver會繪製在子View的是上面。
public void onDrawOver(Canvas c, RecyclerView parent, State state) { onDrawOver(c, parent); }
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), parent); }
該方法會針對每個View進行回調,傳遞的每個View,咱們能夠根據RecyclerView來得到該View的位置,而後根據位置進行相應的offset的設置。
Rect getItemDecorInsetsForChild(View child) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.mInsetsDirty) { return lp.mDecorInsets; } if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) { // changed/invalid items should not be updated until they are rebound. return lp.mDecorInsets; } final Rect insets = lp.mDecorInsets; insets.set(0, 0, 0, 0); final int decorCount = mItemDecorations.size(); for (int i = 0; i < decorCount; i++) { mTempRect.set(0, 0, 0, 0); mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState); insets.left += mTempRect.left; insets.top += mTempRect.top; insets.right += mTempRect.right; insets.bottom += mTempRect.bottom; } lp.mInsetsDirty = false; return insets; }
獲取每個View的ItemDecoration的上下左右的Offset,而後將這個數據保存在其LayoutParams中。在measureChild
中根據獲取到的offset進行相應的測量。
RecyclerView的draw方法
final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDrawOver(c, this, mState); }
RecyclerView的onDraw方法
final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDraw(c, this, mState); }
在RecyclerView的onDraw方法中調用ItemDecoration的onDraw方法,而後進行
draw方法中會先調用onDraw方法,在draw方法中會進行onDraw方法的調用和dispatchDraw進行子View的繪製,最後調用ItemDecoration的onDrawOver方法,將上層的內容畫在其上面。
public void onDraw(Canvas c, RecyclerView parent, State state) { onDraw(c, parent); } public void onDrawOver(Canvas c, RecyclerView parent, State state) { onDrawOver(c, parent); } public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), parent); }
除了將各功能組件很是好的解耦,方便拓展和自定義以外,Recycler還提供了良好的View緩存機制和Prefetch機制,可讓咱們的App變得更加絲滑高效。
RecyclerView對於View的緩存有分爲三層,第一級是CachedViews,第二級是開發者能夠自定義的一層緩存拓展ViewCacheExtension,第三級緩存是RecyclerPool。當三層緩存緩存都差很少相應的View以後,則會經過Adapter進行View的建立和數據的綁定。
Recycler是用來負責管理廢棄的或者分離的View來從新使用,一個廢棄的View是還在其父View RecyclerView上,可是已經被標記爲刪除或者複用的,Recycler最經常使用的一個用法是LayoutManager從Adapter的數據集中經過給定的位置來獲取View,若是這個View將被重用,將會被認爲是dirty,adapter將會要求從新爲其綁定數據,若是不是,這個View將會被Layoutmanager迅速的再次利用,乾淨的View不須要再經過從新的測量。直接佈局。
ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>() ArrayList<ViewHolder> mChangedScrap = null; ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); List<ViewHolder> mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
RecycledViewPool mRecyclerPool; ViewCacheExtension mViewCacheExtension;
RecycledViewPool 可讓咱們在多個RecyclerView之間共享View,若是咱們想跨多個RecyclerView進行View的回收操做,咱們能夠
經過一個RecycledViewPool實例,爲咱們的RecyclerView經過setRecycledViewPool方法設置RecycledViewPool,若是咱們不設置,RecyclerView默認會提供一個。
static class ScrapData { ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); int mMaxScrap = DEFAULT_MAX_SCRAP; long mCreateRunningAverageNs = 0; long mBindRunningAverageNs = 0; } SparseArray<ScrapData> mScrap = new SparseArray<>();
ScrapData 用來保存ViewHolder和記錄ViewHolderd的平均建立實踐,平均綁定時間。
爲每一種ViewType設置最大緩存數量
public void setMaxRecycledViews(int viewType, int max) { ScrapData scrapData = getScrapDataForType(viewType); scrapData.mMaxScrap = max; final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap; if (scrapHeap != null) { while (scrapHeap.size() > max) { scrapHeap.remove(scrapHeap.size() - 1); } } }
根據ViewType獲取緩存數據
private ScrapData getScrapDataForType(int viewType) { ScrapData scrapData = mScrap.get(viewType); if (scrapData == null) { scrapData = new ScrapData(); mScrap.put(viewType, scrapData); } return scrapData; }
講ViewHolder加入到ViewType
public void putRecycledView(ViewHolder scrap) { final int viewType = scrap.getItemViewType(); final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap; if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) { return; } if (DEBUG && scrapHeap.contains(scrap)) { throw new IllegalArgumentException("this scrap item already exists"); } scrap.resetInternal(); scrapHeap.add(scrap); }
當Adapter發生變化
void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, boolean compatibleWithPrevious) { if (oldAdapter != null) { detach(); } if (!compatibleWithPrevious && mAttachCount == 0) { clear(); } if (newAdapter != null) { attach(newAdapter); } }
void attach(Adapter adapter) { mAttachCount++; } void detach() { mAttachCount--; }
將其回收到池子之中
void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) { clearNestedRecyclerViewIfNotNested(holder); if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE)) { holder.setFlags(0, ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE); ViewCompat.setAccessibilityDelegate(holder.itemView, null); } if (dispatchRecycled) { dispatchViewRecycled(holder); } //將該ViewHolder具有的RecyclerView置爲null holder.mOwnerRecyclerView = null; getRecycledViewPool().putRecycledView(holder); }
該方法會返回一個已經被detach的View或者是一個scrap,經過這兩個來進行
public View getViewForPosition(int position) { return getViewForPosition(position, false); }
變量 做用 mAttachedScrap 未與RecyclerView分離的ViewHolder列表(即一級緩存) mChangedScrap RecyclerView中須要改變的ViewHolder列表(即一級緩存) mCachedViews RecyclerView的ViewHolder緩存列表(即一級緩存) mViewCacheExtension 用戶設置的RecyclerView的ViewHolder緩存列表擴展(即二級緩存) mRecyclerPool RecyclerView的ViewHolder緩存池(即三級緩存)
ViewCacheExtension中有一個方法,getViewForPositionAndType,開發者能夠本身實現該方法,來使其成爲一級緩存。
若是RecyclerView有作預先佈局,這個時候,咱們能夠從變化的ViewHolder的列表中去查找相應的ViewHolder,看是否能夠複用。
if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder != null; }
for (int i = 0; i < scrapCount; i++) { final ViewHolder holder = mAttachedScrap.get(i); if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) { holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); return holder; } }
從已經不可見可是未被移除的View中根據當前的位置進行查找。
View view = mChildHelper.findHiddenNonRemovedView(position); if (view != null) { // This View is good to be used. We just need to unhide, detach and move to the // scrap list. final ViewHolder vh = getChildViewHolderInt(view); mChildHelper.unhide(view); int layoutIndex = mChildHelper.indexOfChild(view); if (layoutIndex == RecyclerView.NO_POSITION) { throw new IllegalStateException("layout index should not be -1 after " + "unhiding a view:" + vh + exceptionLabel()); } mChildHelper.detachViewFromParent(layoutIndex); scrapView(view); vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); return vh; }
在ChildHelper內部有一個隱藏View的列表,能夠經過AdapterPosition在這個列表中查找相應的View,而後根據View去查找對應的ViewHolder。每個View的LayoutParams中設置了ViewHolder,所以能夠經過View來得到ViewHolder。
final int cacheSize = mCachedViews.size(); for (int i = 0; i < cacheSize; i++) { final ViewHolder holder = mCachedViews.get(i); // invalid view holders may be in cache if adapter has stable ids as they can be // retrieved via getScrapOrCachedViewForId if (!holder.isInvalid() && holder.getLayoutPosition() == position) { if (!dryRun) { mCachedViews.remove(i); } return holder; } }
從一級緩存View中進行查找。
根據ID從scrap或者緩存中進行查找。若是mViewCacheExtension不爲空,也就是開發者有經過ViewCacheExtension作拓展,所以能夠經過該拓展進行查找緩存的View。
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); if (view != null) { holder = getChildViewHolder(view); } }
從RecyclerPool中查找緩存的ViewHolder。
if (holder == null) { // fallback to pool holder = getRecycledViewPool().getRecycledView(type); if (holder != null) { holder.resetInternal(); if (FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt(holder); } } }
holder = mAdapter.createViewHolder(RecyclerView.this, type);
調用Adapter建立出一個ViewHolder,同時記錄下其建立耗時。最終咱們獲得了ViewHolder,這個時候調用BindViewHolder。而後將ViewHolder設置到View的LayoutParams中。
ViewHolder的回收
public void recycleView(View view) { // This public recycle method tries to make view recycle-able since layout manager // intended to recycle this view (e.g. even if it is in scrap or change cache) ViewHolder holder = getChildViewHolderInt(view); if (holder.isTmpDetached()) { removeDetachedView(view, false); } if (holder.isScrap()) { holder.unScrap(); } else if (holder.wasReturnedFromScrap()) { holder.clearReturnedFromScrapFlag(); } recycleViewHolderInternal(holder); }
首先將View從視圖中移除,而後將其從變化的scrap中移除或者當前的attachedScrap中移除。對於其中的一些回收操做,在執行回收的時候,會經過RecyclerListener和Adapter的一些回收相關的方法會被回調。
經過對onFling和onScroll的事件進行控制,每次滾動以後,計算當前應該處於中間的View,而後計算其距離,讓其進行滾動。同時對於View的滾動能夠本身設置滑動控制來控制其滑動的長度。
@Override public boolean startNestedScroll(int axes, int type) { return getScrollingChildHelper().startNestedScroll(axes, type); }
NestedScrollingChildHelper
public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) { if (hasNestedScrollingParent(type)) { // Already in progress return true; } if (isNestedScrollingEnabled()) { ViewParent p = mView.getParent(); View child = mView; while (p != null) { if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) { setNestedScrollingParentForType(type, p); ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type); return true; } if (p instanceof View) { child = (View) p; } p = p.getParent(); } } return false; }
ViewCompat類主要是用來提供兼容性的, 好比我最近看的比較的多的canScrollVertically方法, 在ViewCompat裏面針對幾個版本有不一樣的實現, 原理上仍是根據版本判斷, 有時甚至還要判斷傳入參數的類型. 可是要注意的是, ViewCompat僅僅讓你調用不崩潰, 並不保證你調用的結果在不一樣版本的機器上一致。
計算中心位置和滾動的方向來控制其下一個要進入到中心的位置。這裏咱們要對用戶的每一次的滑動進行監聽,這裏要監聽的事件有onFling和onScroll。這裏咱們來看一下該方法的具體實現如何?
如何使用
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws IllegalStateException { if (mRecyclerView == recyclerView) { return; // nothing to do } if (mRecyclerView != null) { destroyCallbacks(); } mRecyclerView = recyclerView; if (mRecyclerView != null) { setupCallbacks(); snapToTargetExistingView(); } }
在使用的過程當中,首先經過該方法來設置一個RecyclerView進來,若是以前有RecyclerView,要將設置的滾動和Fling的監聽器置空,而後爲新設置的RecyclerView添加監聽器,而後滾動到指定的位置。
void snapToTargetExistingView() { if (mRecyclerView == null) { return; } RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); if (layoutManager == null) { return; } View snapView = findSnapView(layoutManager); if (snapView == null) { return; } int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, snapView); if (snapDistance[0] != 0 || snapDistance[1] != 0) { mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]); } }
根據當前RecyclerView的LayoutManager來找到目標View,而後計算目標View和當前的距離,而後調用RecyclerView的smoothScrollBy方法,將其滾動到指定的位置。
private View findCenterView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) { int childCount = layoutManager.getChildCount(); if (childCount == 0) { return null; } View closestChild = null; final int center; if (layoutManager.getClipToPadding()) { center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; } else { center = helper.getEnd() / 2; } int absClosest = Integer.MAX_VALUE; for (int i = 0; i < childCount; i++) { final View child = layoutManager.getChildAt(i); int childCenter = helper.getDecoratedStart(child) + (helper.getDecoratedMeasurement(child) / 2); int absDistance = Math.abs(childCenter - center); if (absDistance < absClosest) { absClosest = absDistance; closestChild = child; } } return closestChild; }
若是LayoutManager設置了getClipToPadding
,計算當前佈局的中心位置,而後計算每個子View的中心位置,判斷哪個子View到當前的位置最近,記錄下當前這個子View,返回該View。計算當前最近子View須要滾動的距離,這個時候須要實現一個計算距離的函數。
public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) { int[] out = new int[2]; if (layoutManager.canScrollHorizontally()) { out[0] = distanceToCenter(layoutManager, targetView, getHorizontalHelper(layoutManager)); } else { out[0] = 0; } if (layoutManager.canScrollVertically()) { out[1] = distanceToCenter(layoutManager, targetView, getVerticalHelper(layoutManager)); } else { out[1] = 0; } return out; }
經過distanceToCenter方法,咱們能夠來計算出到達中心的位置,將其記錄在數組之中,經過一個二維數組,記錄下X軸須要滑動的距離和Y軸須要滑動的距離。
distanceToCenter,這個距離就是咱們目標View和中心View的距離,經過計算獲得。至此,咱們完成了一次滾動。最開始的時候,咱們爲其設置了滾動和onFLing事件的監聽,這個時候,咱們能夠看一下其中的實現。如何對每一次的滾動作的控制。
public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) { mScrolled = false; snapToTargetExistingView(); ViewPagerLayoutManager viewPagerLayoutManager = ((ViewPagerLayoutManager)recyclerView.getLayoutManager()); int currentPosition = viewPagerLayoutManager.getCurrentPosition(); ViewPagerLayoutManager.OnPageChangeListener onPageChangeListener = viewPagerLayoutManager.onPageChangeListener; if (onPageChangeListener != null) { onPageChangeListener.onPageSelected(currentPosition); } } }
public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (dx != 0 || dy != 0) { mScrolled = true; } }
對於每一次的滾動進行控制處理,經過一個變量來判斷其是否發生過變化,若是在x座標或者y座標上有變化,這個變量將會被置爲true,也就是表示發生過滑動,只有在發生過滑動而後onStateChange變爲靜止的時候,纔會再次觸發一次歸爲的滑動,來將其滑動到指定的位置。而後在此處添加了一個回調將每一次的滾動事件回調出去。
onFling的處理
public boolean onFling(int velocityX, int velocityY) { RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); if (layoutManager == null) { return false; } RecyclerView.Adapter adapter = mRecyclerView.getAdapter(); if (adapter == null) { return false; } int minFlingVelocity = mRecyclerView.getMinFlingVelocity(); return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity) && snapFromFling(layoutManager, velocityX, velocityY); }
若是x大於最小速度或者y大於最小速度,並且在snapFromFling函數也將事件消耗掉了,就返回true,表明onFling的監聽將該事件消耗掉了。
private boolean snapFromFling(@NonNull RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) { if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) { return false; } RecyclerView.SmoothScroller smoothScroller = createScroller(layoutManager); if (smoothScroller == null) { return false; } int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY); if (targetPosition == RecyclerView.NO_POSITION) { return false; } smoothScroller.setTargetPosition(targetPosition); layoutManager.startSmoothScroll(smoothScroller); return true; }
在onFling中根據x,y的速度和LayoutManager來查找目標位置,而後爲smoothScroller設置目標位置,啓動平滑滾動器來進行滑動操做。這裏的平滑滾動器是咱們能夠進行自定義的。
SmoothScroller是一個抽象方法,這裏咱們返回了一個LinearSmoothScroller,咱們對其中的幾個方法進行了從新,來知足咱們的需求。
final boolean forwardDirection = velocityX > 0; if (forwardDirection) { View lastMostChildView = findLastView(layoutManager, getHorizontalHelper(layoutManager)); if (lastMostChildView == null) { return RecyclerView.NO_POSITION; } return layoutManager.getPosition(lastMostChildView); } else { View startMostChildView = findStartView(layoutManager, getHorizontalHelper(layoutManager)); if (startMostChildView == null) { return RecyclerView.NO_POSITION; } return layoutManager.getPosition(startMostChildView); }
這裏首先根據x的正負來判斷滾動的方向,當咱們快速滑動的時候,爲了讓其中的卡片不會出現滾動到前面以後,又滾動回來的問題,若是向前滾動咱們就將最後一個View置爲當前的中心位置,若是向後滾動,咱們就查找最前面的一個View。得到這個View的方式就是經過根據當前View的數目進行遍歷,而後查找的開始座標最小的和開始座標最大的兩個View,而後計算其位置,讓其滾動到中間。爲SmoothScroller設置一個position,而後調用其滾動方法來進行滾動。
針對RecyclerView代碼的分析,後續將會針對一些細節進行進一步的完善。