RecyclerView從認(rèn)識到實踐(1)

前言

作為一個Android開發(fā),RecyclerView一定是不陌生的,其優(yōu)秀的代碼設(shè)計和豐富的功能實現(xiàn),可以幫助我們迅速的實現(xiàn)我們?nèi)粘5囊恍I(yè)務(wù)需求,同時其內(nèi)部的緩存設(shè)計也很好的提升了我們的App流暢度。但是很多時候,RecyclerView默認(rèn)的實現(xiàn)并不能夠充分的滿足我們的需求,對于一些復(fù)雜的視覺效果的實現(xiàn)上,還需要我們在其基礎(chǔ)上進(jìn)行一些自定義。最近在做幾個與RecyclerView相關(guān)的需求,借此機(jī)會來對于RecyclerView進(jìn)行進(jìn)一步的學(xué)習(xí)。

  • RecyclerView的功能組件與實踐
  • RecyclerView源碼剖析
  • RecyclerView特性分析

在通過這幾個部分對于RecyclerView的學(xué)習(xí)之后,除了對RecyclerView有了進(jìn)一步的了解之后,對于Android中的其它View的學(xué)習(xí)和自定義View的實現(xiàn)問題也會有更深刻理解。

RecyclerView 概述

RecyclerView由layoutManager,Adapter,ItemAnimator,ItemDecoration,ViewHolder五大核心組件。五個組件分別負(fù)責(zé)不同的功能,組合成為功能強(qiáng)大拓展性強(qiáng)的RecyclerView。

RecyclerView功能組件

Adapter 負(fù)責(zé)數(shù)據(jù)和視圖的綁定,LayoutManager負(fù)責(zé)測量和布局, ViewHolder 是視圖的載體,ItemAnimator來負(fù)責(zé)Item View的動畫(包括移除,增加,改變等),ItemDecoration負(fù)責(zé)Item View的間距控制和裝飾。

Adapter 和 ViewHolder

以下是一個簡單的Adapter和ViewHolder創(chuàng)建實例

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中有三個需要我們實現(xiàn)的抽象方法。分別為
onCreateViewHolder,onBindViewHolder,getItemCount,這三個方法分別負(fù)責(zé)ViewHolder的創(chuàng)建,View和數(shù)據(jù)的綁定,確定Item的數(shù)量。對于Adapter的源碼分析,我們從設(shè)置部分開始。

public void setAdapter(Adapter adapter) {
    // bail out if layout is frozen
    setLayoutFrozen(false);
    setAdapterInternal(adapter, false, true);
    requestLayout();
}

setAdapter方法核心實現(xiàn)在setAdapterInternal中,在設(shè)置上Adapter之后調(diào)用requestLayout來進(jìn)行重新布局。
以下是setAdapterInternal的方法實現(xiàn)。

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;
    //將當(dāng)前的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();
}

其調(diào)用的removeAndRecyclerViews方法會終止當(dāng)前的動畫,然后調(diào)用LayoutManager的removeAndRecycleAllViews和removeAndRecyclerScrapInt方法,最后調(diào)用Recycler的clear方法,主要是來將當(dāng)前展示的View移除掉,同時對ViewHolder進(jìn)行回收處理,將其加入到緩存中。

RecyclerView在綁定Adapter的時候,RecyclerView會作為一個觀察者被注冊進(jìn)來,然后其會被調(diào)用,當(dāng)Adapter其中的一些Item發(fā)生變化的時候,就會被回調(diào)到觀察者。RecyclerView內(nèi)部有一個RecyclerViewDataObserver,在setAdapter的時候,會作為觀察者被注冊進(jìn)來,當(dāng)數(shù)據(jù)集發(fā)生變化的時候,會通過一個AdapterHelper來進(jìn)行處理,會通過隊列的方式來維護(hù)一系列的更新事件,然后

  • Adapter狀態(tài)回調(diào)

此外在Adapter中對于Adapter的一些狀態(tài)和對于ViewHolder的一些回收策略的狀態(tài)控制,Adapter提供了一系列的回調(diào)。

