关于android:Android入门教程-View-的介绍

34次阅读

共计 7643 个字符,预计需要花费 20 分钟才能阅读完成。

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 的测量宽高,这个过程完结后,就能够通过 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   onCreate
View       LifeView(Context context, @Nullable AttributeSet attrs)
View       onFinishInflate
Activity   onStart
Activity   onResume
View       onAttachedToWindow
View       onMeasure
View       onSizeChanged
View       onLayout
View       onDraw
View       onWindowFocusChanged  true
View       onMeasure
View       onLayout
View       onDraw
Activity   onPause
View       onWindowFocusChanged  false
Activity   onStop
Activity   onDestroy

能够看出,在 activity 可见(onResume)后,view 与 window 关联起来。先测量,决定本身大小,决定在父容器中的地位和大小,而后绘制到屏幕上。依据下面的 log,咱们也能够看出 View 的次要绘制流程是 measure、layout、draw。

onDraw 和 invalidate()

间断屡次调用 invalidate()onDraw 的执行次数是多少次。

[act] 屡次 invalidate
View 生命周期 onDraw, 1535337048483
View 生命周期 onDraw, 1535337048499
View 生命周期 onDraw, 1535337048516
View 生命周期 onDraw, 1535337048532
View 生命周期 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 零根底入门教程视频参考

正文完
 0