摘要:五个组件分别负责不同的功能,组合成为功能强大拓展性强的。对于其中的每一个进行标记为其插入为为脏,也就是表示不为空。在中根据获取到的进行相应的测量。用来保存和记录的平均创建实践,平均绑定时间。从一级缓存中进行查找。
前言
作为一个Android开发,RecyclerView一定是不陌生的,其优秀的代码设计和丰富的功能实现,可以帮助我们迅速的实现我们日常的一些业务需求,同时其内部的缓存设计也很好的提升了我们的App流畅度。但是很多时候,RecyclerView默认的实现并不能够充分的满足我们的需求,对于一些复杂的视觉效果的实现上,还需要我们在其基础上进行一些自定义。最近在做几个与RecyclerView相关的需求,借此机会来对于RecyclerView进行进一步的学习。
RecyclerView的功能组件与实践
RecyclerView源码剖析
RecyclerView特性分析
在通过这几个部分对于RecyclerView的学习之后,除了对RecyclerView有了进一步的了解之后,对于Android中的其它View的学习和自定义View的实现问题也会有更深刻理解。
RecyclerView 概述RecyclerView由layoutManager,Adapter,ItemAnimator,ItemDecoration,ViewHolder五大核心组件。五个组件分别负责不同的功能,组合成为功能强大拓展性强的RecyclerView。
Adapter 负责数据和视图的绑定,LayoutManager负责测量和布局, ViewHolder 是视图的载体,ItemAnimator来负责Item View的动画(包括移除,增加,改变等),ItemDecoration负责Item View的间距控制和装饰。
Adapter 和 ViewHolder以下是一个简单的Adapter和ViewHolder创建实例
public class DataAdapter extends RecyclerView.Adapter{ private List images; public DataAdapter(List 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的方法实现。
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来进行处理,会通过队列的方式来维护一系列的更新事件,然后
Adapter状态回调
此外在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
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。
ArrayListmItemDecorations = 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
Recycler是用来负责管理废弃的或者分离的View来重新使用,一个废弃的View是还在其父View RecyclerView上,但是已经被标记为删除或者复用的,Recycler最常用的一个用法是LayoutManager从Adapter的数据集中通过给定的位置来获取View,如果这个View将被重用,将会被认为是dirty,adapter将会要求重新为其绑定数据,如果不是,这个View将会被Layoutmanager迅速的再次利用,干净的View不需要再通过重新的测量。直接布局。
ArrayListmAttachedScrap = new ArrayList<>() ArrayList mChangedScrap = null; ArrayList mCachedViews = new ArrayList (); List mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
RecycledViewPool mRecyclerPool; ViewCacheExtension mViewCacheExtension;
RecycledViewPool
RecycledViewPool 可以让我们在多个RecyclerView之间共享View,如果我们想跨多个RecyclerView进行View的回收操作,我们可以
通过一个RecycledViewPool实例,为我们的RecyclerView通过setRecycledViewPool方法设置RecycledViewPool,如果我们不设置,RecyclerView默认会提供一个。
static class ScrapData { ArrayListmScrapHeap = new ArrayList<>(); int mMaxScrap = DEFAULT_MAX_SCRAP; long mCreateRunningAverageNs = 0; long mBindRunningAverageNs = 0; } SparseArray mScrap = new SparseArray<>();
ScrapData 用来保存ViewHolder和记录ViewHolderd的平均创建实践,平均绑定时间。
为每一种ViewType设置最大缓存数量
public void setMaxRecycledViews(int viewType, int max) { ScrapData scrapData = getScrapDataForType(viewType); scrapData.mMaxScrap = max; final ArrayListscrapHeap = 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 ArrayListscrapHeap = 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,开发者可以自己实现该方法,来使其成为一级缓存。
获取一个ViewHolder
如果RecyclerView有做预先布局,这个时候,我们可以从变化的ViewHolder的列表中去查找相应的ViewHolder,看是否可以复用。
从changedScrapView 列表中查找ViewHolder
if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder != null; }
从attach的ViewHolder中或者隐藏的孩子View或者缓存中获取相应的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; } }
从已经不可见但是未被移除的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的一些回收相关的方法会被回调。
实践RecyclerView Item滑动居中实现
通过对onFling和onScroll的事件进行控制,每次滚动之后,计算当前应该处于中间的View,然后计算其距离,让其进行滚动。同时对于View的滚动可以自己设置滑动控制来控制其滑动的长度。
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里面针对几个版本有不同的实现, 原理上还是根据版本判断, 有时甚至还要判断传入参数的类型. 但是要注意的是, ViewCompat仅仅让你调用不崩溃, 并不保证你调用的结果在不同版本的机器上一致。
计算中心位置的Item
计算中心位置和滚动的方向来控制其下一个要进入到中心的位置。这里我们要对用户的每一次的滑动进行监听,这里要监听的事件有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代码的分析,后续将会针对一些细节进行进一步的完善。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/14823.html
摘要:很多开发团队也越来越认识到,自动化测试和持续部署可帮助开发团队提高迭代效率和质量。且整款的全部工作都是作者一个人完成的。从开始学习系列之进阶来自分享。 从 iOS 7 翻天覆地的全新设计,iOS 8 中 Size Classes 的出现,应用扩展,以及 Cloud Kit 的加入,iOS 9 的分屏多任务特性,今年的 WWDC iOS 10 SDK 又有哪些新的特性呢? 来看看喵神 @...
摘要:应用继续瘦身,以及一些注意事项掘金自上次对应用瘦身过后,经历的若干功能的迭代,很快的,安装包大小又到了,老大说要控制在之内,于是便开始了新一轮的瘦身之旅。 2017 腾讯实习生 Android 客户端开发面试总结 - Android - 掘金先做个自我介绍,本人大三狗一枚,就读的是广州一个普通的一本大学 (非 985、211),专业是比较尴尬的电子商务 (非计算机学院,连 C 的课程都...
摘要:对下半年所分享的文章进行整理,上半年总结的篇好文请点击这里,很多读者当时忘记了收藏,以致于查找一篇历史文章很费劲,因此在这里顺便做下记录。目前就分下下面几个大类,没有更多细分,已基本可以查找了。 对下半年所分享的文章进行整理,上半年总结的 98 篇好文请点击这里,很多读者当时忘记了收藏,以致于查找一篇历史文章很费劲,因此在这里顺便做下记录。目前就分下下面几个大类,没有更多细分,已基本可...
摘要:与老前辈使用攻略刷新篇掘金小序继使用攻略助力篇之后,一直没有更新上下拉刷新的功能实现,主要还是受限于个人现有的技术实力,总觉得没有经过实际打磨的,就不敢有上场的自信。 DrawerLayout 和 NavigationView 使用详解 - Android - 掘金Android Material Design Library 推出了很长时间,越来越多的APP使用了符合Library ...
这是一个系列,我们将其命名为android最佳实践,如果你还没有看之前的文章: Android最佳实践(一) android最佳实践(二) android最佳实践(四) android最佳实践(五) Android最佳实践(六)之扫描二维码模块 现阶段,我们创建了最简单的Android项目,现在在此公布github链接https://github.com/neuyu/android-best-pr...
阅读 3267·2021-09-08 09:36
阅读 2268·2019-08-30 15:54
阅读 2159·2019-08-30 15:54
阅读 1649·2019-08-30 15:44
阅读 2256·2019-08-26 14:04
阅读 2290·2019-08-26 14:01
阅读 2731·2019-08-26 13:58
阅读 1038·2019-08-26 13:47