public void onAttachedToRecyclerView(RecyclerView recyclerView) {
}

public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
}
public void onViewRecycled(VH holder) {
}
  • 數(shù)據(jù)集狀態(tài)變化通知

在數(shù)據(jù)集發(fā)生變化,有插入,刪除,變化等操作的時候,在Adapter相應(yīng)的方法被調(diào)用之后,其觀察者將會被調(diào)用。

  • 對于數(shù)據(jù)變化的具體執(zhí)行。
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
    assertNotInLayoutOrScroll(null);
    if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
        triggerUpdateProcessor();
    }
}

調(diào)用AdapterHelper的onItemRangeChanged的方法,返回true,將會再執(zhí)行triggerUpdateProcessor

回調(diào)到AdapterHelper中,然后調(diào)用triggerUpdateProcessor。這個時候會進(jìn)行RequestLayout或者調(diào)用ViewCompat的postAnimation。在AdapterHelper中回調(diào)每一個觀察者的對應(yīng)的數(shù)據(jù)變化的回調(diào)。

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

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的內(nèi)部維護(hù)了一個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進(jìn)行標(biāo)記為其插入為為臟,也就是表示不為空。然后將Recycler中緩存的View該字段也置為true。然后調(diào)用requestLayout方法進(jìn)行重新測量,布局,繪制。

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進(jìn)行回調(diào),傳遞的每一個View,我們可以根據(jù)RecyclerView來獲得該View的位置,然后根據(jù)位置進(jìn)行相應(yīng)的offset的設(shè)置。

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,然后將這個數(shù)據(jù)保存在其LayoutParams中。在measureChild中根據(jù)獲取到的offset進(jìn)行相應(yīng)的測量。

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方法中調(diào)用ItemDecoration的onDraw方法,然后進(jìn)行

draw方法中會先調(diào)用onDraw方法,在draw方法中會進(jìn)行onDraw方法的調(diào)用和dispatchDraw進(jìn)行子View的繪制,最后調(diào)用ItemDecoration的onDrawOver方法,將上層的內(nèi)容畫在其上面。

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);
}

緩存機(jī)制

除了將各功能組件非常好的解耦,方便拓展和自定義之外,Recycler還提供了良好的View緩存機(jī)制和Prefetch機(jī)制,可以讓我們的App變得更加絲滑高效。

RecyclerView緩存機(jī)制

RecyclerView對于View的緩存有分為三層,第一級是CachedViews,第二級是開發(fā)者可以自定義的一層緩存拓展ViewCacheExtension,第三級緩存是RecyclerPool。當(dāng)三層緩存緩存都差不多相應(yīng)的View之后,則會通過Adapter進(jìn)行View的創(chuàng)建和數(shù)據(jù)的綁定。

  • Recycler

    Recycler是用來負(fù)責(zé)管理廢棄的或者分離的View來重新使用,一個廢棄的View是還在其父View RecyclerView上,但是已經(jīng)被標(biāo)記為刪除或者復(fù)用的,Recycler最常用的一個用法是LayoutManager從Adapter的數(shù)據(jù)集中通過給定的位置來獲取View,如果這個View將被重用,將會被認(rèn)為是dirty,adapter將會要求重新為其綁定數(shù)據(jù),如果不是,這個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

RecycledViewPool 可以讓我們在多個RecyclerView之間共享View,如果我們想跨多個RecyclerView進(jìn)行View的回收操作,我們可以
通過一個RecycledViewPool實例,為我們的RecyclerView通過setRecycledViewPool方法設(shè)置RecycledViewPool,如果我們不設(shè)置,RecyclerView默認(rèn)會提供一個。

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的平均創(chuàng)建實踐,平均綁定時間。

為每一種ViewType設(shè)置最大緩存數(shù)量

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);
      }
  }
}

根據(jù)ViewType獲取緩存數(shù)據(jù)

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);
}

當(dāng)Adapter發(fā)生變化

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);
}

