源码分析:onAttach, onMeasure, onLayout, onDraw 的顺序。

从前文《源码解析:dialog, popupwindow, 和activity 的第一个view是怎么来的?》中知道了activity第一个view或者说根view或者说mDecorView 其实就是一个FrameLayout,以及是在系统handleResume的时候加入到系统windowManager中的,并由framework中的ViewRootImpl
接管,通过ViewRootImpl.setView() 开始整个显示过程的。这次着重梳理一下view的显示过程(onAttach, onMeasure, onLayout, onDraw )在源码中的过程。

从ViewRootImpl.setview 开始。

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

                <span style="color:#ff0000;">requestLayout();</span>
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    res = sWindowSession.add(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mAttachInfo.mContentInsets,
                            mInputChannel);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

在第一次赋值mView的时候,会调用ViewRootImpl.requestLayout();

    public void requestLayout() {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }

进而scheduleTraversals();

    public void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;

            //noinspection ConstantConditions
            if (ViewDebug.DEBUG_LATENCY && mLastTraversalFinishedTimeNanos != 0) {
                final long now = System.nanoTime();
                Log.d(TAG, "Latency: Scheduled traversal, it has been "
                        + ((now - mLastTraversalFinishedTimeNanos) * 0.000001f)
                        + "ms since the last traversal finished.");
            }

            sendEmptyMessage(DO_TRAVERSAL);
        }
    }

进而在handleMessage() 中

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
        case DO_TRAVERSAL:

            performTraversals();
<span style="white-space:pre">	</span>}

进而就是performTraversals(),也就是本次分析的重点。

这个函数比较长,不适合把全部函数代码都 贴上来。就分段叙述。

1. 函数刚开始的部分,初始化了一些后面会用到的变量和标志位。

        final View host = mView;
        WindowManager.LayoutParams lp = mWindowAttributes;
        final View.AttachInfo attachInfo = mAttachInfo;
        CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get();
        Rect frame = mWinFrame;
        mTraversalScheduled = false;
        mWillDrawSoon = true;
        boolean windowSizeMayChange = false;
        boolean fullRedrawNeeded = mFullRedrawNeeded;
        boolean newSurface = false;
        boolean surfaceChanged = false;

2.  接下来,是一个重要的判断, 如果是初次执行,则调用host.dispatchAttachedToWindow(attachInfo, 0);

        if (mFirst) {
            // 略去一大堆赋值
            mLastConfiguration.setTo(host.getResources().getConfiguration());
            host.dispatchAttachedToWindow(attachInfo, 0);
            //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn);

            host.fitSystemWindows(mAttachInfo.mContentInsets);

        } else {
            desiredWindowWidth = frame.width();
            desiredWindowHeight = frame.height();
            if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
                if (DEBUG_ORIENTATION) Log.v(TAG,
                        "View " + host + " resized to: " + frame);
                fullRedrawNeeded = true;
                mLayoutRequested = true;
                windowSizeMayChange = true;
            }
        }

在dispatchAttachedToWindow()中重点处理了三件事:

2.1. onAttachedToWindow();

2.2. listener.onViewAttachedToWindow(this);

2.3 onWindowVisibilityChanged(vis);

    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        //System.out.println("Attached! " + this);
        mAttachInfo = info;
        mWindowAttachCount++;
        onAttachedToWindow();

        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                mOnAttachStateChangeListeners;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewAttachedToWindow(this);
            }
        }

        int vis = info.mWindowVisibility;
        if (vis != GONE) {
            onWindowVisibilityChanged(vis);
        }
    }

