Android中的View类代表用户界面中基本的构建块。一个View在屏幕中占据一个矩形区域、并且负责绘制和事件处理。View是所有widgets的基础类,widgets是我们通常用于创建和用户交互的组件,比如按钮、文本输入框等等。子类ViewGroup是所有布局(layout)的基础类。layout是一个不看见的容器,里面堆放着其他的view或者ViewGroup,并且设置他们的布局属性。

所有的view在窗口中是以树状结构来管理。你可以通过代码或者编辑xml布局文件来添加一个view。view有很多的子类来负责控制或者有能力展示图片,文字等等。

使用:

一旦你创建了一个view的树状结构。你通常做以下的常见操作。

1,设置属性,比如可以给一个TextView设置文本属性。每一个view都有特定的属性和设置他们的方法。一些可以遇见的属性可以在view的xml文件中提前设置。

2,设置焦点,Android框架本身可以处理用户移动的焦点事件。你可以强制调用requestFocus()方法,来聚焦一个view。

3,设置监听器,view允许客户端来设置监听器,监听感兴趣的事件。当感兴趣的事件发生,系统会通知监听器。比如,所有的view都支持你设置获得和失去焦点事件的监听器,你只需要通过方法setOnFocusChangedListener(android.view.View.OnFocusChangedListener)来设置。其他的view子类都支持自己特性的事件监听器设置,比如Button支持设置点击事件监听器。

4,设置可见性,你可以通过方法setVisibility()来设置view的隐藏和可见。

Android框架会负责view的测量(measure)、布局(layout)、绘制(draw)。一般情况下你不需要调用相关的方法,除非你自己定义了一个view。

自定义view

你如果自定义实现一个view,你需要覆盖重写view中的一些被Android框架调用的标准方法。当然你也没有必要把所有的方法都覆盖重写,但你应该从onDraw方法开始。

类别方法描述
创建构造方法创建view时,分为两种情况,一种是写代码创建,一种是从xml文件中创建,第二种情况会转化和应用你在xml设置的view属性。
onFinishInflate()在该view和子view都从xml文件中创建完成时被调用。
布局onMeasure(int, int)确定view和子view的大小要求时被调用。
onLayout(boolean, int, int, int, int)给view的子view确定大小和位置的时候调用。
onSizeChanged(int, int, int, int)当view的大小变化时被调用
绘制onDraw(android.graphics.Canvas)当view需要渲染他的内容时被调用。
事件处理onKeyDown(int, android.view.KeyEvent)当物理按键按下时,被调用。
onKeyUp(int, android.view.KeyEvent)当物理按键抬起时,被调用。
onTrackballEvent(android.view.MotionEvent)当轨迹球运动事件发生时,被调用。
onTouchEvent(android.view.MotionEvent)当触摸屏幕事件发生时,被调用。
焦点onFocusChanged(boolean, int, android.graphics.Rect)获得、失去焦点时,被调用。
onWindowFocusChanged(boolean)包含视图的屏幕获得、失去焦点的时候被调用。
附着onAttachedToWindow()当view附着到window时,被调用。
onDetachedFromWindow()当view从window上剥离,被调用。
onWindowVisibilityChanged(int)包含视图的window的可见性发生变化时,别调用。

 

ID

view都有一个Integer属性的id,这个id一般是在定义viewxml文件中设置的。被用于在指定的view树中查找view。常见的一种形式:

在布局文件中定义个按钮,并给它设置唯一的id

 <Button
     android:id="@+id/my_button"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:text="@string/my_button_text"/>
 

在Activity的onCreate方法中查找这个按钮:

 Button myButton = findViewById(R.id.my_button);
 

view的id不需要在整个应用中都保持唯一,但你需要在它所在的view视图树状结构中保持唯一。

位置:

一个view的形状是一个矩形,每一个view都有一个位置,被表示为一个坐标(left,top)。还有两个尺寸,width和height。位置和尺寸的单位都是像素(pixel)。

你可以通过调用getLeft()和getTop()方法来获取一个view 的位置。前者返回的是left或者X坐标,后者返回的是top或者Y坐标。这些方法返回的位置是相对于该view的父view来说的,比如,getLeft()方法返回了20,这就是说这个view位于他的直接父view左边框的右边20像素的地方。

另外view还提供了另外一些关于位置的方法,减少你的运算。getRight()方法和getBottom()方法返回view矩形的right和botom边对应的坐标。比如调用getRight()方法等同于调用getLeft()+ getWidth()方法。

Size, padding and margins

一个view的size被width和height来指定,一个veiw通常情况下有两对width和height值。

第一对值呢是测量宽(measured width)和测量高(measured height)。这个值的含义是这个view想要在他的父view中有多大。这个值可以通过getMeasuredWidth()和getMeasuredHeight()方法获取。

第二对就是宽(width)和高(height),有时被称为绘制宽和绘制高,代表这view的实际宽和高,这两个值可能,会和测量宽和高不一样,可以通过getWidth()方法和getHeight()方法来获取相应的值。