該方法會返回一個已經(jīng)被detach的View或者是一個scrap,通過這兩個來進(jìn)行

 public View getViewForPosition(int position) {
            return getViewForPosition(position, false);
}

變量  作用
mAttachedScrap  未與RecyclerView分離的ViewHolder列表(即一級緩存)
mChangedScrap   RecyclerView中需要改變的ViewHolder列表(即一級緩存)
mCachedViews    RecyclerView的ViewHolder緩存列表(即一級緩存)
mViewCacheExtension 用戶設(shè)置的RecyclerView的ViewHolder緩存列表擴(kuò)展(即二級緩存)
mRecyclerPool   RecyclerView的ViewHolder緩存池(即三級緩存)

ViewCacheExtension中有一個方法,getViewForPositionAndType,開發(fā)者可以自己實現(xiàn)該方法,來使其成為一級緩存。

  • 獲取一個ViewHolder

如果RecyclerView有做預(yù)先布局,這個時候,我們可以從變化的ViewHolder的列表中去查找相應(yīng)的ViewHolder,看是否可以復(fù)用。

  • 從changedScrapView 列表中查找ViewHolder
if (mState.isPreLayout()) {
    holder = getChangedScrapViewForPosition(position);
    fromScrapOrHiddenOrCache = holder != null;
}
  • 從attach的ViewHolder中或者隱藏的孩子View或者緩存中獲取相應(yīng)的ViewHolder
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;
    }
}

從已經(jīng)不可見但是未被移除的View中根據(jù)當(dāng)前的位置進(jìn)行查找。

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內(nèi)部有一個隱藏View的列表,可以通過AdapterPosition在這個列表中查找相應(yīng)的View,然后根據(jù)View去查找對應(yīng)的ViewHolder。每一個View的LayoutParams中設(shè)置了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中進(jìn)行查找。

根據(jù)ID從scrap或者緩存中進(jìn)行查找。如果mViewCacheExtension不為空,也就是開發(fā)者有通過ViewCacheExtension做拓展,因此可以通過該拓展進(jìn)行查找緩存的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);

調(diào)用Adapter創(chuàng)建出一個ViewHolder,同時記錄下其創(chuàng)建耗時。最終我們得到了ViewHolder,這個時候調(diào)用BindViewHolder。然后將ViewHolder設(shè)置到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中移除或者當(dāng)前的attachedScrap中移除。對于其中的一些回收操作,在執(zhí)行回收的時候,會通過RecyclerListener和Adapter的一些回收相關(guān)的方法會被回調(diào)。

實踐

  • RecyclerView Item滑動居中實現(xiàn)

通過對onFling和onScroll的事件進(jìn)行控制,每次滾動之后,計算當(dāng)前應(yīng)該處于中間的View,然后計算其距離,讓其進(jìn)行滾動。同時對于View的滾動可以自己設(shè)置滑動控制來控制其滑動的長度。

  • onTouchEvent處理
@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里面針對幾個版本有不同的實現(xiàn), 原理上還是根據(jù)版本判斷, 有時甚至還要判斷傳入?yún)?shù)的類型. 但是要注意的是, ViewCompat僅僅讓你調(diào)用不崩潰, 并不保證你調(diào)用的結(jié)果在不同版本的機(jī)器上一致。

  • 計算中心位置的Item

計算中心位置和滾動的方向來控制其下一個要進(jìn)入到中心的位置。這里我們要對用戶的每一次的滑動進(jìn)行監(jiān)聽,這里要監(jiān)聽的事件有onFling和onScroll。這里我們來看一下該方法的具體實現(xiàn)如何?

如何使用

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();
    }
}

在使用的過程中,首先通過該方法來設(shè)置一個RecyclerView進(jìn)來,如果之前有RecyclerView,要將設(shè)置的滾動和Fling的監(jiān)聽器置空,然后為新設(shè)置的RecyclerView添加監(jiān)聽器,然后滾動到指定的位置。

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]);
    }
}

