View工作原理-学习笔记

View工作原理-学习笔记

2023年7月15日发(作者:)

View⼯作原理-学习笔记View的⼯作原理主要是三⼤流程和⾃定义View的实现View⼯作原理.png1. 前置知识1.1 ViewRoot对应ViewRootImpl类,是连接WindowManager和DecorView的纽带,负责View的三⼤流程。问:ViewRootImpl什么时候创建?ActivityTrhead的handleResumeActivity,DecorView会由WindowManager添加到Window,同时创建ViewRootImpl对象,并与DecorView建⽴关联。//ivate Activity handleResumeActivity(...){ //1.回调onResume⽣命周期 performResumeActivity(...); //Manager添加DecorView w(decor, l); //3.设置DecorView可见 sible();}//id makeVisible(){ //设置DecorView可见 ibility(E);}//blic void addView(...){ //1.创建ViewRoot root = new ViewRootImpl(...); //2.设置DecorView布局参数 outParams(...); //3.记录DecorView、ViewRoot、LayoutParams (view); (root); (wparams); //ot关联DecorView w();}思考题:⼦线程可以操作UI吗?1.2 DecorViewWindow的根视图,是View树的根节点。ViewRootImpl通过DecorView层层递归执⾏三⼤流程。问:DecorView什么时候创建?DecorView在Activity#setContentView()时,由PhoneWindow创建。⽽PhoneWindow是在activity#attach()时创建。//ivate Activity performLaunchActivity(...){ //1.反射创建Activity实例 Activity activity = ivity(...); //2.初始化Activity (...); //3.回调onCreate⽣命周期 tivityOnCreate(...);}//nal void attach(...){ //....省略代码.... mWindow = new PhoneWindow(...); //....省略代码....}//otected void onCreate(...){ //设置Activity布局 setContentView();}//blic void setContent(int layoutResID){ //转交给PhoneWindow getWindow().setContentView(layoutResID);}//blic void setContentView(int layoutResID){ //1.创建DecorView installDecor(); //e布局,并添加DecorView的content e(layoutResID, mContentParent);}2. 三⼤流程的作⽤measure过程决定了View的测量宽⾼,测量完成后可以通过View#getMeasureWidth()、getMeasureHeight()获得,但在特殊情况下它并不是View的最终宽⾼。⽽且onMeasure可能会进⾏多次。layout过程决定了View的四个顶点的坐标和实际View的宽/⾼。可以通过View#getLeft()、getRight()、getTop()、getBottom()获得四个顶点,通过getWidth()、getHeight,获得View的最终宽⾼。draw过程决定了View显⽰的内容三⼤流程的起点//ivate void performTraversals() { //1.测量 performMeasure(); //2.布局 performLayout(); //3.绘制 performDraw();}3.测量MeasureSpec测量规格,⽗容器对⼦容器的约束条件。View的MeasureSpec由⽗ViewGroup的MeasureSpec结合View的LayoutParams得出,⽤于计算View的测量宽⾼。32位int值,⾼2位表⽰SpecMode测量模式,低30位表⽰SpecSize测量⼤⼩。三种SpecMode1. unspecified⽗容器对⼦容器不做任何限制2. exactly已经测量出View的精确⼤⼩SpecSize。对应match_parent和具体数值3. atmost⽗容器指定了View的最⼤⼤⼩SpecSize,对应wrap_contentView#setMeasuredDimension()将onMeasure后计算出来的测量宽⾼,保存到View的成员变量mMeasuredWidth、mMeasuredHeight。测量完后必须调⽤,不然测量结果不会保存。DecorViewDecorView因为没有⽗ViewGroup,所以它的MeasureSpec由窗⼝⼤⼩和⾃⾝的LayoutParams决定。//ivate boolean measureHierarchy(...){ //计算DecorView的measureSpec childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, ); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, ); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);}private static int getRootMeasureSpec(int windowSize, int rootDimension) {

