1.Android 花费5年 自定义view面试题都在这
笔者是面霸,面试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会填充父控件。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
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系列)