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都加载实现时走这个函数。
LayoutonMeasure(int, int)view和它的子view要决定尺寸的时候调用。
onLayout(boolean, int, int, int, int)view给它所有的子view指定尺寸和地位。
onSizeChanged(int, int, int, int)当view的尺寸发生变化时走这个办法。
DrawingonDraw(android.graphics.Canvas)view绘制它的内容时走这个办法。
Event processingonKeyDown(int, android.view.KeyEvent)当key事件产生时走这个办法。
onKeyUp(int, android.view.KeyEvent)key up 事件产生
onTrackballEvent(android.view.MotionEvent)光标球挪动事件
onTouchEvent(android.view.MotionEvent)接管到了触摸事件
FocusonFocusChanged(boolean, int, android.graphics.Rect)view取得或失去关注(focus)时调用
onWindowFocusChanged(boolean)view所在的window取得或失去关注(focus)时调用
AttachingonAttachedToWindow()当view关联到window时调用
onDetachedFromWindow()当view与window解除关联时调用
onWindowVisibilityChanged()当view关联的window可见性变动时调用
绘制流程

分为3个阶段:measure、layout、draw

onMeasure 办法中 View 会对其所有的子元素执行 measure 过程,此时 measure 过程就从父容器“传递”到了子元素中,接着子元素会递归的对其子元素进行 measure 过程,如此重复实现对整个 View 树的遍历。onLayout 与 onDraw 过程的执行流程与此相似。

measure 过程决定了 View 的测量宽高,这个过程完结后,就能够通过 getMeasuredHeightgetMeasuredWidth 取得 View 的测量宽高了。

layout 过程决定了 View 在父容器中的地位和 View 的最终显示宽高,getTop 等办法可获取 View 的 top 等四个地位参数(View的左上角顶点的坐标为(left, top), 右下角顶点坐标为(right, bottom))。 getWidthgetHeight 可取得 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.LayoutParamsweight这个属性。 父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先于onTouchEventmOnTouchListener能够拦住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零根底入门教程视频参考