View工作原理包括其三大流程:onMeasure() -->onLayout--->onDraw()
测量、布局、绘制。
View的常用回调方法onAttach() onVisibilityChanged() onDetach()
如果View是可以滑动的,还需要解决View的滑动冲突
View 的自定义实现方式
1.继承View/viewGroup
2.继承现有的控件
### 初识ViewRoot 和DecorView
ViewRoot对应于ViewRootImpl是最终执行onMeasure() --onLayout() --onDraw()的类,
连接了WinodowManager和DecorView;
DecorView作为顶级View,其实是一个FrameLayout,通常包含一个Vertical的LinearLayout,里面是 titlebar +contentView,我们用的setContentView()就是set到这个content里。
所有的View事件都是经过Decorview然后传到我们的View.
Measure 过程
view的measure:
### OnMeasure
View系统绘制从ViewRoot.performTraversals() 开始,在其内部调用
View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,在其内部调用View的measure()方法。measure()方法接收两个参数,
接下来我们看下View的measure()方法里面的代码吧,如下所示:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
mPrivateFlags &= ~MEASURED_DIMENSION_SET;
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
}
onMeasure(widthMeasureSpec, heightMeasureSpec);
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;
如上面👆代码可以看到:measure()这个方法是final的,因此我们无法在子类中去重写这个方法,说明Android是不允许我们改变View的measure框架的。然后调用了onMeasure()方法,这里才是真正去测量并设置View大小的地方,默认会调用getDefaultSize()方法来获取视图的大小,如下所示:
### onMeasure()方法如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
### getDefaultSize()方法如下:
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
这上上面的代码中传入的measureSpec是measure()方法中传来的,先解析measureSpec中的specSize specMode,
如果是AT_MOST EXACTLY,就返回specSize(),这是系统默认的。如果是UNSPECIFIED,测量大小就是getSuggestedMinimumWidth() ;
getSuggestedMinimumWidth() ;
protected int getSuggestedMinimumWidth() {
//如果没有给View设置背景,那么就返回View本身的最小宽度mMinWidth
//如果给View设置了背景,那么就取View本身最小宽度mMinWidth和背景的最小宽度的最大值
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
mBackground.getMinimumWidth()返回的是Drawable 的原始宽度,ShapDrawable无原始宽度,BitmapDrawable有原始宽度。即:无背景,返回minWidth(),不设置为0;有背景返回max(mMinWidth,背景最小宽度);
之后在setMeasuredDimension()中设置测量的大小,这样一次measure过程结束。
viewgroup的measure过程
viewgroup除了measure本身,还要遍历子view,调用每个子View的measure()
ViewGroup 是个抽象类,它没有ovveridde View的onMeasure(),
但是定义了measureChildren()来测量子视图大小。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
遍历所有子视图,然后逐一调用measureChild() 方法测量子视图。
### measureChild() 方法below :
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
根据布局中的MATCH_PARENT、WRAP_CONTENT通过getChildMeasureSpec()计算得到 child的MeasureSpec,然后传入measure()中,之后,就是前面的measure()流程。
可以重写 onMeasure()方法:
public class MyView extends View {
......
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(200, 200);
}
主要代码:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
。。。
// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
....
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
}
measureChildBeforeLayout()内部调用各个子View的measure(),mTotalLength存储Linearlayout在竖直方向的高度,每测量一个view,mTotalLength增加,增加部分是子元素的高度以及竖直的margin;当子元素测量完后,linearlayout进入自己的测量
setMeasuredDimension();
这样不管xml布局怎样设置,view的大小都是200 *200 ;
注意:只有在setMeasuredDimension之后,才可以getMeasuredWidth()来获取测量后的宽高。
ViewGroup没有实现具体的测量方法,因为ViewGroup是抽象类,其测量过程的onMeasure()x需要各个子类具体去实现。为什么需要具体实现?因为不同的ViewGroup子类如RelativeLayout、LinearLayout的特性不一致,所以需要具体实现。
下面以LinearLayout 的onMeasure()为例子,来分析:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
视图大小由父视图、布局、视图本身决定,父视图提供参考,布局
onLayout() :
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);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
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;
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
这里判断view的位置和尺寸是否改变了,
//如果isLayoutModeOptical()返回true,那么就会执行setOpticalFrame()方法,
//否则会执行setFrame()方法。并且setOpticalFrame()内部会调用setFrame(),
//所以无论如何都会执行setFrame()方法。
//setFrame()方法会将View新的left、top、right、bottom存储到View的成员变量中
//并且返回一个boolean值,如果返回true表示View的位置或尺寸发生了变化,
//否则表示未发生变化
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
如果
网友评论