在这里onAttachedToWindow() 的注释带来了一个问题:仅保证会在onDraw 前调用,而不保证在onMeasure 之前或者之后调用 onAttachedToWindow。

    /**
     * This is called when the view is attached to a window.  At this point it
     * has a Surface and will start drawing.  Note that this function is
     * guaranteed to be called before {@link #onDraw(android.graphics.Canvas)},
     * however it may be called any time before the first onDraw -- including
     * before or after {@link #onMeasure(int, int)}.
     *
     * @see #onDetachedFromWindow()
     */
    protected void onAttachedToWindow() {
    protected void onAttachedToWindow() {
        // Order is important here: LayoutDirection MUST be resolved before Padding
        // and TextDirection
        resolveLayoutDirectionIfNeeded();
        resolvePadding();
        resolveTextDirection();
        if (isFocused()) {
            InputMethodManager imm = InputMethodManager.peekInstance();
            imm.focusIn(this);
        }
    }

3 fitSystemWindows(), 如果是系统window 则使用padding的值计算一下insets,并开始android.view.View.requestLayout()

    protected boolean fitSystemWindows(Rect insets) {
        if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
            mPaddingLeft = insets.left;
            mPaddingTop = insets.top;
            mPaddingRight = insets.right;
            mPaddingBottom = insets.bottom;
            requestLayout();
            return true;
        }
        return false;
    }

在android.view.View.requestLayout()中,就是简单的一层一层向上检查parent是否存在,若存在调用parent的requestLayout();

    /**
     * Call this when something has changed which has invalidated the
     * layout of this view. This will schedule a layout pass of the view
     * tree.
     */
    public void requestLayout() {
        if (ViewDebug.TRACE_HIERARCHY) {
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);
        }

        mPrivateFlags |= FORCE_LAYOUT;
        mPrivateFlags |= INVALIDATED;

        if (mParent != null) {
            if (mLayoutParams != null) {
                mLayoutParams.resolveWithDirection(getResolvedLayoutDirection());
            }
            if (!mParent.isLayoutRequested()) {
                mParent.requestLayout();
            }
        }
    }

但是每层调用完成,并不是立即执行layout操作,而是通过赋值标志位mPrivateFlags |= FORCE_LAYOUT;,来标识一下而已。真正的layout过程在后面。

4. 有一段代码要说一下。

        if (mLayoutRequested && !mStopped) {
            // Execute enqueued actions on every layout in case a view that was detached
            // enqueued an action after being detached
            getRunQueue().executeActions(attachInfo.mHandler);

RunQueue 是在handler 没有初始化的时候用来处理事件的消息队列。 把给ViewRootImpl post的事件 类型是runnable , 等到handler 构造好后,再发给handler 处理。不是本文的重点,就简单提一下。

5 接下来是measure 的部分

            boolean goodMeasure = false;
            if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
                // On large screens, we don't want to allow dialogs to just
                // stretch to fill the entire width of the screen to display
                // one line of text.  First try doing the layout at a smaller
                // size to see if it will fit.
                final DisplayMetrics packageMetrics = res.getDisplayMetrics();
                res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
                int baseSize = 0;
                if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                    baseSize = (int)mTmpValue.getDimension(packageMetrics);
                }
                if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": baseSize=" + baseSize);
                if (baseSize != 0 && desiredWindowWidth > baseSize) {
                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                    host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                    if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured ("
                            + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                        goodMeasure = true;
                    } else {
                        // Didn't fit in that size... try expanding a bit.
                        baseSize = (baseSize+desiredWindowWidth)/2;
                        if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": next baseSize="
                                + baseSize);
                        childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                        host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                        if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured ("
                                + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
                        if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                            if (DEBUG_DIALOG) Log.v(TAG, "Good!");
                            goodMeasure = true;
                        }
                    }
                }
            }
            if (!goodMeasure) {
                childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                    windowSizeMayChange = true;
                }
            }

这段代码基本就是在某些情况下 用特定的参数来measure , 另外一些情况下,用另外一些值来measure.

都是调用的host.measure(childWidthMeasureSpec, childHeightMeasureSpec); 只是情况不同,使用的参数不同。

计算值的这部分参见前文《源码分析:LayoutParams的wrap_content, match_parent, 和具体值

然后就是调用measure()方法

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {

            // first clears the measured dimension flag
            mPrivateFlags &= ~MEASURED_DIMENSION_SET;

            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
            }

            // measure ourselves, this should set the measured dimension flag back
            onMeasure(widthMeasureSpec, heightMeasureSpec);

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
    }

在一些判断和标志位mPrivateFlags 赋值后,调用onMeasure() 方法。 onMeasure() 的讨论也请移步前文《源码分析:LayoutParams的wrap_content, match_parent, 和具体值

在方法的最后给标志位置位

