0%

重新认识 View(一)

1. 前言

Android中View的重要性堪比四大组件,甚至有过之而无不及。View是Android提供给开发者呈现UI的方式。在越来越重视用户体验的时代,UI是App的门面,想要快速高效地做出各种炫酷页面,必须要了解Android-View的工作原理。

2. View的绘制

想象一下,如果让你来设计一套UI系统,用户可以往屏幕上画各式各样的图案。你会怎么设计呢?类似于画画,首先,我们需要确定图案大小,其次我们需要确定图案的位置,最后我们可能需要确定用什么颜色去画。这个过程其实和Android中View的绘制过程相差无几。View的绘制过程也会经历onLayout(位置)、onMesure(大小)、onDraw(着色)三个流程。

2.1 measure过程

measure过程有一个重要你的类MeasureSpec,通过这个类把一个int类型数据解包出View测量的模式和View绘制的大小.View的测量模式有三种

  • EXACTLY
    当View的layout_width或layout_height属性为match_parent或者固定值时,就是EXACTLY模式(如layout_width=”20dp”或layout_width=”match_parent”)

  • AT_MOST
    当View的layout_width或layout_height属性为wrap_content时便是使用AT_MOST模式

  • UNSPECIFIED
    不常见,View想要多大给多大。通常在自定义View才能使用得到。
    注意:自定义View时,如果想要View支持wrap_content的方式,必须重写onMeasure方法,并返回默认值,否则在布局中使用wrap_content相当于match_parent。具体原因可以看ViewGroup.getChildMeasureSpec。wrap_conten模式下的SpecMode为AT_MOST,此时childSize就是parentSize。

2.2 layout过程

layout主要是确定View在父控件中的位置,因为不同的控件layout方法不同,所以在View和ViewGroup中并没有真正实现onLayout方法,而是交给子类去实现。layout过程中会通过setFrame方法确定View四个顶点(mLeft、mTop、mRight、mBottom)位置,此时View的大小和位置便确定下来。

2.3 draw过程

draw过程相对简单,主要在前两步基础上在固定位置、画相应大小的图形即可。

  • 画背景background.draw(canvas)
  • 画View本身 View.onDraw
  • 画子View (dispatchDraw)
  • 画其他(onDrawScrollBars)

3. 自定义View常用方法

  • 继承View 重写onMesure、onLayout、onDraw方法

  • 继承系统View实现自定义效果(如继承TextView更改字体继承ViewGroup实现竖向ViewPager等等),这是一种非常常见的自定义View方式

  • 使用继承FrameLayout,然后使用View.inflate()添加自定义布局,这种适合封装成通用控件(如App的搜索框)

4. View的事件分发

当一个事件MotionEvent产生后,系统需要把事件传递给各个View.其分发机制需要靠三个方法完成

  • public boolean dispatchTouchEvent(MotionEvent ev) 用来传递View事件,如果事件能够传递到本View,则该方法一定会调用
  • public boolean onInterceptTouchEvent(MotionEvent ev) 表示在事件分发过程中,是否要拦截事件,如果拦截事件,则会调用本View的onTouchEvent方法
  • public boolean onTouchEvent(MotionEvent) 处理事件,如果该方法返回true,表示该事件已经被处理,不会再传递给下面的子view.
    对于每一个层级的View来说,基本上都会走如下流程:
    先判断本View需不需要拦截View事件,如果需要拦截,则走到本View.onTouchEvent方法中如果不需要,则继续分发事件给子View.

其伪代码如下:

1
2
3
public boolean dispatchTouchEvent(MotionEvent ev) {
return !onInterceptTouchEvent(ev) ? this.onTouchEvent(ev) : child.dispatchTouchEvent(ev);
}

举个更形象的例子,假设公司只有三个人,CEO,Manager(你上司)、你,来了个活,CEO看了一下,这事so easy,交给下面的人办就可以了(onInterceptTouchEvent()=false),先把这活分给(dispatch)经理,经理也觉得这活比较简单,也直接(dispatch)给你了,这活给了你以后,你没有下属了,只能是自己来处理了(这也是View没有onInterceptTouchEvent方法和onTouchEvent默认返回true的原因之一)。如果你发现你自己仍然不能处理好该活(onTouchEvent=false),那只能继续上报你的经理,如果经理能处理(onTouchEvent=true),则不用再继续上报给CEO,自己处理就好了。

在另外一种场景之下,如果在事件分发阶段,CEO或者经理发现这事不需要下面的人去解决,自己搞定的话,只需把onInterceptTouchEvent() = true,然后在自己的**onTouchEvent()**方法中处理该事件即可。

事件总是先传递到Activity,Activity再传递到window,经由window传递给view,如果事件传递过程中,中间没有任何对象消费掉该事件,则最后由Activity来处理(Activity.onTouchEvent)

5. 参考

《Android开发艺术探索》
《Android群英传》