android view的绘制原理,Android RecyclerView绘制原理

RecyclerView基本使用

12345678910//首先设置RecyclerView的布局管理模式mRecyclerView.setLayoutManager(new LinearLayoutManager(this));mAdapter = new MyAdapter(getData());//设置Item项的UI装饰器mRecyclerView.addItemDecoration(new RecyclerItemDecoration(LinearLayoutManager.VERTICAL));//设置Item项的不同操作的动画mRecyclerView.setItemAnimator(new DefaultItemAnimator());//设置数据开始装配mRecyclerView.setAdapter(mAdapter);RecyclerView原理分析

Adapter数据适配

RecyclerView.Adapter类中有一个很重要的属性:123//Adapter中被观察对象Observaleprivate final AdapterDataObservable mObservable = new AdapterDataObservable();

RecyclerView中也有一个很重要的属性12//数据观察者, AdapterDataObserver实例private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();

当使用recyclerView.setAdapter(data)设置数据时,会调用以下方法使得RecyclerView成为Adapter的观察者(间接):1234567891011private void setAdapterInternal(Adapter adapter,boolean compatibleWithPrevious,boolean removeAndRecycleViews) {......if (adapter != null) {//通过此处添加观察者,此时RecyclerView就会对Adapter中的数据进行观察监听adapter.registerAdapterDataObserver(mObserver);......}......}

通常当我们改变Adapter中的数据源时,一般都会通过调用Adapter.notifyDataSetChanged()方法来刷新列表,我们来看看这个方法的实现,看看Adapter是如何通过这个方法来刷新列表的:123public final void notifyDataSetChanged() {mObservable.notifyChanged();}

我们接着看AdapterDataObservable.notifyChanged()方法实现:123456//mObservers是Observable中的属性,是一个ArrayListpublic void notifyChanged() {for (int i = mObservers.size() - 1; i >= 0; i--) {mObservers.get(i).onChanged();}}

我们再来看RecyclerViewDataObserver.onChanged()方法:12345678@Overridepublic void onChanged() {......//Adapter目前没有待更新的数据if (!mAdapterHelper.hasPendingUpdates()) {requestLayout();}}

看到requestLayout()这个方法,我们就明白了,调用此方法后系统会重新measure, layout, draw,这样列表视图就会被更新。

RecyclerView.onMeasure()

我们来看看RecyclerView的测量方法onMeasure:123456789101112131415161718192021222324@Overrideprotected void onMeasure(int widthSpec, int heightSpec) {......if (mLayout.mAutoMeasure) {final int widthMode = MeasureSpec.getMode(widthSpec);final int heightMode = MeasureSpec.getMode(heightSpec);final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY  && heightMode == MeasureSpec.EXACTLY;//委托给LayoutManager来进行测量mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);if (skipMeasure || mAdapter == null) {return;}if (mState.mLayoutStep == State.STEP_START) {dispatchLayoutStep1();}mLayout.setMeasureSpecs(widthSpec, heightSpec);mState.mIsMeasuring = true;dispatchLayoutStep2();mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);......}}......}

上面onMeasure方法中mLayout变量就是我们上面设置的LinearLayoutManager实例,而LinearLayoutManager的构造函数中给变量mAutoMeasure值设置为true,因此测量时就会执行上面的代码,通过上面的方法我们可以看到此处测量分为两种:

当RecyclerView的宽高设置为match_parent或具体值的时候,skipMeasure=true,此时会只需要测量其自身的宽高就可以知道RecyclerView的大小,这时是onMeasure方法测量结束。

当RecyclerView的宽高设置为wrap_content时,skipMeasure=false,onMeasure会继续执行下面的dispatchLayoutStep2(),其实就是测量RecyclerView的子视图的大小最终确定RecyclerView的实际大小,这种情况真正的测量操作都是在方法dispatchLayoutStep2()里执行的:12345678private void dispatchLayoutStep2() {......mState.mItemCount = mAdapter.getItemCount();// Step 2: Run layoutmState.mInPreLayout = false;mLayout.onLayoutChildren(mRecycler, mState);......}

从这里也可以看出RecyclerView真正的测量是委托给LayoutManager在处理,我们看看LinearLayoutManager的onLayoutChildren方法:12345678910111213141516@Overridepublic void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {......if (mAnchorInfo.mLayoutFromEnd) {...fill(recycler, mLayoutState, state, false);......} else {......fill(recycler, mLayoutState, state, false);......}......layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);......}

很明显可以看到,最终执行了fill()方法:12345678910int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {......LayoutChunkResult layoutChunkResult = new LayoutChunkResult();while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {layoutChunkResult.resetInternal();layoutChunk(recycler, state, layoutState, layoutChunkResult);......}......}

上面的while判断条件中remainingSpace可以理解为当前列表中是否还有多余的位置可用于添加绘制child,而layoutState.hasMore(state)则是判断当前绘制的child索引位置是否在Adapter数据范围内123boolean hasMore(RecyclerView.State state) {return mCurrentPosition >= 0 && mCurrentPosition 

再来看上面的layoutChunk()方法:1234567891011121314151617181920212223242526void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {View view = layoutState.next(recycler);......LayoutParams params = (LayoutParams) view.getLayoutParams();if (layoutState.mScrapList == null) {if (mShouldReverseLayout == (layoutState.mLayoutDirection== LayoutState.LAYOUT_START)) {addView(view);} else {addView(view, 0);}} else {if (mShouldReverseLayout == (layoutState.mLayoutDirection== LayoutState.LAYOUT_START)) {addDisappearingView(view);} else {addDisappearingView(view, 0);}}measureChildWithMargins(view, 0, 0);......// To calculate correct layout position, we subtract margins.layoutDecorated(view, left + params.leftMargin, top + params.topMargin, right - params.rightMargin, bottom -  params.bottomMargin);......}

上面的方法中addView与addDisappearingView最终都是调用的RecyclerView的addView方法,也就是将子child添加到RecyclerView中。

我们再来看看View view = layoutState.next(recycler);这行代码的实现:1234567891011View next(RecyclerView.Recycler recycler) {if (mScrapList != null) {return nextViewFromScrapList();}final View view = recycler.getViewForPosition(mCurrentPosition);//获取某个位置需要展示的ViewmCurrentPosition += mItemDirection;//将当前绘制的child的索引下移一位,配合while循环return view;}

我们看看上面的获取position位置的view是如何获取的:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758View getViewForPosition(int position, boolean dryRun) {......ViewHolder holder = null;// 0) If there is a changed scrap, try to find from thereif (mState.isPreLayout()) {holder = getChangedScrapViewForPosition(position);fromScrap = holder != null;}// 1) Find from scrap by positionif (holder == null) {holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);......}if (holder == null) {final int offsetPosition = mAdapterHelper.findPositionOffset(position);......final int type = mAdapter.getItemViewType(offsetPosition);// 2) Find from scrap via stable ids, if existsif (mAdapter.hasStableIds()) {holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);......}//mViewCacheExtension的缓存是由开发者自己实现来控制ViewHolder的缓存策略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);......}}if (holder == null) { // fallback to recycler......holder = getRecycledViewPool().getRecycledView(type);......}if (holder == null) {holder = mAdapter.createViewHolder(RecyclerView.this, type);......}}......boolean bound = false;if (mState.isPreLayout() && holder.isBound()) {// do not update unless we absolutely have to.holder.mPreLayoutPosition = position;} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {......holder.mOwnerRecyclerView = RecyclerView.this;//此处就是调用Adapter中bindViewHolder方法mAdapter.bindViewHolder(holder, offsetPosition);......}......return holder.itemView;}

将指定位置的View获取得到之后添加到RecyclerView中,紧接着再来看后面执行的measureChildWithMargins方法:12345678910111213public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {final LayoutParams lp = (LayoutParams) child.getLayoutParams();//通过ItemDecorate获取offsetfinal Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);widthUsed += insets.left + insets.right;heightUsed += insets.top + insets.bottom;final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin + widthUsed, lp.width, canScrollHorizontally());final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin + heightUsed, lp.height, canScrollVertically());if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {child.measure(widthSpec, heightSpec);}}

该方法中调用mRecyclerView.getItemDecorInsetsForChild(child);获取child的offset,然后对child重新测量绘制:1234567891011121314Rect getItemDecorInsetsForChild(View child) {......final int decorCount = mItemDecorations.size();for (int i = 0; i 

这个里面的mItemDecorations就是文章开头例子中我通过mRecyclerView.addItemDecoration(new RecyclerItemDecoration(LinearLayoutManager.VERTICAL));添加的Item装饰器1234public void layoutDecorated(View child, int left, int top, int right, int bottom) {final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;child.layout(left + insets.left, top + insets.top, right - insets.right, bottom - insets.bottom);}

可以看到layoutDecorated方法中直接调用了View的layout方法对child视图进行layout布局。

到此RecyclerView列表中Item项视图的measure和layout实际上已经完成。

RecyclerView.onLayout

看完onMeasure方法,再来看看onLayout方法:123456789101112131415161718192021222324@Overrideprotected void onLayout(boolean changed, int l, int t,int r, int b) {TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);dispatchLayout();TraceCompat.endSection();mFirstLayoutComplete = true;}void dispatchLayout() {......mState.mIsMeasuring = false;if (mState.mLayoutStep == State.STEP_START) {......dispatchLayoutStep2();} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()) {......dispatchLayoutStep2();} else {// always make sure we sync them (to ensure mode is exact)mLayout.setExactMeasureSpecsFrom(this);}dispatchLayoutStep3();}

通过dispatchLayout方法可以看到onLayout中又执行了我们前面分析过的dispatchLayoutStep2()方法,在最后又执行了一个dispatchLayoutStep3()方法,我们再来看看这个:12345678910111213141516171819202122232425262728293031323334private void dispatchLayoutStep3() {......if (mState.mRunSimpleAnimations) {for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));......final ItemHolderInfo animationInfo = mItemAnimator.recordPostLayoutInformation(mState, holder);......if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {......if (oldDisappearing && oldChangeViewHolder == holder) {//此处会执行动画// run disappear animation instead of changemViewInfoStore.addToPostLayout(holder, animationInfo);} else {......if (preInfo == null) {handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);} else {//此方法最终调用DefaultItemAnimate的相关动画animateChange(oldChangeViewHolder, holder, preInfo, postInfo, oldDisappearing, newDisappearing);}}} else {mViewInfoStore.addToPostLayout(holder, animationInfo);}}// Step 4: Process view info lists and trigger animationsmViewInfoStore.process(mViewInfoProcessCallback);}......}

上面的方法中调用了ItemAnimation动画类的相关方法

RecyclerView.onDraw123456789@Overridepublic void onDraw(Canvas c) {super.onDraw(c);final int count = mItemDecorations.size();for (int i = 0; i 

可以看到ItemDecoration的onDraw方法是在此处调用12345678910@Overridepublic void draw(Canvas c) {super.draw(c);final int count = mItemDecorations.size();for (int i = 0; i 

这个地方可以看到ItemDecoration的onDrawOver方法是在此处调用

到这里,RecyclerView使用过程中比较常用的几个类(LayoutManager, ItemDecoration, ItemAnimation)的主要作用及使用场景有了个大概的了解。

在RecyclerView中是没有为我们内置Item的单击和长按事件监听接口的,一般为Item设置单击和长按监听都是是直接在Adapter初始化Item视图时,为我们的Item视图直接设置单击监听和长按监听,这种方式与Adapter的耦合度比较高,而且频繁的为view设置监听对象,感觉不太好。其实RecyclerView中为我们提供了一个类OnItemTouchListener通过这个类再结合手势GestureDetector完全可以实现一个耦合度更低复用度更高的单击和长按监听。我们再来看看OnItemTouchListener的实现方式:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869@Overridepublic boolean onTouchEvent(MotionEvent e) {......if (dispatchOnItemTouch(e)) {cancelTouch();return true;}......}private boolean dispatchOnItemTouch(MotionEvent e) {final int action = e.getAction();if (mActiveOnItemTouchListener != null) {if (action == MotionEvent.ACTION_DOWN) {// Stale state from a previous gesture, we're starting a new one. Clear it.mActiveOnItemTouchListener = null;} else {//此处即调用OnItemTouchListener的方法mActiveOnItemTouchListener.onTouchEvent(this, e);if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {// Clean up for the next gesture.mActiveOnItemTouchListener = null;}return true;}}// Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept// as called from onInterceptTouchEvent; skip it.if (action != MotionEvent.ACTION_DOWN) {final int listenerCount = mOnItemTouchListeners.size();for (int i = 0; i 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值