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 listenersCopy =(ArrayList)li. mOnLayoutChangeListeners .clone();int numListeners = listenersCopy.size() ;for ( int i = 0 ; i }
在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 gofinal int width = right - left;int childRight = width - mPaddingRight ;// Space available for childint 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 alreadychildTop = mPaddingTop + bottom - top - mTotalLength;break;// mTotalLength contains the padding alreadycase Gravity.CENTER_VERTICAL:childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;break;case Gravity. TOP:default :childTop = mPaddingTop;break;}for (int i = 0; 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过程
它有以下几步:
看下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 neededint 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 contentif (!dirtyOpaque) onDraw(canvas) ;// Step 4, draw the childrendispatchDraw(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宽高确定
protected void onStart(){
super.onStart();
view.post(new Runnable(){
public void run(){
int width = view.getMeasuredWidth();
int height = new .getMeasuredHeight();
}
})
}
ViewObserver obserber = view.getViewObserver ();
obserber.addOnGlobalLayoutListener(new OnGlobalLayoutListener(){
public void onGlobalLayout(){
obserber.removeOnGlobalLayoutListener(this);
int width = view.getMeasuredWidth();
int height = new .getMeasuredHeight();
}
})
int width = MeasureSpec.makeMeasureSpec(100,Measure.EXACTLY);//确定值
int height= MeasureSpec.makeMeasureSpec(100,Measure.EXACTLY);//确定值
view.measure(width,height);
对于wrap_content:
int width &#61; MeasureSpec.makeMeasureSpec((1<<30)-1,Measure.AT_MOST);//wrap_content
int height&#61; MeasureSpec.makeMeasureSpec((1<<30)-1,Measure.AT_MOST);//wrap_content
view.measure(width,height);
四、自定义view中注意事项
自定义View需要注意的事项&#xff1a;
-
如果是继承view或者viewGroup&#xff0c;让view支持wrap_content。
-
如果有必要&#xff0c;让view支持padding。
-
View中如果有动画或者线程&#xff0c;要在onDetachedFromWindow中及时停止。当view的Activity退出或者当前view被remove时&#xff0c;调用它。