mPrivateFlags |= LAYOUT_REQUIRED;

6 接着一大堆复杂的逻辑和赋值之后,调用了relayoutWindow() 这个还不太明白是怎么回事 // TODO

                relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);

7 接着又是一堆逻辑和赋值,并在某种情况下还调用了host.measure()。算是measure 完成了

8. 然后是layout 的部分。

        final boolean didLayout = mLayoutRequested && !mStopped;
        boolean triggerGlobalLayoutListener = didLayout
                || attachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            mLayoutRequested = false;
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

在layout() 中,先setFrame,然后判断状态标志位,进而回调onLayout(); 也就是自定义的部分。

    public void layout(int l, int t, int r, int b) {
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        boolean changed = setFrame(l, t, r, b);
        if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
            }

            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~LAYOUT_REQUIRED;

            if (mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }
        mPrivateFlags &= ~FORCE_LAYOUT;
    }

在setFrame()中,判断新的位置和旧的位置是否一致。

    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        if (DBG) {
            Log.d("View", this + " View.setFrame(" + left + "," + top + ","
                    + right + "," + bottom + ")");
        }

        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;

            // Remember our drawn bit
            int drawn = mPrivateFlags & DRAWN;

            int oldWidth = mRight - mLeft;
            int oldHeight = mBottom - mTop;
            int newWidth = right - left;
            int newHeight = bottom - top;
            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

            // Invalidate our old position
            invalidate(sizeChanged);

            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;

            mPrivateFlags |= HAS_BOUNDS;

            if (sizeChanged) {
                if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
                    // A change in dimension means an auto-centered pivot point changes, too
                    if (mTransformationInfo != null) {
                        mTransformationInfo.mMatrixDirty = true;
                    }
                }
                onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
            }

            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                // If we are visible, force the DRAWN bit to on so that
                // this invalidate will go through (at least to our parent).
                // This is because someone may have invalidated this view
                // before this call to setFrame came in, thereby clearing
                // the DRAWN bit.
                mPrivateFlags |= DRAWN;
                invalidate(sizeChanged);
                // parent display list may need to be recreated based on a change in the bounds
                // of any child
                invalidateParentCaches();
            }

            // Reset drawn bit to original value (invalidate turns it off)
            mPrivateFlags |= drawn;

            mBackgroundSizeChanged = true;
        }
        return changed;
    }

如果不一致,则调用invalidate();

    void invalidate(boolean invalidateCache) {
<span style="white-space:pre">		</span>if (p != null && ai != null) {
                final Rect r = ai.mTmpInvalRect;
                r.set(0, 0, mRight - mLeft, mBottom - mTop);
                // Don't call invalidate -- we don't want to internally scroll
                // our own bounds
                p.invalidateChild(this, r);
            }
        }

调用父控件的p.invalidateChild() 来计算并标识 dirty 区域, 区域范围就是子view 所处的区域。

    public void invalidateChild(View child, Rect dirty) {
        checkThread();
        if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
        if (dirty == null) {
            // Fast invalidation for GL-enabled applications; GL must redraw everything
            invalidate();
            return;
        }
        if (mCurScrollY != 0 || mTranslator != null) {
            mTempRect.set(dirty);
            dirty = mTempRect;
            if (mCurScrollY != 0) {
               dirty.offset(0, -mCurScrollY);
            }
            if (mTranslator != null) {
                mTranslator.translateRectInAppWindowToScreen(dirty);
            }
            if (mAttachInfo.mScalingRequired) {
                dirty.inset(-1, -1);
            }
        }
        if (!mDirty.isEmpty() && !mDirty.contains(dirty)) {
            mAttachInfo.mSetIgnoreDirtyState = true;
            mAttachInfo.mIgnoreDirtyState = true;
        }
        mDirty.union(dirty);
        if (!mWillDrawSoon) {
            scheduleTraversals();
        }
    }

如果setframe 的返回值 为true ,则表示 区域的内容发生了变化进而回调onLayout() 方法,和mOnLayoutChangeListeners的onLayoutChange()。

    /**
     * Called from layout when this view should
     * assign a size and position to each of its children.
     *
     * Derived classes with children should override
     * this method and call layout on each of
     * their children.
     * @param changed This is a new size or position for this view
     * @param left Left position, relative to parent
     * @param top Top position, relative to parent
     * @param right Right position, relative to parent
     * @param bottom Bottom position, relative to parent
     */
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

