1.Android 花费5年 自定义view面试题都在这 (5分钟入门到牛逼)面试+源码+demo 深圳一线大厂都在学

笔者是面霸,面试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

你可能感兴趣的:(1.Android 花费5年 自定义view面试题都在这 (5分钟入门到牛逼)面试+源码+demo 深圳一线大厂都在学)