int measureSpec; switch (rootDimension) {

case _PARENT: //撑满窗⼝ exactly 窗⼝⼤⼩ measureSpec = asureSpec(windowSize, Y);

break;

case _CONTENT: //包裹内容 atmost 窗⼝⼤⼩ measureSpec = asureSpec(windowSize, _MOST);

break;

default: //固定数值 exactly view⼤⼩ measureSpec = asureSpec(rootDimension, Y);

break;

}

return measureSpec;

}private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { //传递给DecorView测量规格,开始递归测量整个View树 e(childWidthMeasureSpec, childHeightMeasureSpec);}ViewView有⾃⼰默认的onMeasure逻辑,但是wrap_content得⾃⾏实现。这样设计的原因是,View绘制内容各不相同,内容宽⾼也各不相同,那就由View的⼦类重写去决定吧。//otected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),

getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}public static int getDefaultSize(int size, int measureSpec) {

int result = size;

int specMode = e(measureSpec);

int specSize = e(measureSpec);

switch (specMode) {

case IFIED: //⽗ViewGroup不约束,所以直接使⽤⼦View的宽⾼ result = size;

break;

//可以看到,这⾥wrap_content也设置为了⽗ViewGroup的宽⾼ case _MOST: case Y: result = specSize;

break;

}

return result;

}//View的最⼩宽度,由minWidth属性和背景的固有宽⾼决定protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, imumWidth());

}//blic int getMinimumWidth() {

final int intrinsicWidth = getIntrinsicWidth();

return intrinsicWidth > 0 ? intrinsicWidth : 0;

}ViewGroupViewGroup没有重写onMeasure逻辑,因为每个ViewGroup的具体测量⽅式各不相同,那就由ViewGroup的⼦类重写去吧。ViewGroup提供了getChildMeasureSpec()来⽣成child的MeasureSpec,measureChildren()和来测量整个children,measureChild()和measureChildWithMargins()来测量单个child。//测量它的children,不结合child的marginprotected 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 ((lags & VISIBILITY_MASK) != GONE) {

measureChild(child, widthMeasureSpec, heightMeasureSpec);

}

}

}//结合child的margin测量child的宽⾼protected void measureChildWithMargins(View child,

int parentWidthMeasureSpec, int widthUsed,

int parentHeightMeasureSpec, int heightUsed) {

final MarginLayoutParams lp = (MarginLayoutParams) outParams(); //padding值 = 左右padding + child左右margin + 已使⽤宽 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + rgin + argin + widthUsed, final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + gin + Margin + heightUsed,

e(childWidthMeasureSpec, childHeightMeasureSpec);

}//不结合child的margin测量child宽⾼protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {

final LayoutParams lp = outParams();

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,

mPaddingLeft + mPaddingRight, );

final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,

mPaddingTop + mPaddingBottom, );

e(childWidthMeasureSpec, childHeightMeasureSpec);

}//计算child的MeasureSpecpublic static int getChildMeasureSpec(int spec, int padding, int childDimension) {

int specMode = e(spec);

int specSize = e(spec);

int size = (0, specSize - padding);

int resultSize = 0;

int resultMode = 0;

switch (specMode) { // ⽗ViweGroup是固定宽⾼ case Y:

if (childDimension >= 0) { //child也是固定宽⾼,specSize就是child的layoutparam的宽⾼ resultSize = childDimension;

resultMode = Y;

} else if (childDimension == _PARENT) {

//child填满⽗容器,specSize就是⽗宽⾼ resultSize = size;

resultMode = Y;

} else if (childDimension == _CONTENT) { //child包裹内容,所以最⼤不能超过⽗容器,specSize就是⽗宽⾼ resultSize = size;

resultMode = _MOST;

}

break; //⽗ViewGroup有个最⼤宽⾼ case _MOST:

if (childDimension >= 0) {

//child固定宽⾼,那就直接⽤ resultSize = childDimension;

resultMode = Y;

} else if (childDimension == _PARENT) { //child填满⽗容器,所以child最⼤不能超过⽗容器的最⼤宽⾼ resultSize = size; resultMode = _MOST;

} else if (childDimension == _CONTENT) { } else if (childDimension == _CONTENT) { //child包裹内容,所以child最⼤不能超过⽗容器的最⼤宽⾼ resultSize = size; resultMode = _MOST;

}

break;

//⽗ViewGroup不约束,那就看child想要多⼤ case IFIED:

if (childDimension >= 0) {

//child固定宽⾼,那就给它 resultSize = childDimension;

resultMode = Y;

} else if (childDimension == _PARENT) { //child填满⽗容器 resultSize = roUnspecifiedMeasureSpec ? 0 : size;

resultMode = IFIED;

} else if (childDimension == _CONTENT) { //child包裹内容,那就先⽤⽗的宽⾼ resultSize = roUnspecifiedMeasureSpec ? 0 : size;

resultMode = IFIED;

}

break;

}

