View 是用户交互的根本组件。一个 View 占据了屏幕上的一个方形区间,可能绘制图像并处理事件。 View 是 UI 的根底,咱们后面看的 TextView , Button , LinearLayout , RelativeLayout 其实都是 View 的子类。 子类 ViewGroup 是各种 layout 的基类。 ViewGroup 可装载 View 和其它 ViewGroup 。
应用 View
屏幕上的所有 view 都同属于1棵树(tree)。能够用代码来增加 view 或者在 layout 的 xml 文件中指定。 View的子类有很多,可显示文字( TextView )、图片( ImageView )、网页( WebView)等。
view 的树形构造例子
有一些通用的操作:
- 设置属性:比方给TextView设置文字内容。每种子类都有不同的办法。在xml中也能够指定view的内容。
- 设置关注:framework会解决用户输出时的挪动关注。要强行关注某个view,请调用
requestFocus()
。 - 设置监听器:View容许客户端设置一些监听器。例如所有的view都能设置监听器去监听focus事件。 开发者能够用
setOnFocusChangeListener(android.view.View.OnFocusChangeListener)
来设置focus事件监听。 也可监听点击事件。 - 设置是否可见:用
setVisibility(int)
办法显示或暗藏view
不要被动调 measure layout draw 的相干办法
- ndroid framework 会解决 View 的测量(measure),布局(layout)和绘制(draw)工作。 开发者不须要被动调用相干的办法。除非是自定义了ViewGroup。
View 的回调函数
咱们关注过 Activity , Service 等等组件的回调函数(生命周期)。这里介绍 View 的回调函数。
分类 | 办法 | 阐明 |
---|---|---|
创立 | 结构器 | 用代码创立View用的结构器和加载layout中的View用的结构器不同。从layout加载View时,会解析xml中定义的一些参数。 |
onFinishInflate() | 从xml中加载view,当它以及它的所有子view都加载实现时走这个函数。 | |
Layout | onMeasure(int, int) | view和它的子view要决定尺寸的时候调用。 |
onLayout(boolean, int, int, int, int) | view给它所有的子view指定尺寸和地位。 | |
onSizeChanged(int, int, int, int) | 当view的尺寸发生变化时走这个办法。 | |
Drawing | onDraw(android.graphics.Canvas) | view绘制它的内容时走这个办法。 |
Event processing | onKeyDown(int, android.view.KeyEvent) | 当key事件产生时走这个办法。 |
onKeyUp(int, android.view.KeyEvent) | key up 事件产生 | |
onTrackballEvent(android.view.MotionEvent) | 光标球挪动事件 | |
onTouchEvent(android.view.MotionEvent) | 接管到了触摸事件 | |
Focus | onFocusChanged(boolean, int, android.graphics.Rect) | view取得或失去关注(focus)时调用 |
onWindowFocusChanged(boolean) | view所在的window取得或失去关注(focus)时调用 | |
Attaching | onAttachedToWindow() | 当view关联到window时调用 |
onDetachedFromWindow() | 当view与window解除关联时调用 | |
onWindowVisibilityChanged() | 当view关联的window可见性变动时调用 |
绘制流程
分为3个阶段:measure、layout、draw
在 onMeasure
办法中 View 会对其所有的子元素执行 measure 过程,此时 measure 过程就从父容器“传递”到了子元素中,接着子元素会递归的对其子元素进行 measure 过程,如此重复实现对整个 View 树的遍历。onLayout 与 onDraw 过程的执行流程与此相似。
measure 过程决定了 View 的测量宽高,这个过程完结后,就能够通过 getMeasuredHeight
和getMeasuredWidth
取得 View 的测量宽高了。
layout 过程决定了 View 在父容器中的地位和 View 的最终显示宽高,getTop
等办法可获取 View 的 top 等四个地位参数(View的左上角顶点的坐标为(left, top), 右下角顶点坐标为(right, bottom))。 getWidth
和getHeight
可取得 View 的最终显示宽高(width = right - left;height = bottom - top)
。
draw 过程决定了 View 最终显示进去的样子,此过程实现后,View 才会在屏幕上显示进去。
自定义一个类继承View,放到activity的layout中,打印log察看各个函数调用状况
Activity onCreateView LifeView(Context context, @Nullable AttributeSet attrs)View onFinishInflateActivity onStartActivity onResumeView onAttachedToWindowView onMeasureView onSizeChangedView onLayoutView onDrawView onWindowFocusChanged trueView onMeasureView onLayoutView onDrawActivity onPauseView onWindowFocusChanged falseActivity onStopActivity onDestroy
能够看出,在 activity 可见(onResume
)后,view 与 window 关联起来。 先测量,决定本身大小,决定在父容器中的地位和大小,而后绘制到屏幕上。 依据下面的 log,咱们也能够看出 View 的次要绘制流程是 measure、layout、draw。
onDraw 和 invalidate()
间断屡次调用 invalidate()
,onDraw
的执行次数是多少次。
[act] 屡次 invalidateView生命周期 onDraw, 1535337048483View生命周期 onDraw, 1535337048499View生命周期 onDraw, 1535337048516View生命周期 onDraw, 1535337048532View生命周期 onDraw, 1535337048549
从例子中可看出,onDraw
执行的距离大概是 16ms。和 Android 刷新界面的工夫距离靠近。
再看 invalidate()
办法正文,粗心是当 view 可见时,onDraw
办法将在将来某个工夫点被调用。
/** * Invalidate the whole view. If the view is visible, * {@link #onDraw(android.graphics.Canvas)} will be called at some point in * the future. * .... */public void invalidate() { invalidate(true);}
也就是说,调用了 invalidate()
办法后,并不保障会立刻执行 onDraw
。
ID
View 可能会有一个与之关联的数字 id。通常来说这些是在 layout xml 文件中调配的 id。 常见用法是:
定义一个 Button 在 layout 中并且调配一个 id
<Button android:id="@+id/my_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/my_button_text"/>
在 Activity 的 onCreated
办法里通过 id 找到这个 Button
Button myButton = findViewById(R.id.my_button);
view 的 ID 并不需要全局举世无双的,而是要在它所属的树里是惟一的。
地位 Position
一个 view 占据一个方形的地位。view 的地位用左上角的点来示意。地位和尺寸的单位是像素(pixel)。 调用 getLeft()
和 getTop()
能够取得 view 的坐标。getLeft()
返回 view 的 left 值,或者说是x值。getTop()
返回top值,或者说是y值。这些办法返回的坐标是 view 在它的父 view 中的地位。例如,假如getLeft()
返回20,示意这个view在它父view左边缘往右20个像素的地位。
另外,getRight()
办法能返回 view 的 right 值。getBottom()
办法返回 bottom 值。 getRight() == getLeft() + getWidth()
Size, padding 和 margins
view 的尺寸(size)有宽(width)和高(height)。一个 view 实际上有两对宽高值。
第一对宽高是测量宽高(measured width/height)。这个尺寸示意一个 view 想要在父 view 里要多大。通过 getMeasuredWidth()
和 getMeasuredHeight()
办法能够失去测量宽高。
第二对宽高能够了解为理论宽高。这个宽高有可能与测量宽高不同。通过 getWidth()
和 getHeight()
可拿到宽高值。
为了测量尺寸,view 须要思考 padding 值。padding 值的单位是像素(px),分为左上右下(left,top,right,bottom)。 padding可用于将view的内容偏移特定数量的像素。 例如,左padding为2像素的时候,会把view的内容从左向右推2个像素。 能够用setPadding(int, int, int, int)
或者setPaddingRelative(int, int, int, int)
办法设置padding值。 用getPaddingLeft()
, getPaddingTop()
, getPaddingRight()
, getPaddingBottom()
, getPaddingStart()
, getPaddingEnd()
获取对应的padding值。
view能够设定padding值,但没有margin值。ViewGroup能反对margin值。
setX 与 setTranslationX的区别
首先来看 getX()
与 getTranslationX()
的区别。
getX()
取得view在屏幕中的x坐标。getTranslationX()
取得绝对于起始地位x的差值。
例如一个 view 初始时程度居中在屏幕中。getX, Y = [536.0000, 0.0000]; TranslationX,Y = [0.0000, 0.0000];
。
setX
指定了view在父视图中的地位。setTranslationX
指定了绝对于初始地位的地位。
如果咱们想让view回到初始地位,能够间接调用setTranslationX(0)
。
setTranslationX
能够利用在drawerLayout中,弹出抽屉视图时让主视图跟着挪动。
Layout
layout(布局)有2个过程:测量(measure)过程和布局(layout)过程。测量过程在 measure(int, int)
办法里实现,view树从顶往下遍历测量一遍。在遍历过程中,每个view把尺寸信息往下传。在测量过程中,每个view都存下了它的测量值。 第二个过程在布局办法中layout(int, int, int, int)
,也是自顶向下进行的。 测量过程中,每个父view都用测量值来负责定位它的子view。
当 view 的measure()
办法执行结束,getMeasuredWidth()
和getMeasuredHeight()
的返回值曾经确定了;它的子view同理。 一个view的测量宽高必须恪守它的父view的限度。这保障了测量过程的最初,所有的父view都能承受它们子view的测量后果。一个父view可能会屡次调用它的子view的measure()
办法。 例如,父view可能会传未指定尺寸给子view,来搞清楚子view想要多大的尺寸。如果子view的未固定尺寸太小或者太大,就给子view的measure
办法传确定的参数。 测量过程用2个类来示意尺寸。MeasureSpec类用来通知父view想要的尺寸和地位。 根底的LayoutParams类形容宽高想要多大。对于宽或高,可用以下的设定:
- 一个确切数字
MATCH_PARENT
,示意和父view一样大(要扣除padding值)WRAP_CONTENT
,能蕴含本人的内容即可(要思考本人的padding值)
对于不同的ViewGroup的子类,有对应的不同的LayoutParams子类。例如LinearLayout.LayoutParams有weight
这个属性。 父view传递给子view,用MeasureSpecs来传递要求。MeasureSpecs有3种模式:
UNSPECIFIED
父控件对子控件不加任何解放,子元素能够失去任意想要的大小,这种MeasureSpec个别是由父控件本身的个性决定的。比方ScrollView,它的子View能够随便设置大小,无论多高,都能滚动显示,这个时候,size个别就没什么意义。EXACTLY
父view给一个确定的尺寸数值给子view。心愿子View齐全依照本人给定尺寸来解决。AT_MOST
父view给一个最大值,心愿子view不能超出限度。
事件处理和线程
一个 view 的根本解决流程如下
- 来了一个事件,散发到对应的 view。这个 view 处理事件,并告诉相干的监听器。
- 如果在处理事件过程中,view的边界须要变动,会调用
requestLayout()
。 - 相似的,如果view的内容发生变化,会调用
invalidate()
。 - 调用
requestLayout()
或者invalidate()
,framework会在失当工夫解决好测量,布局和绘制的过程。
UI 线程:
解决 view 的线程叫做 UI 线程。必须在 UI 线程操作 view。 如果在其余线程里想要操作 view,能够思考应用 Handler
触摸事件形容
触摸事件散发
由根视图向子 view 散发。onInterceptTouchEvent
办法(ViewGroup才有)的返回值决定是否拦挡触摸事件(true:拦挡,false:不拦挡)。 如果 ViewGroup 拦挡了触摸事件,那么其 onTouchEvent
就会被调用用来解决触摸事件。 散发的过程中,ViewGroup能够拦挡事件,不再持续散发。
触摸事件生产
onTouchEvent
办法的返回值决定是否解决实现触摸事件
- true:曾经解决实现,不须要给父 ViewGroup 解决
- false:还没解决实现 ,须要传递给父 ViewGroup 解决
onTouch,onTouchEvent区别
onTouch
先于onTouchEvent
,mOnTouchListener
能够拦住onTouchEvent
; 有mOnTouchListener
,则执行mOnTouchListener.onTouch
办法。 onTouchEvent
获取手机屏幕的触摸事件。
View 相干面试题
1. View 绘制流程
触发 addView 流程:
2. MeasureSpec 是什么?
MeasureSpec示意的是一个32位的整形值,它的高2位示意测量模式SpecMode,低30位示意某种测量模式下的规格大小SpecSize。MeasureSpec是View类的一个动态外部类,用来阐明应该如何测量这个View。它由三种测量模式,如下:
- EXACTLY:精确测量模式,视图宽高指定为match_parent或具体数值时失效,示意父视图曾经决定了子视图的准确大小,这种模式下View的测量值就是SpecSize的值。
- AT_MOST:最大值测量模式,当视图的宽高指定为wrap_content时失效,此时子视图的尺寸能够是不超过父视图容许的最大尺寸的任何尺寸。
- UNSPECIFIED:不指定测量模式, 父视图没有限度子视图的大小,子视图能够是想要的任何尺寸,通常用于零碎外部,利用开发中很少用到。
MeasureSpec 通过将 SpecMode 和 SpecSize 打包成一个 int 值来防止过多的对象内存调配,为了不便操作,其提供了打包和解包的办法,打包办法为makeMeasureSpec,解包办法为 getMode 和 getSize。
3. 子 View 创立 MeasureSpec 创立规定是什么?
依据父容器的 MeasureSpec 和子 View 的 LayoutParams 等信息计算子 View 的MeasureSpec
4. 自定义 Viewwrap_content 不起作用的起因
因为 onMeasure()->getDefaultSize(),当 View 的测量模式是 AT_MOST 或EXACTLY 时,View 的大小都会被设置成子 View MeasureSpec 的 specSize。
public static int getDefaultSize(int size, int measureSpec) { switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }
5. 为什么 onCreate 获取不到 View 的宽高
Activity 在执行完 oncreate,onResume 之后才创立 ViewRootImpl,ViewRootImpl 进行 View 的绘制工作调用链
startActivity -> ActivityThread.handleLaunchActivity -> onCreate -> 实现 DecorView 和 Activity 的创立 -> handleResumeActivity -> onResume() -> DecorView 增加到WindowManager -> ViewRootImpl.performTraversals()办法,测量(measure),布局(layout),绘制(draw), 从 DecorView 自上而下遍历整个 View 树。
Android零根底入门教程视频参考