android View绘制源码分析(下)

来源:互联网 发布:淘宝刷单群 编辑:程序博客网 时间:2024/05/21 19:37

Layout的过程

viewGroup会遍历所有子元素并调用 其layout方法,layout方法来确定子元素的位置。viewgroup如下:

protected abstract void onLayout(boolean changed,        int l , int t, int r, int b) ;

需要子类自己实现。看下view的layout:

public void layout(int l, int t , int r, int b) {    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT ) != 0) {        onMeasure(mOldWidthMeasureSpec , mOldHeightMeasureSpec) ;        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;    }    int oldL = mLeft;    int oldT = mTop;    int oldB = mBottom;    int oldR = mRight;    boolean changed = isLayoutModeOptical( mParent) ?            setOpticalFrame(l, t , r, b) : setFrame(l, t , r, b);    if (changed || ( mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED ) {        onLayout(changed, l, t , r, b);        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;        ListenerInfo li = mListenerInfo;        if (li != null && li.mOnLayoutChangeListeners != null) {            ArrayList<OnLayoutChangeListener> listenersCopy =                    (ArrayList<OnLayoutChangeListener>)li. 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 &= ~PFLAG_FORCE_LAYOUT;    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;}

在setFrame中确定了view的四个顶点坐标。mleft等。onLayout view也没有具体实现,要看具体的。以LinearLayout为例:

protected void onLayout(boolean changed, int l , int t, int r, int b) {    if (mOrientation == VERTICAL) {        layoutVertical(l, t, r , b);    } else {        layoutHorizontal(l, t, r , b);    }}

以layoutVertical为例:

void layoutVertical(int left, int top , int right, int bottom) {    final int paddingLeft = mPaddingLeft ;    int childTop ;    int childLeft ;    // Where right end of child should go    final int width = right - left;    int childRight = width - mPaddingRight ;    // Space available for child    int childSpace = width - paddingLeft - mPaddingRight ;    final int count = getVirtualChildCount() ;    final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;    final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;    switch (majorGravity) {       case Gravity.BOTTOM:           // mTotalLength contains the padding already           childTop = mPaddingTop + bottom - top - mTotalLength;           break;           // mTotalLength contains the padding already       case Gravity.CENTER_VERTICAL:           childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;           break;       case Gravity. TOP:       default :           childTop = mPaddingTop;           break;    }    for (int i = 0; i < count ; i++) {        final View child = getVirtualChildAt(i) ;        if (child == null) {            childTop += measureNullChild(i);        } else if (child.getVisibility() != GONE) {            final int childWidth = child.getMeasuredWidth() ;            final int childHeight = child.getMeasuredHeight();            final LinearLayout.LayoutParams lp =                    (LinearLayout.LayoutParams) child.getLayoutParams() ;            int gravity = lp. gravity;            if (gravity < 0) {                gravity = minorGravity;            }            final int layoutDirection = getLayoutDirection() ;            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection) ;            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {                case Gravity. CENTER_HORIZONTAL :                    childLeft = paddingLeft + ((childSpace - childWidth) / 2)                            + lp. leftMargin - lp.rightMargin ;                    break;                case Gravity.RIGHT:                    childLeft = childRight - childWidth - lp. rightMargin;                    break;                case Gravity.LEFT:                default:                    childLeft = paddingLeft + lp. leftMargin;                    break;            }            if (hasDividerBeforeChildAt(i)) {                childTop += mDividerHeight;            }            childTop += lp.topMargin;            setChildFrame(child , childLeft, childTop + getLocationOffset(child) ,                    childWidth, childHeight);            childTop += childHeight + lp. bottomMargin + getNextLocationOffset(child);            i += getChildrenSkipCount(child , i);        }    }}

主要看以下代码:

final int childWidth = child.getMeasuredWidth() ;final int childHeight = child.getMeasuredHeight() ;setChildFrame(child , childLeft, childTop + getLocationOffset(child) ,        childWidth , childHeight);childTop += childHeight + lp. bottomMargin + getNextLocationOffset(child);

top会逐渐增大,所以会往下排。setChildFrame仅仅是调用子元素的layout方法。

private void setChildFrame(View child, int left, int top , int width, int height) {          child.layout(left, top, left + width , top + height);}

通过子元素的layout来确定自身。

draw过程

它有以下几步:

  • 绘制背景,(canvas)

  • 绘制自己。(onDraw)

  • 绘制children(dispatchDraw)

  • 绘制装饰(onDrawScrollBars)

看下view的draw源码:

public void draw(Canvas canvas) {    final int privateFlags = mPrivateFlags;    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK ) == PFLAG_DIRTY_OPAQUE &&            (mAttachInfo == null || !mAttachInfo .mIgnoreDirtyState );    mPrivateFlags = (privateFlags & ~ PFLAG_DIRTY_MASK ) | PFLAG_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) {        drawBackground(canvas);    }    // 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) ;        if ( mOverlay != null && !mOverlay.isEmpty()) {            mOverlay .getOverlayView().dispatchDraw(canvas) ;        }        // we're done...        return;    }

viewgroup中的dispatchDraw用于遍历子view并调用子view的draw方法。这样就一层层的传下去。到此源码分析就结束了。在绘制view的时候经常会在activity中获得view的宽高,因为activity的生命周期和view不同步,在oncreate中无法获取到view的宽高,接下来讲讲activity中如何获取view。

三、view宽高确定

  • onWindowFocusChanged:view己经初始化完毕,宽高己经准备好。当Activity得到焦点和失去焦点均会被调用,所以它会调用多次。

  • 通过view.post,将一个runnable投弟到消息队列尾部,等待looper调用时,view己经初始化好。

protected void onStart(){super.onStart();view.post(new Runnable(){public void run(){int width = view.getMeasuredWidth();int height = new .getMeasuredHeight();}})}

  • ViewTreeObserver: 

    使用ViewTreeObserver众多回调接口来完成,如OnGlobalLayoutListener,当view树状态发生改变时或内部view可见性发生改变时会回调。

ViewObserver obserber = view.getViewObserver ();obserber.addOnGlobalLayoutListener(new OnGlobalLayoutListener(){public void onGlobalLayout(){obserber.removeOnGlobalLayoutListener(this);int width = view.getMeasuredWidth();int height = new .getMeasuredHeight();}})

  • 通过view进行measure来得到view的宽高。

int width = MeasureSpec.makeMeasureSpec(100,Measure.EXACTLY);//确定值int height= MeasureSpec.makeMeasureSpec(100,Measure.EXACTLY);//确定值view.measure(width,height);对于wrap_content:int width = MeasureSpec.makeMeasureSpec((1<<30)-1,Measure.AT_MOST);//wrap_contentint height= MeasureSpec.makeMeasureSpec((1<<30)-1,Measure.AT_MOST);//wrap_contentview.measure(width,height);

四、自定义view中注意事项

自定义View需要注意的事项:

  • 如果是继承view或者viewGroup,让view支持wrap_content。

  • 如果有必要,让view支持padding。

  • View中如果有动画或者线程,要在onDetachedFromWindow中及时停止。当view的Activity退出或者当前view被remove时,调用它。

0 0
原创粉丝点击