测量一个view的值,会考虑到他的填充(padding)值。padding值同样以像素作为单位,包含left、top、right、bottom四个值。padding用来设置view的内容和view的边界偏离值。比如 left padding的值是2,意味着view的内容相对view的左边移动2个像素。padding可以用方法setPadding(int,int,int,int)或setPaddingRelative(int,int,int,int)方法来设置。可以用方法getPaddingLeft(),getPaddingTop(),getPaddingRight(),getPaddingBottom(),getPaddingStart(),getPaddingEnd()来查询相关值。

尽管一个view可以设置padding值,但是他不支持margin值,然而,view group可以设置margin。查看VIewGroup.MarginLayoutParams获取更多相关知识。

Layout(布局)

Layout包含两个过程,一个测量(mesasure)、一个布局(layout)。测量是在继承的measure(int,int)方法里面来进行,并且是至上而下的遍历view的树状结构,每一个view都将尺寸规格推到tree中,在测量完成后,每一个view都保存了自己的测量值,第二个过程发生在layout(int,int,int,int),并且也是自顶向下,在这个过程中每一个父view都负责用第一个工程测量的值来定位它的子view。

等view的measure()方法结束,view 的getMeasuredWidth()和getMeasuredHeight()值一定会被设置,***一个view的测量宽和测量高的值都必须遵守父亲节点设置的约束。这样保证了,在measure过程结束后,所有的父亲view节点都接受了其子view节点的测量值,一个父亲view节点针对它的子节点view可能会不止一次的调用measure方法,例如,父亲view节点会测量它的每一个为指定尺寸的(unspecified)子view节点一次,来找出子view节点想要多大,然后,再次调用measure方法使用真实的的值,如果所有的不受约束的子view节点的大小总和太大或者太小。

测量过程使用两个类来计算大小,MeasureSpec类被用于告诉父亲view节点,它们想要多大和怎么定位。LayoutParams类仅仅描述了view要想多大的宽和高,分以下三种情况。

  • 一个确定的值。
  • MATCH_PARENT,想要和父亲view节点一样大,但是要减去padding值。
  • WRAP_CONTENT, 包裹view的内容大小。但是要加上padding值。

Android框架中有许多针对不同的ViewGroup有不同的LayoutParams的子类。比如,AbsoluteLayout有它自己的子类,LayoutParmams来添加X,Y值。

MeasureSpecs被用于将要求由父亲view传递到子view。一个MeasureSpec可以是以下三个值中的任意一种。

  • UNSPECIFIED:被父亲view节点用于来决定子view节点想要的大小,例如,一个LinearLayout可以调用子 view节点的measure()方法来计算它想要多高,这个子view的height被指定为UNSPECIFIED,宽是确定的值240像素。
  • EXACTLY:被父亲view节点用于使用一个确定的值来指定它的子view的大小。这个子 view节点必须是这个大小,另外这个子view的所有子view节点都必须在这个大小范围内来调整自己的大小。
  • AT_MOST:被父亲节点用于来给子view节点一个最大的值,子view节点以及它的子 view节点都必须调整以适合这个大小。

初始化一个layout,可以调用requestLayout()方法,这个方法通常会被view自身调用,在它自己认为不再适合当前的范围的时候。

绘制
绘制是通过遍历树并记录需要更新的任何视图的绘图命令来处理的。在这之后,整个树的绘制命令被发送到screen,剪切到新损坏的区域。

这棵树很大程度上是按顺序记录和绘制的,而父亲view节点则是在此之前绘制的。他们的孩子和兄弟姐妹按照他们在树上出现的顺序绘制出来。如果您为视图设置了一个背景drawable,那么视图将在调用其onDraw()方法之前绘制它。可以使用ViewGroup中的ViewGroup#setChildrenDrawingOrderEnabled(boolean)和视图上的setZ(float)自定义Z值}覆盖子view绘制顺序。

若要强制视图绘图,请调用invalidate()。

事件处理和线程
视图的基本周期如下:

  • 事件进入并被分派到适当的视图。视图处理事件并通知任何侦听器。
  • 如果在处理事件的过程中,视图的边界可能需要更改,那么视图将调用requestLayout()。
  • 类似地,如果在处理事件的过程中可能需要更改视图的外观,则视图将调用invalidate()。

如果调用了requestLayout()或invalidate(),框架将根据需要度量、布局和绘制树。

注意:整个视图树是单线程的。在调用任何视图上的任何方法时,必须始终处于UI线程上。如果您正在处理其他线程,并且希望从该线程更新视图的状态,则应该使用处理程序。

焦点处理
该框架将处理响应用户输入的常规焦点移动。这包括在视图被删除或隐藏时,或在新视图可用时更改焦点。视图表明它们愿意通过isfocavailable()方法进行关注。要更改视图是否可以获取焦点,请调用setfocavailable (boolean)。当处于触摸模式(见下面的注释)时,视图指示他们是否仍然希望通过isFocusableInTouchMode()进行聚焦,并且可以通过setFocusableInTouchMode(boolean)进行更改。