根據(jù)當(dāng)前RecyclerView的LayoutManager來找到目標(biāo)View,然后計算目標(biāo)View和當(dāng)前的距離,然后調(diào)用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設(shè)置了getClipToPadding,計算當(dāng)前布局的中心位置,然后計算每一個子View的中心位置,判斷哪一個子View到當(dāng)前的位置最近,記錄下當(dāng)前這個子View,返回該View。計算當(dāng)前最近子View需要滾動的距離,這個時候需要實現(xiàn)一個計算距離的函數(shù)。

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方法,我們可以來計算出到達(dá)中心的位置,將其記錄在數(shù)組之中,通過一個二維數(shù)組,記錄下X軸需要滑動的距離和Y軸需要滑動的距離。

distanceToCenter,這個距離就是我們目標(biāo)View和中心View的距離,通過計算得到。至此,我們完成了一次滾動。最開始的時候,我們?yōu)槠湓O(shè)置了滾動和onFLing事件的監(jiān)聽,這個時候,我們可以看一下其中的實現(xiàn)。如何對每一次的滾動做的控制。

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;
    }
}

對于每一次的滾動進(jìn)行控制處理,通過一個變量來判斷其是否發(fā)生過變化,如果在x坐標(biāo)或者y坐標(biāo)上有變化,這個變量將會被置為true,也就是表示發(fā)生過滑動,只有在發(fā)生過滑動然后onStateChange變?yōu)殪o止的時候,才會再次觸發(fā)一次歸為的滑動,來將其滑動到指定的位置。然后在此處添加了一個回調(diào)將每一次的滾動事件回調(diào)出去。

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函數(shù)也將事件消耗掉了,就返回true,代表onFling的監(jiān)聽將該事件消耗掉了。

    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中根據(jù)x,y的速度和LayoutManager來查找目標(biāo)位置,然后為smoothScroller設(shè)置目標(biāo)位置,啟動平滑滾動器來進(jìn)行滑動操作。這里的平滑滾動器是我們可以進(jìn)行自定義的。
SmoothScroller是一個抽象方法,這里我們返回了一個LinearSmoothScroller,我們對其中的幾個方法進(jìn)行了重新,來滿足我們的需求。

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);
}

這里首先根據(jù)x的正負(fù)來判斷滾動的方向,當(dāng)我們快速滑動的時候,為了讓其中的卡片不會出現(xiàn)滾動到前面之后,又滾動回來的問題,如果向前滾動我們就將最后一個View置為當(dāng)前的中心位置,如果向后滾動,我們就查找最前面的一個View。獲得這個View的方式就是通過根據(jù)當(dāng)前View的數(shù)目進(jìn)行遍歷,然后查找的開始坐標(biāo)最小的和開始坐標(biāo)最大的兩個View,然后計算其位置,讓其滾動到中間。為SmoothScroller設(shè)置一個position,然后調(diào)用其滾動方法來進(jìn)行滾動。

針對RecyclerView代碼的分析,后續(xù)將會針對一些細(xì)節(jié)進(jìn)行進(jìn)一步的完善。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 這篇文章分三個部分,簡單跟大家講一下 RecyclerView 的常用方法與奇葩用法;工作原理與ListView比...
    LucasAdam閱讀 4,700評論 0 27
  • 一、概述 對于RecyclerView的學(xué)習(xí),主要是需要掌握以下幾點: 數(shù)據(jù):Adapter 使用:Recycle...
    澤毛閱讀 7,629評論 1 23
  • 序言 RecyclerView有三大典型的功能,一個是Recycler的緩存機(jī)制,一個LayoutManager的...
    非專業(yè)程序員閱讀 4,405評論 1 10
  • 做一個類似微信朋友圈的小任務(wù),信心滿滿地打算使用ListView控件完成,和小組一討論發(fā)現(xiàn)大家都推薦我用Recyc...
    sunnyaxin閱讀 2,530評論 3 23
  • 剛剛又有人讓我給她參加的一個比賽點贊、投票。數(shù)了一下,這已經(jīng)是她這個學(xué)期第三次因為這種事情找我了。 不知道什么時候...
    幾禾呀閱讀 386評論 0 1

友情鏈接更多精彩內(nèi)容