【本文出自大圣代的技术专栏 http://blog.csdn.net/qq_23191031】
【转载烦请注明出处,尊重他人劳动成果就是对您自己的尊重】
前言
View在Android的世界中扮演着重要的角色,正是这些控件组成了一个又一个精美的App。View体系是Android界面编程的核心,虽然它不属于四大组件但是它的重要行却毫不逊色,这个系列我会陆续从View的滑动事件、View 的事件反馈、自定义View等多个方面逐步介绍Android View体系。如果能帮助到你,那是我莫大的荣幸。
Android控件框架
在Android的世界中View是所有控件的基类(祖宗),其中也包括ViewGroup在内。View是一个抽象的概念,特指界面中的某一个控件。而ViewGroup是代表着控件的集合,其中可以包含多个View控件,并管理他们。从某种角度上来讲Android中的控件可以分为两大类:View与ViewGroup。通过ViewGroup,整个界面的控件形成了一个树形结构,这也就是我们常说的控件树,上层的控件要负责测量与绘制下层的控件,并传递交互事件。我们在开发中常常使用到的findViewById()方法,就是在控件树中进行深度遍历来查找对应元素的。在每棵控件树的顶部都存在着一个ViewParent对象,它是整棵控件树的核心所在,所有的交互管理事件都由它来统一调度和分配,从而对整个视图进行整体控制。
在每一个Activity中都包含了一个Window,而这个Window通常上是由PhoneWindow实现的,而PhoneWindow又将DecorView设置为整个界面的根布局,DecorView作为根布局将要显示的具体内容呈现在PhoneWindow上,并提供了一些通用方法来操作界面。这里所有View的交互事件都由WindowManagerService(WMS)进行接收,并通过Activity回调相应的onClickListener。
在上面的视图上我们可以看到此时屏幕被分成了两部分:TitleView与ContentView。如图红色的区域就是ContentView,contentView是一个ID为content的Framelayou这也是我们通过布局文件可以控制的区域,实际上我们所有的布局都设置在这样的Fragmelayout中。
这也就是为什么Activity、Fragment中设置根布局的方法叫做setContentView了。
插播: requestWindowFeature(Window.FEATURE_NO_TITLE) 与 setContentView() 调用顺序的关系
在设置setContentView()方法之前我们可以通过
requestWindowFeature(Window.FEATURE_NO_TITLE)
方法设置标签来显示全屏。如果你看了Activity源码中的setContentVeiw()方法你会发现,当setContentView()一旦调用,ContentView布局与TitleView会同时被加载,加载之后在调用requestWindowFeature(Window.FEATURE_NO_TITLE)
方法设置标签已经没有作用了。所以只有在setContentView()
方法之前设置标签才能剔除TitleView
达到ContentView
占据全屏的效果。
当Acitivity的生命周期中,当onCreate()方法中调用setContentView方法后,ActivityManagerService(AMS)会调用onResume()方法,此时系统才会把整个DecorView添加到PhoneWindow中显示出来,至此界面回执完成。
贴一张图汇总一下吧
更详细的说明请参见【Android View源码分析(一)】setContentView加载视图机制深度分析
Android的常用坐标系
在Android的世界中我们最常用到的就是Android坐标系(我认为称为世界坐标系更准确)和视图坐标系了。对于一个控件而言,它在Android世界坐标系中的位置我们可以称之为:绝对坐标系;而在视图坐标系中,指示的就是它的相对位置了。下面我们就来分析一下他们吧
1,世界坐标系
在Android的世界中,屏幕的左上角定点作为世界坐标系的原点,从这个原点水平向右为X轴正方向,原点垂直向下为Y轴正反向。
Android系统中为我们提供了getLocationOnScreen(int[] location)
方法来获取控件在整个屏幕的绝对坐标,此时要注意的是:该坐标是从屏幕的左上角(原点)开始获取的,所以也包括了状态栏的高度,如下图。
1.1,世界坐标系中屏幕区域的划分
通过上图我们可以很直观的看到Android的屏幕区域是如何划分的。接下来我们就看看如何或者这些区域中的坐标和度量方法吧。
//获取屏幕区域的宽高等尺寸获取
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int widthPixels = metrics.widthPixels;
int heightPixels = metrics.heightPixels;
//应用程序App区域宽高等尺寸获取
Rect rect = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
//获取状态栏高度
Rect rect= new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
int statusBarHeight = rectangle.top;
//View布局区域宽高等尺寸获取
Rect rect = new Rect();
getWindow().findViewById(Window.ID_ANDROID_CONTENT).getDrawingRect(rect);
注意:
这些方法最好都在Activity的
onWindowFocusChanged()
方法之后调用,因为在Activity的声明周期中 onCreate、onStar、 onResume这些方法都不是界面visible的真正时刻,真正的visible是在onWindowFocusChanged()
方法执行时才被执行的。(onWindowFocusChanged()
是在onResume()
之后调用的,所以有的文章也会说是onResume()
之后调用,其实更加准确的是在onWindowFocusChanged()
之后,此处我会在以后的文章中做详细介绍)
2,视图坐标系
在日常开发中我们接触最对的就是视图坐标系了,视图坐标系描述的是子控件在父控件中相对位置。贴一张图来说明一下
所谓视图坐标系是以控件(例如图中的TextView)父视图(图中的ViewGroup))的左上角为坐标原点的(绿色部分),从原出发水平向右为x轴正方向,垂直向下为y轴正方向来表示控件的相对位置的。
那么这个相对位置到底如何表示呢,同样看图说话。
简单的总结一下:
View提供的获取坐标方法
通过如下方法可以获得View到其父控件(ViewGroup)的距离:
方法 | 解释 |
---|---|
getTop() | 获取View自身顶边到其父布局顶边的距离 |
getLeft() | 获取View自身左边到其父布局左边的距离 |
getRight() | 获取View自身右边到其父布局左边的距离 |
getBottom() | 获取View自身底边到其父布局顶边的距离 |
getX() | 返回值为getLeft()+getTranslationX(),当setTranslationX()时getLeft()不变,getX()变。 |
getY() | 返回值为getTop()+getTranslationY(),当setTranslationY()时getTop()不变,getY()变。 |
MotionEvent提供的获取坐标方法
我们看上图那个触摸点,我们知道无论是View还是ViewGroup,最终的点击事件都会由onTouchEvent(MotionEvent event)方法来处理,MotionEvent也提供了各种获取焦点坐标的方法:
方法 | 解释 |
---|---|
getX() | 获取点击事件距离控件左边的距离,即视图坐标 |
getY() | 获取点击事件距离控件顶边的距离,即视图坐标 |
getRawX() | 获取点击事件距离整个屏幕左边距离,即绝对坐标 |
getRawY() | 获取点击事件距离整个屏幕顶边的的距离,即绝对坐标 |
注意:
View中的
getX()
、getY()
方法只是与MotionEvent中的getX(
)、getY()
方法只是重名而已,并不是一个。
上面就解释了你在很多代码中看见各种getXXX方法进行数学逻辑运算判断的含义。不过上面只是说了一些相对静止的世界坐标点关系,下面我们来看看几个和上面方法紧密相关的View方法,此处在本篇文章中不是重点,我会在以后的文章中做详细讲解。:
View宽高方法 | 解释 |
---|---|
getWidth() | layout后有效,返回值是mRight-mLeft,一般会参考measure的宽度(measure可能没用),但不是必须的。 |
getHeight() | layout后有效,返回值是mBottom-mTop,一般会参考measure的高度(measure可能没用),但不是必须的。 |
getMeasuredWidth() | 返回measure过程得到的mMeasuredWidth值,供layout参考,或许没用。 |
getMeasuredHeight() | 返回measure过程得到的mMeasuredHeight值,供layout参考,或许没用。 |
参考:
如果说我比别人看得更远些,那是因为我站在了巨人的肩上
《Android群英传第三章》
Android中的坐标系以及获取坐标的方法
Android应用坐标系统全面详解