在onLayout() 中, view应该通过调用每一个子view 的layout() 方法,来指定每一个子view 的大小和位置,。

9 接着有个computesInternalInsets的部分不太懂,sWindowSession.setInsets // TODO

        if (computesInternalInsets) {
            // Clear the original insets.
            final ViewTreeObserver.InternalInsetsInfo insets = attachInfo.mGivenInternalInsets;
            insets.reset();

            // Compute new insets in place.
            attachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);

                try {
                    sWindowSession.setInsets(mWindow, insets.mTouchableInsets,
                            contentInsets, visibleInsets, touchableRegion);
                } catch (RemoteException e) {
                }
            }
        }

10, 略去一部分处理过程,有焦点的回调和处理, 输入法的处理等。

11. 进入draw 的部分。

        if (!cancelDraw && !newSurface) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }
            mFullRedrawNeeded = false;

            final long drawStartTime;
            if (ViewDebug.DEBUG_LATENCY) {
                drawStartTime = System.nanoTime();
            }

            draw(fullRedrawNeeded);

            if (ViewDebug.DEBUG_LATENCY) {
                mLastDrawDurationNanos = System.nanoTime() - drawStartTime;
            }

            if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0
                    || mReportNextDraw) {
                if (LOCAL_LOGV) {
                    Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
                }
                mReportNextDraw = false;
                if (mSurfaceHolder != null && mSurface.isValid()) {
                    mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder);
                    SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
                    if (callbacks != null) {
                        for (SurfaceHolder.Callback c : callbacks) {
                            if (c instanceof SurfaceHolder.Callback2) {
                                ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
                                        mSurfaceHolder);
                            }
                        }
                    }
                }
                try {
                    sWindowSession.finishDrawing(mWindow);
                } catch (RemoteException e) {
                }
            }

在draw() 方法中,计算了dirty 的区域, 如果使用了硬件加速的话,进行相应的处理,否则使用canvas  来绘制view。

<span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;">    private void draw(boolean fullRedrawNeeded) {</span>
<span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;">                        mView.draw(canvas);</span>

在android.view.View.draw(Canvas)中,绘制所有的子类

    public void draw(Canvas canvas) {
        if (ViewDebug.TRACE_HIERARCHY) {
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
        }

        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            final Drawable background = mBGDrawable;
            if (background != null) {
                final int scrollX = mScrollX;
                final int scrollY = mScrollY;

                if (mBackgroundSizeChanged) {
                    background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
                    mBackgroundSizeChanged = false;
                }

                if ((scrollX | scrollY) == 0) {
                    background.draw(canvas);
                } else {
                    canvas.translate(scrollX, scrollY);
                    background.draw(canvas);
                    canvas.translate(-scrollX, -scrollY);
                }
            }
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            // Step 6, draw decorations (scrollbars)
            onDrawScrollBars(canvas);

            // we're done...
            return;
        }

注释写的很明白, 在step 1 中绘制背景, 在step 3 中调用自己的ondraw(); 在step 4 中 调用dispatchDraw() 绘制子view 。

最后调用sWindowSession.finishDrawing() 估计是通知底层完成吧,不太懂。//TODO

                try {
                    sWindowSession.finishDrawing(mWindow);
                } catch (RemoteException e) {
                }

draw的部分到此完成

至此,一次完整的绘制流程就走完了,剩下的就是一遍遍的重复这个过程啦。回调过程参见前文《深入分析UI 上层事件处理核心机制 Choreographer

尾巴

尾巴,接下来过一下事件的流程吧。

源码分析:onAttach, onMeasure, onLayout, onDraw 的顺序。

时间: 2024-11-24 05:08:53

源码分析:onAttach, onMeasure, onLayout, onDraw 的顺序。的相关文章

Android视图View绘制流程与源码分析(全)