焦点移动是基于在给定方向上找到最近邻居的算法。在极少数情况下,默认算法可能与开发人员的预期行为不匹配。在这些情况下,您可以使用布局文件中的这些XML属性提供显式覆盖:

nextFocusDown
nextFocusLeft
nextFocusRight
nextFocusUp


要获得一个特定的视图来获取焦点,请调用requestFocus()。

触摸模式


当用户通过方向键(如D-pad)在用户界面中导航时,有必要将焦点放在可操作的项(如按钮)上,这样用户就可以看到需要输入什么。但是,如果设备具有触摸功能,并且用户开始通过触摸与界面交互,则不再需要总是突出显示或关注特定的视图。这激发了一种名为“触摸模式”的交互模式。

对于具有触摸功能的设备,一旦用户触摸屏幕,设备将进入触摸模式。从现在开始,只有isFocusableInTouchMode()为真的视图才是可聚焦的,比如文本编辑小部件。其他可触摸的视图,如按钮,在触摸时不会聚焦;它们只会触发on click侦听器。

每当用户按下方向键,例如D-pad方向,视图设备就会退出触摸模式,并找到一个视图进行对焦,这样用户就可以在不再次触摸屏幕的情况下继续与用户界面交互。

触摸模式状态是跨活动维护的。调用isInTouchMode()查看设备当前是否处于触摸模式。

滚动
框架为希望在内部滚动其内容的视图提供了基本支持。这包括跟踪X和Y滚动偏移量以及绘制滚动条的机制。有关详细信息,请参见scrollBy(int, int)、scrollTo(int, int)和awakenScrollBars()。

标签
与id不同,标记不用于标识视图。标签本质上是可以与视图关联的额外信息。它们通常用于方便地在视图本身中存储与视图相关的数据,而不是将它们放在单独的结构中。

标签可以用布局XML中的字符序列值指定,可以使用android:tag属性作为单个标签,也可以使用<tag>子元素作为多个标签:

     <View ...
           android:tag="@string/mytag_value" />
     <View ...>
         <tag android:id="@+id/mytag"
              android:value="@string/mytag_value" />
     </View>
 

还可以使用代码中的任意对象使用setTag(java.lang.Object)或setTag(int, java.lang.Object)指定标记。

主题
默认情况下,视图是使用提供给其构造函数的上下文对象的主题创建的;但是,可以使用layout XML中的android:theme属性指定不同的主题,或者从代码中将ContextThemeWrapper传递给构造函数。

当android:theme属性在XML中使用时,指定的主题应用于通胀上下文的主题之上(请参阅LayoutInflater),并用于视图本身和任何子元素。

在下面的示例中,将使用材质深色方案创建两个视图;但是,由于使用了一个覆盖主题,它只定义了属性的子集,android:colorAccent的值将保留在通货膨胀上下文的主题(例如活动主题)上定义。

     <LinearLayout
             ...
             android:theme="@android:theme/ThemeOverlay.Material.Dark">
         <View ...>
     </LinearLayout>
 

属性
视图类公开一个ALPHA属性,以及几个与转换相关的属性,如TRANSLATION_X和TRANSLATION_Y。这些属性以属性形式和类似命名的setter/getter方法(如ALPHA的setAlpha(float))都可用。这些属性可用于设置与视图上这些呈现相关属性关联的持久状态。属性和方法也可以与基于动画的动画结合使用,动画部分将对此进行详细描述。

动画
从Android 3.0开始,视图动画的首选方式是使用android.animation api。这些基于动画的类更改视图对象的实际属性,如alpha和translationX。这种行为与3.0之前的基于动画的类形成了对比,3.0之前的类只对视图在屏幕上的绘制方式进行动画。特别是,ViewPropertyAnimator类使这些视图属性的动画特别容易和有效。

或者,您可以使用3.0之前的动画类来动画视图的呈现方式。您可以使用setAnimation(android.view.animation.Animation)或startAnimation(android.view.animation.Animation)将动画对象附加到视图。动画可以随着时间改变视图的缩放、旋转、平移和alpha值。如果动画附加到具有子视图,则动画将影响该节点所根的整个子树。当动画启动时,框架将负责重绘适当的视图,直到动画完成。

安全
有时,应用程序必须能够在用户完全知情和同意的情况下验证正在执行的操作,例如批准许可请求、购买或单击广告。不幸的是,恶意应用程序可能通过隐藏视图的预期用途,在不知情的情况下试图欺骗用户执行这些操作。作为补救措施,该框架提供了一个触摸过滤机制,可用于提高视图的安全性,从而提供对敏感功能的访问。

要启用触摸过滤,请调用setfiltertoucheswhen(boolean)或将android: filtertoucheswhenlayout属性设置为true。当启用时,框架将丢弃视图窗口被另一个可见窗口遮挡时接收到的触摸。因此,当toast、对话框或其他窗口出现在视图窗口上方时,视图将不会接收触摸。

要对安全性进行更细粒度的控制,请考虑覆盖onFilterTouchEventForSecurity(android.view.MotionEvent)方法来实现您自己的安全策略。参见MotionEvent # FLAG_WINDOW_IS_OBSCURED。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