笔者是面霸,面试200+场 当过考官:面过别人300+场 去过500强,也呆过初创公司。
关注我就能达到大师级水平,这话我终于敢说了, 年薪60万不是梦!
斩获腾讯、华为、字节跳动,蚂蚁金服,oppo,VIVO,安卓岗offer!我有一套速通大厂技巧分享给你!
请问的格式怎么排版??每次写完都变格式了。非常苦恼
1.View绘制流程?
2.View Window ViewRootImp之间的关系?
3.控件的宽高和哪些因素有关系?
4.Android的wrap_content是如何计算的?
5.为什么你的自定义View wrap_content不起作用?
6.说下Measurepec这个类
7.invalidate()和postInvalidate()和requestlayout的使用与区别
8.自定义View执行invalidate()方法,为什么有时候不会回调onDraw()
9.如何获取 View 宽高?
10.一个view的宽和高是由什么决定!
11.getWidth() ( getHeight())与 getMeasuredWidth() (getMeasuredHeight())获取的宽 (高)有什么区别?
12.Android消息机制原理——为什么不能在子线程更新UI?
13.如何自定义View?
14.自定义View为什么有3个构造函数
15.自定义view效率高于xml定义吗?说明理由
16.自定义view的生命周期如何?
17.如何优化自定义View?
18.veiw状态的保持
自定义view十八连问
1.View绘制流程?
setContentView开始
View------Window-----ViewRootImp的setView()----ViewRootImp的RequestLayout()---performTraversals()3个方法
源码如下:
WindowManagerImpl实现类:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params)
;
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
ViewRootImpl在windowManager传入的!
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view
, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index
, true);
}
throw e;
}
ViewRootImpl的setView方法:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
然后调用ViewRootImpl类都requestLayout()方法
ViewRootImpl是继承ViewParent
ublic final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
View系统的绘制流程会从ViewRootImpl的performTraversals()方法中开始,performTraversals()的意思是:执行遍历
Traversals:遍历的意思
ViewRoot中包含了窗口的总容器DecorView,ViewRoot中的performTraversal()方法会依次调用decorView的measure、layout、draw方法,从而完成view树的绘制。
measure()方法,layout(),draw()三个方法主要存放了一些标识符,来判断每个View是否需要再重新测量,布局或者绘制
View树的绘制是一个递归的过程,从ViewGroup一直向下遍历,直到所有的子view都完成绘制
View的整个绘制流程可以分为以下三个阶段:
measure: 判断是否需要重新计算View的大小,需要的话则计算;每个View的控件的实际宽高都是由父视图和本身视图决定的
layout: 判断是否需要重新计算View的位置,需要的话则计算;
draw: 判断是否需要重新绘制View,需要的话则重绘制。
measure()、layout()、draw(),其内部又分别包含了onMeasure()、onLayout()、onDraw()三个子方法。
其他:
View的measure方法是final的,不允许重载,View子类只能重载onMeasure来完成自己的测量逻辑。
最顶层DecorView测量时的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法确定的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)。
ViewGroup类提供了measureChild,measureChild和measureChildWithMargins方法,简化了父子View的尺寸计算。
只要是ViewGroup的子类就必须要求LayoutParams继承子MarginLayoutParams,否则无法使用layout_margin参数。
使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。
2.View Window ViewRootImp之间的关系?
3.控件的宽高和哪些因素有关系?
测量逻辑:
如果子视图对于 Measure 得到的大小不满意的时候,父视图会介入并设置测量规则进行第二次
measure。比如,父视图可以先根据未给定的 dimension 去测量每一个子视图, 如果最终子视图的未约束尺寸太大或者太小的时候,父视图就会使用一个确切的 大小再次对子视图进行 measure。
如果子视图对于 Measure 得到的大小不满意的时候,父视图会介入并设置测量规则进行第二次measure。
比如,父视图可以先根据未给定的 dimension 去测量每一个子视图, 如果最终子视图的未约束尺寸太大或者太小的时候,父视图就会使用一个确切的 大小再次对子视图进行 measure。
measure 过程传递尺寸的两个类
1.ViewGroup.LayoutParams (View 自身的布局参数)
2.MeasureSpecs 类(父视图对子视图的测量要求)
当不需要绘制 Layer 的 时候第二步和第五步会跳过。因此在绘制的时候,能省的 layer 尽可省,可以
View的measure方法是final的,不允许重载,View子类只能重载onMeasure来完成自己的测量逻辑。
最顶层DecorView测量时的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法确定的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)。
ViewGroup类提供了measureChild,measureChild和measureChildWithMargins方法,简化了父子View的尺寸计算。
只要是ViewGroup的子类就必须要求LayoutParams继承子MarginLayoutParams,否则无法使用layout_margin参数。
使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。
4.请简述一下ViewGroup的绘制流程?
Android的wrap_content是如何计算的?
ViewGroup去会管理其子View,包括管理负责子View的显示大小。当ViewGroup的大小为wrap_content,ViewGroup就需要对子View进行遍历,以便获得所有子View的大小,从而来决定自己的大小。
在其他模式下会通过具体的指定值来设置自身的大小。 VIewGroup在测量时会通过遍历所有子View,从而调用子View的Measure方法来获得每个子View的测量结果,前面所说的对View的测量,就是在这里进行的。当子View测量完毕后,就需要将子View放到合适的位置,这个过程就是View的Layout过程。ViewGroup在执行Layout过程时,同样是遍历来调用子View的Layout方法,并指定其具体显示的位置,从而来决定其布局位置。
在自定义ViewGroup时,通常会去重写onLayout()方法来控制其子View显示位置的逻辑。同样,如果需要支持wrap_content属性,那么它必须要还要重写onMeasure()方法,这点与View是相同的。
下面简述一下ViewGroup绘制的逻辑:通常ViewGroup情况下不需要绘制,因为本身就没什么可绘制的东西,如果不是指定了ViewGroup的背景颜色,那么ViewGroup的onDraw()方法都不会被调用。ViewGroup会使用dispatchDraw()方法来绘制其子View,其过程同样是通过遍历所有子View,并调用子View的绘制方法来完成绘制工作。
源码分析如下:
protected void measureChildWithMargins(View child,
int
parentWidthMeasureSpec, int widthUsed,
int
parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int
childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int
childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
getChildMeasureSpec();
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int
specSize = MeasureSpec.getSize(spec);
int
size = Math.max(0, specSize - padding);
int
resultSize = 0;
int
resultMode = 0;
switch
(specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension
;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension
;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
EXACTLY:(确切的数据),
如果当前控件的宽高是确切值就用这个值,否则由父元素决定。
如果子控件是match_parenter。那么就把父元素的大小给子控件。
如果子控件是wrap_content。父元素的大小给子控件
如果父类是Wrap_content
如果父view不限制,就按自己的背景大小或者最小值来显示,如果父view有限制,就按父view给的尺寸来显示。
按照这个逻辑,如果要自己写一个自定义View,大小可以在布局中确定的话,一般不用再重新onMeasure 再做什么工作了。
但是如果自己的自定义View在布局中使用WRAP_CONTENT,并且内容大小并不确定的话,还是要根据自己的显示逻辑做一些工作的。
比如,自己写一个显示图片的控件,布局中使用WRAP_CONTENT,那么根据以上的逻辑梳理,父view很可能就扔给你一个尺寸模式:大小是父view本身的大小,模式是MeasureSpec.AT_MOST;
这样的话即使你布局里写的是WRAP_CONTENT,你也会使用父view建议给你的尺寸,占满父view全部的空间了,即使你的图片并没有那么大~是不是会很奇怪?
所以,一般情况下,展示内容尺寸不确定的自定义View,onMeasure可以作如下类似的逻辑:
5.为什么你的自定义View wrap_content不起作用?
举一个实例:
extend view ,不写onMeasure ,会怎么显示
如果自定义View没有重写onMeasure函数,就看viewGroup里面的源码
extent viewGroup 不写onMeasure 不写onMeasure,不可以显示
模式是由父布局和自己决定的。
比如:父亲是wrapcontent,就算子布局是match_parent,这个时候测量模式还是at_most
父亲是match_parent,子布局是match_parent,这个时候测量模式还是exactly
其他结论
1.继承view,子布局即使在xml中有宽高,不写onMeasure,可以显示
2.View测量的时候,默认是EXACTLY模式,你不重写OnMeasure方法,即使设置wrap_content属性,他也是填充父容器。(不是viewgroup)
3.继承ViewGroup,子布局即使在xml中有宽高,不写onMeasure,不可以显示 ,必须重写ononMeasure
4.继承LinearyLayout,子布局即使在xml中有宽高,不写onMeasure,可以显示
Demo地址:https://github.com/pengcaihua123456/shennandadao/tree/master
View的测量大小与 wrap_content
先说结论:默认情况下,当父布局为 wrap_content 或者 match_parent 时,无论子 view(view 或者 viewgroup) 是wrap_content 还是 match_parent,最终的效果都是 match_parent。也就是 子 view 会占据父布局中剩下的所有空间。
实战二:
有这样一个需求?一个父类宽高,然后一个imageview要完全填充父类?一个surfaceview可以完成填充父类。
1.父类为wrap_content,子类也未wrap_content
那么子控件的宽高是多少?
当ViewGroup的大小为wrap_content,ViewGroup就需要对子View进行遍历,以便获得所有子View的大小,从而来决定自己的大小。
发现:子view会填充父控件。
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center_horizontal" android:orientation="vertical" android:layout_marginRight="@dimen/dp_16" android:layout_marginLeft="@dimen/dp_16" android:layout_marginTop="@dimen/dp_16" android:layout_marginBottom="@dimen/dp_16" tools:background="@color/color_eeeeee"> android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/black"> android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/red"> 2.父类为wrap_content,子类为match_parent android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/black"> android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/red"> 那么子控件的宽高是多少? 和上面一样,填充父布局 3.父类为50dp,子类为wrap_content 那么子控件的宽高是多少? android:layout_width="@dimen/dp_60" android:layout_height="@dimen/dp_60" android:layout_gravity="center" android:background="@color/black"> android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:background="@color/red"> 和上面一样,填充父布局 4.父类为wrap_content,子类为80dp 子控件说了算 android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:background="@color/green"> android:layout_width="@dimen/dp_50" android:layout_height="@dimen/dp_50" android:layout_gravity="center" android:background="@color/red"> 实战3:控件的大小怎么调试都没有用,外面少了一层父布局。首先View必须存在于一个布局中???、 1.progrossbar的高度问题 2.linearylayout的高度问题 实战分析: 3.场景分析:通过一个viewPager自定义banner? 发现 viewPager wrap的高度不生效 原理是:viewPager重写了onMesure。固定了自己的高度。然后测量了子view 4.类似的情况,recyleView里面的item高度问题。同样可以 onmesure什么时候触发: 1.父类调用一次 2.onlayout的时候又去调用次 viewpager本来是用来切换整个屏幕的。不是用来做banner的。 所以高度要自己测量高度。 测量原理:树的深度变量。 解决办法:重写onMesure方法 先度量子view然后再度量自己。 这样是不正确的,要用到LayoutParams才行 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int height = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i) ; child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); int h = child.getMeasuredHeight(); if (h > height) height = h; } heightMeasureSpec = MeasureSpec.makeMeasureSpec(height , MeasureSpec.EXACTLY); super .onMeasure(widthMeasureSpec, heightMeasureSpec); 一个viewGroup。父类给他一个参考值,同时要先度量子view。才能确定 有点像view的时间分发。 通过源码发现:测量自己之前先测量自己都子view ViewPager源码: setMeasuredDimension:测量自己 LayoutParams:子空间的参数测量 MeasureSpec.makeMeasureSpec(widthSize, widthMode); 测量的计算模式 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // For simple implementation, our internal size is always 0. // We depend on the container to specify the layout size of // our view. We can't really know what it is since we will be // adding and removing different arbitrary views and do not // want the layout to change as this happens. setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), getDefaultSize(0, heightMeasureSpec)); final int measuredWidth = getMeasuredWidth(); final int maxGutterSize = measuredWidth / 10; mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize); // Children are just made to fill our space. int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight(); int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); /* * Make sure all children have been properly measured. Decor views first. * Right now we cheat and make this less complicated by assuming decor * views won't intersect. We will pin to edges based on gravity. */ int size = getChildCount(); for (int i = 0; i < size; ++i) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (lp != null && lp.isDecor) { final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; int widthMode = MeasureSpec.AT_MOST; int heightMode = MeasureSpec.AT_MOST; boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM; boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT; if (consumeVertical) { widthMode = MeasureSpec. EXACTLY; } else if (consumeHorizontal) { heightMode = MeasureSpec. EXACTLY; } int widthSize = childWidthSize; int heightSize = childHeightSize; if (lp.width != LayoutParams.WRAP_CONTENT) { widthMode = MeasureSpec. EXACTLY; if (lp.width != LayoutParams.MATCH_PARENT) { widthSize = lp. width; } } if (lp.height != LayoutParams.WRAP_CONTENT) { heightMode = MeasureSpec. EXACTLY; if (lp.height != LayoutParams.MATCH_PARENT) { heightSize = lp. height; } } final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode); final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode); child.measure(widthSpec, heightSpec); MeasureSpec.makeMeasureSpec(widthSize, widthMode); makeMeasuerMespect:度量计算:把参数转成具体指 6.说下Measurepec这个类 一个类,把模式和值封装在一起,这个类在view中。是是一个类,不是一个常量 测量规格(MeasureSpec) = 测量模式(mode) + 测量大小(size) 然后测量转化为具体的数值。 MeasureSpec(View的内部类)测量规格为int型,值由高2位规格模式specMode和低30位具体尺寸specSize组成。其中specMode只有三种值 MeasureSpec.EXACTLY //确定模式,父View希望子View的大小是确定的,由specSize决定; MeasureSpec.AT_MOST //最多模式,父View希望子View的大小最多是specSize指定的值; MeasureSpec.UNSPECIFIED //未指定模式,父View完全依据子View的设计值来决定; 他们对应的二进制值分别是: UNSPECIFIED=00000000000000000000000000000000 EXACTLY =01000000000000000000000000000000 AT_MOST =10000000000000000000000000000000 由于最前面两位代表模式,所以他们分别对应十进制的0,1,2; 1.EXACTLY 精确值模式,当控件的layout_width和layout_height属性指定为具体数值或match_parent时。 match_parent:属于哪种?EXACTLY 2.AT_MOST 最大值模式,当空间的宽高设置为wrap_content时。 wrap_content:属于哪种?AT_MOST 3.UNSPECIFIED 未指定模式,View想多大就多大,通常在绘制自定义View时才会用。 决定因素:值由子View的布局参数LayoutParams和父容器的MeasureSpec值共同决定。具体规则见下图: 引申:直接继承View的自定义View需要重写onMeasure()并设置wrap_content时的自身大小,否则效果相当于macth_pather:原因是因为:源代码里面有 结论:子View的MeasureSpec值根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来的 7.invalidate()和postInvalidate()和requestlayout的使用与区别 invalidate 会先找到父类去走绘制流程,最终遍历所有相关联的 View ,触发它们的 onDraw 方法进行绘制 invalidate()得在UI线程中被调动,在工作者线程中可以通过Handler来通知UI线程进行界面更新。而postInvalidate()在工作者线程中被调用。 requestLayout:有布局需要发生改变,需要调用requestlayout方法,如果只是刷新动画,则只需要调用invalidate方法。 requestLayout()方法会调用measure过程和layout过程,不会调用draw过程,也不会重新绘制任何View包括该调用者本身。 onMeasure()和onLayout()要调用requestLayout()才能让改变生效 invalidate:View(非容器类)调用invalidate方法只会重绘自身,ViewGroup调用则会重绘整个View树。 源码分析:2个方法都会调用scheduleTraversals(); 但是他们都有标识位控制。 https://blog.csdn.net/a553181867/article/details/51583060 @Override public final void invalidateChild(View child, final Rect dirty) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null && attachInfo.mHardwareAccelerated) { // HW accelerated fast path onDescendantInvalidated(child, child); return; } ViewParent parent = this; if (attachInfo != null) { // If the child is drawing an animation, we want to copy this flag onto // ourselves and the parent to make sure the invalidate request goes @Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread() ; if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty); if (dirty == null) { invalidate() ; return null; } else if (dirty.isEmpty() && !mIsAnimating) { return null; } 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); } } invalidateRectOnScreen(dirty) ; return null; } private void invalidateRectOnScreen(Rect dirty) { final Rect localDirty = mDirty; // Add the new dirty rect to the current one localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom); // Intersect with the bounds of the window to skip // updates that lie outside of the visible region final float appScale = mAttachInfo.mApplicationScale; final boolean intersected = localDirty.intersect(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); if (!intersected) { localDirty.setEmpty() ; } if (!mWillDrawSoon && (intersected || mIsAnimating)) { scheduleTraversals() ; } } 8.自定义View执行invalidate()方法,为什么有时候不会回调onDraw() 调用view.invalidate(),会触发onDraw和computeScroll()。前提是该view被附加在当前窗口上 自定义一个ViewGroup,重写onDraw。onDraw可能不会被调用,原因是需要先设置一个背景(颜色或图)。 表示这个group有东西需要绘制了,才会触发draw,之后是onDraw。 因此,一般直接重写dispatchDraw来绘制viewGroup,自定义一个ViewGroup,dispatchDraw会调用drawChild 9. android中View的GONE和INVISIBLE的原理 1.visible:3个方法都执行 2.INVISIBLE:执行2个方法,不执行onDraw方法 3.Gone: 一个方法都不会执行 所以,获取宽和高不一样 Gone:得不到宽和高 INVISIBLE可以的都宽高 发现: viewRoot=View.inflate(context, R.layout.tab_main_group_run, this); getViews(); setViewsOnClick(); init(); int webViewHeightheight = webView.getHeight(); Log.d("peng", "onLoadFinish_height" + webViewHeightheight+"view height"+viewRoot.getHeight()); int webViewHeightheight = webView.getHeight(); 9.如何获取 View 宽高? 通过View.post ()。获取宽和高 在 onResume 中handler.post 在 View.post 后面为什么执行反而在前面; 通过上面第2点和点3点分析可以知道View.post的在后面performTraversals中被执行,而handler.post在performTraversals之前就被执行 View.post() 为什么能够获取到 View 的宽高 ? 里面发送了一个消息,仅仅保存起来。 测量后回调用dispatchAttachedToWindow 源码分析: 可以看出 onResume() 方法在 addView() 方法前调用 重点关注:onResume() 方法所处的位置,前后都发生了什么? 从上面总结的流程看出,onResume() 方法是由 handleResumeActivity 触发的,而界面绘制被触发是因为 handleResumeActivity() 中调用了wm.addView(decor, l); 10.一个view的宽和高是由什么决定! 从源码可以看出来,子View的测量模式是由自身LayoutParam和父View的MeasureSpec来决定的。 11.问题八:getWidth() ( getHeight())与 getMeasuredWidth() (getMeasuredHeight())获取的宽 (高)有什么区别? getWidth() / getHeight():获得View最终的宽 / 高 getMeasuredWidth() / getMeasuredHeight():获得 View测量的宽 / 高 // 获得View测量的宽 / 高 public final int getMeasuredWidth() { return mMeasuredWidth & MEASURED_SIZE_MASK; // measure过程中返回的mMeasuredWidth } public final int getMeasuredHeight() { return mMeasuredHeight & MEASURED_SIZE_MASK; // measure过程中返回的mMeasuredHeight } // 获得View最终的宽 / 高 public final int getWidth() { return mRight - mLeft; // View最终的宽 = 子View的右边界 - 子view的左边界。 } public final int getHeight() { return mBottom - mTop; // View最终的高 = 子View的下边界 - 子view的上边界。 } 他们的值大部分时间都是相同的,但意义确是根本不一样的, - 首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。 - getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过layout(left,top,right,bottom)方法设置的。 12.如果不手动设置支持padding属性,那么padding属性在自定义View中是不会生效的? protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 获取传入的padding值 final int paddingLeft = getPaddingLeft(); final int paddingRight = getPaddingRight(); final int paddingTop = getPaddingTop(); final int paddingBottom = getPaddingBottom(); 13. android 源码分析padding替代margin======3大布局性能比较的时候 14.Android消息机制原理——为什么不能在子线程更新UI?竟然崩溃了,那问题来了,到底子线程能不能更新Ui呢? android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 最重要的方法来了,mThread线程是主线程,Thread.currentThread()是当前线程,即我们运行的子线程 假如当前更新UI不在主线程,就会导致CalledFromWrongThreadException异常 由此可见,每一次刷新View都会调用ViewRootImp的checkThread()方法去检测是否在主线程 按理来说,这样是可以的!但是google为什么要这样去设计呢? (1)如果在不同的线程去控制用一个控件,由于网络延时或者大量耗时操作,会使UI绘制错乱,出了问题也很难去排查到底是哪个线程更新时出了问题; (2)主线程负责更新,子线程负责耗时操作,能够大大提高响应效率 (3)UI线程非安全线程,多线程进行并发访问有可能会导致内存溢出,降低硬件使用寿命;且非线程安全不能加Lock线程锁,否则会阻塞其他线程对View的访问,效率就会变得低下! 自定义View问题 1.如何自定义View? 自定义view套路: 1.自定义属性(可配置) 2.onMesure ,如果是继承view,要写。如果是继承button,这种就不要写了 3.ondraw() 4.onTouch() 自定义viewgroup 1.自定义属性(可配置)很少写 2.onMesuare() for循环测量子view。根据子view计算自己的宽和高 3.onlayout() 4.不会ondraw,如果要实现用DispatchDraw() 5.一般不继承viewGroup.而是linearyLayout,或者viewPager(); 自定义控件实现方式:1.自定义组合控件2.继承已有控件3.继承View(构造函数里获取自定义属性)4.继承ViewGroup 而一些自定义View,现在大厂中必备的技能,频率非常非常之高,可能每个人对自定义View的理解也不尽相同,又说可能说有三种可能说有多种,其实在大厂中用的最多的那种叫做自定义组合View。 因为大厂里不建议你直接去画一个View,即自己去绘制的这个控件,而更建议去使用原生的或者现成的优质View,即能去组合就去组合,所以这也体现了自定义组合View的重要。 自定义组合View因为可以把自己的逻辑封装到一起,这样可以即简洁又高效。其实有一些部门可能会专门去画一些View,封装这些View以及框架等,或者说有一些专门的人就做一些纯绘制View。 这样会避免一些自己画的可能兼容性和通用性不是很好,也可能还会隐藏其他的BUG,所以说大厂中很不建议自己就画一个View(直接继承View和ViewGroup),因此说自定义组合View成了一个大厂的基本的一个要求 2.自定义View为什么有3个构造函数 第一个:new 出来 第二个:xml中,findviewById 可以查看源码:layoutFlate,里面通过反射实现的。(context ,attr) 第三个:主题用到 3.自定义view效率高于xml定义吗?说明理由 自定义view效率高于xml定义: 1、少了解析xml。 2.、自定义View 减少了ViewGroup与View之间的测量,包括父量子,子量自身,子在父中位置摆放,当子view变化时,父的某些属性都会跟着变化。 计算一个view的嵌套层级 private int getParents(ViewParents view){ if(view.getParents() == null) return 0; } else { return (1 + getParents(view.getParents)); } 4.自定义view的生命周期如何? View生命周期相关方法: onFinishInflate() 当View中所有的子控件均被映射成xml后触发 onMeasure( int , int ) 确定所有子元素的大小 onLayout( boolean , int , int , int , int ) 当View分配所有的子元素的大小和位置时触发 onSizeChanged( int , int , int , int ) 当view的大小发生变化时触发 onDraw(Canvas) view渲染内容的细节 onKeyDown( int , KeyEvent) 有按键按下后触发 onKeyUp( int , KeyEvent) 有按键按下后弹起时触发 onTrackballEvent(MotionEvent) 轨迹球事件 onTouchEvent(MotionEvent) 触屏事件 onFocusChanged( boolean , int , Rect) 当View获取或失去焦点时触发 onWindowFocusChanged( boolean ) 当窗口包含的view获取或失去焦点时触发 onAttachedToWindow() 当view被附着到一个窗口时触发 onDetachedFromWindow() 当view离开附着的窗口时触发,Android123提示该方法和 onAttachedToWindow() 是相反的。 onWindowVisibilityChanged( int ) 当窗口中包含的可见的view发生变化时触发 综上所述:View 的关键生命周期为 [改变可见性] --> 构造View --> onFinishInflate --> onAttachedToWindow --> onMeasure --> onSizeChanged --> onLayout --> onDraw --> onDetackedFromWindow onAttachedToWindow是在第一次onDraw前调用的。也就是我们写的View在没有绘制出来时调用的,但只会调用一次。 onDetachedFromWindow:销毁资源(既销毁view)之后调用。 5. 如何优化自定义View? 6.veiw状态的保持 view是先父view测量子view,等子view测量完,再测量自己 首先Activity 被意外终止时,Activity 会调用onSaveInstanceState 去保存数据,然后Activity 会委托Window 去保存数据,接着Window 在委托它上面的顶级容器去保存数据。顶级容器是一个ViewGroup,一般来说它很可能是DecorView。 最后顶层容器再去一一通知它的子元素来保存数据,这样整个数据保存过程就完成了。可以发现,这是一个典型的委托思想,上层委托下层,父容器去委托子元素去处理一件事情,这种思想在Android 中有很多应用,比如:View 的绘制过程,事件分发等都是采用类似的思想。 既然View的状态是基于它的ID存储的 , 因此如果一个VIew没有ID,那么将不会被保存到container中。没有保存的支点(id),我们也无法恢复没有ID的view的状态,因为不知道这个状态是属于哪个View的。 这里需要注意一个细节:想要保存View的状态,需要在XML布局文件中提供一个唯一的ID(android:id), 如果没有设置这个ID的话,View控件的onSaveInstanceState是不会被调用的。 要保存view的状态,至少有两点需要满足: view要有id 要调用setSaveEnabled(true) 都用SparseArray来存储的 7.自定义View 如何考虑机型适配 ? o 合理使用warp_content,match_parent o 尽可能的是使用RelativeLayout o 针对不同的机型,使用不同的布局文件放在对应的目录下,android 会自动匹配。 o 尽量使用点9 图片。 o 使用与密度无关的像素单位dp,sp o 引入android 的百分比布局。 o 切图的时候切大分辨率的图,应用到布局当中。在小分辨率的手机上也会有很好的显示效果。 自定义控件不错的地方 https://www.jianshu.com/p/705a6cb6bfee 手把手教你写一个完整的自定义View(非常不错,view系列) https://www.jianshu.com/p/e9d8420b1b9c