来源:[工匠若水 http://blog.csdn.net/yanbober] 1 背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原理,记不记得最终分析结果就是下面的关系: 看见没有,如上图中id为content的内容就是整个View树的结构,所以对每个具体View对象的操作,其实就是个递归的实现. 前面<Android触摸屏事件派发机制详解与源码分析一(

ViewPager源码分析

1.问题 由于Android Framework源码很庞大,所以读源码必须带着问题来读!没有问题,创造问题再来读!否则很容易迷失在无数的方法与属性之中,最后无功而返. 那么,关于ViewPager有什么问题呢? 1. setOffsreenPageLimit()方法是如何实现页面缓存的? 2. 在布局文件中,ViewPager布局内部能否添加其他View? 3. 为什么ViewPager初始化时,显示了一个页面却不会触发onPageSelected回调? 问题肯定不止这三个,但是有这三个问题基本

invalidate和requestLayout方法源码分析

invalidate方法源码分析 在之前分析View的绘制流程中,最后都有调用一个叫invalidate的方法,这个方法是啥玩意?我们来看一下View类中invalidate系列方法的源码(ViewGroup没有重写这些方法),如下: /**  * Mark the area defined by dirty as needing to be drawn. dirty代表需要重新绘制的脏的区域  * If the view is visible, onDraw(Canvas) will be c

Android应用层View绘制流程与源码分析

Android应用层View绘制流程与源码分析 1 背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原理,记不记得最终分析结果就是下面的关系: 看见没有,如上图中id为content的内容就是整个View树的结构,所以对每个具体View对象的操作,其实就是个递归的实现. 前面<Android触摸屏事件派发机制详解与源码分析一(View篇)>文章的3-1

SuperSwipeRefreshLayout源码分析

SuperSwipeRefreshLayout源码分析 源码及DEMO SuperSwipeRefreshLayout源码:GitHub 特性 支持下拉刷新和上拉加载更多 非侵入式,对原来的ListView.RecyclerView没有任何影响,用法和SwipeRefreshLayout类似. 可自定义头部View的样式,调用setHeaderView方法即可 可自定义页尾View的样式,调用setFooterView方法即可 支持RecyclerView,ListView,ScrollView

SwipeRefreshLayout 源码分析

## SwipeRefreshLayout 源码分析 > 本文基于 v4 版本 `23.2.0` extends `ViewGroup` implements `NestedScrollingParent` `NestedScrollingChild` ``` java.lang.Object ? android.view.View ? android.view.ViewGroup ? android.support.v4.widget.SwipeRefreshLayout ``` SwipeR

Android 中View的绘制机制源码分析 三

到目前为止,measure过程已经讲解完了,今天开始我们就来学习layout过程,不过在学习layout过程之前,大家有没有发现我换了编辑器,哈哈,终于下定决心从Html编辑器切换为markdown编辑器,这里之所以使用"下定决心"这个词,是因为毕竟Html编辑器使用好几年了,很多习惯都已经养成了,要改变多年的习惯确实不易,相信这也是还有很多人坚持使用Html编辑器的原因.这也反应了一个现象,当人对某一事物非常熟悉时,一旦出现了新的事物想取代老的事物时,人们都有一种抵触的情绪,做技术的

DrawerLayout 源码分析

简介 DrawerLayout充当窗口内容的顶层容器,允许"抽屉"式的控件可以从窗口的一边或者两边垂直边缘拉出 使用 抽屉的位置或者布局可以通过android:layout_gravity子view的属性控制从那边拉出,left/start代表从左边拉出,right/end代表从右侧拉出,需要注意的是只能有一个抽屉控件从窗口的垂直边缘,如果布局中每个垂直窗口有多于一个抽屉控件,将会抛出异常 根布局使用DrawerLayout作为第一个主内容布局,主内容布局宽高设置为match_pare

ListView 源码分析

前言 虽然 RecyclerView 出来很长时间了,ListView 似乎已经过时了,但 ListView 仍然有许多优秀的思想值得学习.讲到 ListView,大家都会想到其复用机制,我这里就不废话说一大堆为什么需要复用等这些废话,直接进入正题. 源码分析 首先,由于 ListView 是个极其复杂的 View,由于本人能力以及篇幅的原因,不可能面面俱到的把整个 ListView 进行分析,那么这里我就分析最普遍的情况,以下就是我使用 ListView 的代码,我们将针对这段代码来进行分析.