return asureSpec(resultSize, resultMode);

}//otected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ //保存测量结果 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),

getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}LinearLayout举例分析以垂直⽅向的LinearLayout为例,如果它是wrap_content,那么它的宽⾼取决于children的宽⾼和⾃⾝的MeasureSpec,否则只取决于⾃⾝的MeasureSpec。protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

if (mOrientation == VERTICAL) {

measureVertical(widthMeasureSpec, heightMeasureSpec);

} else {

measureHorizontal(widthMeasureSpec, heightMeasureSpec);

}

}//简化版void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { //遍历children for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); //测量child宽⾼ measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, usedHeight); //累加child⾼度和margin mTotalLength = (totalLength, totalLength + suredHeight() +

gin + Margin + getNextLocationOffset(child)); } //加上⾃⼰的padding mTotalLength += mPaddingTop + mPaddingBottom;

int heightSize = mTotalLength; //取mTotalLength和最⼩⾼度的较⼤值作为LinearLayout的⾼度 heightSize = (heightSize, getSuggestedMinimumHeight()); int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0); //....省略⼀段宽度计算代码.... //保存LinearLayout的测量结果 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),

heightSizeAndState);}获取View的宽⾼如何在View中获取最终测量宽⾼/最终宽⾼?某些情况下,View会经过多次measure才会确定最终的测量宽⾼。所以可以在onLayout时去获取最终的测量宽⾼/最终宽⾼。如何在Activity⽣命周期⽅法中,去获取View的宽⾼?这时候测量还不⼀定完成,直接获取测量宽⾼或者最终宽⾼,可能是0。可以通过以下⼏种⽅法:1. View#post(Runnable)⽐较靠谱,三⼤流程执⾏完后执⾏Runnable并且只会执⾏⼀次,原理可见学习笔记 - 简书2. ViewTreeObserver监听View树的状态变化或者可见性变化,所以会触发多次。ViewTreeObserver ob = wTreeObserver();LobalLayoutListener(new OnGlobalLayoutListener){ @Override public void onGlobalLayout(){ //触发后就移除监听,避免重复触发 GlobalLayoutListener(this); int widht = sureWidth(); int height = sureHeight(); }}3. View#measure()⼿动测量,挺复杂的。需要结合View的LayoutParams,match_parent时没法计算MeasureSpec,因为⽗ViewGroup宽⾼不知道。4. Activity/View onWindowFocusChanged窗⼝获取焦点或失去焦点时,此时View已经准备好宽⾼。onResume、onPause会重复触发逻辑。4.布局确定View在ViewGroup的位置,⽽对于ViewGroup,还确定了child的位置,顺带最终宽⾼也确定了。View实现了默认的layout()⽅法,保存⽗ViewGroup传过来的ltrb。onLayout()是空⽅法,交由⼦类实现。对于⾮ViewGroup⼀般也不会去修改,因为对于⾮ViewGroup如何摆放通常取决于⽗ViewGroup。//blic void layout(int l, int t, int r, int b){ //保存坐标到View成员变量 boolean changed = isLayoutModeOptical(mParent) ?

setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); onLayout(changed, l, t, r, b);}protected void onLayout(boolean changed, int left, int top, int right, int bottom) { //默认啥也没⼲}ViewGrouplayout()逻辑跟View⼀致,只是多了layout动画逻辑,对应xml属性animateLayoutChanges,当child位置发⽣变化时,会触发动画。ViewGroup的⼦类需要实现onLayout逻辑,来确定child的位置。public final void layout(int l, int t, int r, int b) { if (!mSuppressLayout && (mTransition == null || !gingLayout())) { //layout动画 if (mTransition != null) {

Change(this);

} (l, t, r, b);

} else { mLayoutCalledWhileSuppressed = true;

}}LinearLayout举例分析可以看到LinearLayout重写了onLayout逻辑,来控制child的位置。以垂直⽅向的LinearLayout为例@Override

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);

}

}void layoutVertical(int left, int top, int right, int bottom) { //......省略⼀部分代码...... final int count = getVirtualChildCount(); for (int i = 0; i < count; i++) {

final View child = getVirtualChildAt(i); //这⾥省略了⼀段计算child的位置逻辑 setChildFrame(child, childLeft, childTop + getLocationOffset(child),

childWidth, childHeight); }}//调⽤child的layout()来保存其位置private void setChildFrame(View child, int left, int top, int width, int height) { (left, top, left + width, top + height);}注意:最终宽⾼可以与测量宽⾼不同,因为开发者可以在View#onLayout中,修改⽗ViewGroup给的坐标,但没什么实际意义。5.绘制四步⾛1. 绘制背景2. 绘制⾃⼰3. 绘制children4. 绘制装饰//blic void draw(Canvas canvas){ //1.绘制背景 drawBackground(canvas); //2.绘制⾃⼰ onDraw(canvas); //3.绘制children dispatchDraw(canvas); //4.绘制装饰 e.g. 前景foreground或者scrollbar onDrawForeground(canvas);}只有ViewGroup才有dispatchDraw实现,调⽤child的draw(三参数)⽅法//@Override

protected void dispatchDraw(Canvas canvas) { //......简化了逻辑,只提取部分逻辑...... final int childrenCount = mChildrenCount;

final View[] children = mChildren; for(int i = 0; i < childrenCount; i++){ View child = children[i]; (canvas, this, drawingTime); }}//olean draw(Canvas canvas, ViewGroup parent, long drawingTime) { if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { //如果设置了PFLAG_SKIP_DRAW,那么就会跳过⾃⾝的draw逻辑 mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); } else { draw(canvas);

}}willNotDraw可以通过setWillNotDraw(true)来设置不绘制,⼀旦设置就不会⾛draw逻辑,内部实现是设置了PFLAG_SKIP_DRAW的flag。ViewGroup默认为true,因为ViewGroup⼀般不绘制东西。但是如果ViewGroup设置了background或者foreground,就会把willNotDraw去掉了,也可以⾃⾏调⽤setWillNotDraw(false)public ViewGroup(...){ //ViewGroup默认设置这个flag setFlags(WILL_NOT_DRAW, DRAW_MASK);}public void setWillNotDraw(boolean willNotDraw) {

setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);

}//setWillNotDraw最终是在mPrivateFlags成员变量上追加了标志位PFLAG_SKIP_DRAWpublic setFlags(...){ //......省略部分逻辑...... if ((mViewFlags & WILL_NOT_DRAW) != 0) {

if (mBackground != null

|| mDefaultFocusHighlight != null

|| (mForegroundInfo != null && ble != null)) {

mPrivateFlags &= ~PFLAG_SKIP_DRAW;

} else {

mPrivateFlags |= PFLAG_SKIP_DRAW;

}

} else {

mPrivateFlags &= ~PFLAG_SKIP_DRAW;

}}6.⾃定义View实现⾃定义View的⼏种⽅式1. 继承View,重写onDrawwrap_conten、padding得⾃⾏处理,可以⾃由实现绘制的内容2. 继承ViewGroup,重写onMeasure、onLayout需要合适地处理测量、布局3. 继承已有View的⼦类,做功能扩展4. 继承已有ViewGroup的⼦类,做功能扩展注意事项:1. View的wrap_content的⽀持2. View的padding的⽀持3. View#post的使⽤4. View内部线程和动画及时停⽌,避免内存泄露,可在onDetachFromWindow处理5. 滑动冲突的处理参考资料:《安卓开发艺术探索》

发布者:admin,转转请注明出处:http://www.yc00.com/news/1689431017a247126.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信