Android触摸滑动全解(三)——View坐标体系详解

当我们触摸屏幕上的View时,有时候想要获取此时View的一些属性状态,比如说在屏幕的坐标,或者相对于父布局的坐标,或者View的宽高等,但是由于View有很多属性,我们很苦恼不知道应该去选择哪个方法去调用,今天,我们就梳理一下View的坐标体系。

一、屏幕区域划分

Android系统的屏幕区域划分如图:


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);

二、View坐标轴

我们平时的开发工作中,一般都是在APP的区域,因此我们比较关系的是APP部分的坐标体系。
Android系统和我们平时接触的坐标轴不一样,它是以屏幕左上角为原点,向右为X正方向,向下为Y轴正方向,因此屏幕左上角坐标为(0,0)。


View坐标体系

1、View的尺寸和相对于父布局的位置

1.1 View的位置(相对于父布局)

1.1.1 View的初始位置(在XML中布局时的位置)

View中有四个属性:

 protected int mLeft; protected int mRight; protected int mTop; protected int mBottom; 

其值由layout过程的四个参数(l,t,r,b)确定。这四个参数的设置一般会参考measure过程中测量出来的值。View的四个属性值表示layout过程中确定的基本位置。含义如下图所示,坐标系是父View的视图坐标:


View的位置

并且有四个方法获取它们:

  • view.getLeft():View左侧到父View左侧的距离。
  • view.getRight():View右侧到父View左侧的距离。
  • view.getTop():View上侧到父View上侧的距离。
  • view.getBottom():View下侧到父View上侧的距离。

可通过两个方法改变它们的值:

  • view.offsetLeftAndRight(int offset):改变mLeftmRight的值,offset为正View整体位置向右偏移,为负则向左偏移。
  • view.offsetTopAndBottom(int offset):改变mTopmBottom的值,offset为正View整体位置向下偏移,为负则向上偏移。
1.1.2 获取移动后的偏移量

View中还有两个方法可以设置View的偏移量,这两个方法可以改变当前View的位置:

  • view.setTranslationX(int offset)offset为正View整体位置向右偏移,为负则向左偏移。
  • view.setTranslationY(int offset)offset为正View整体位置向下偏移,为负则向上偏移。

相应的获取偏移量:

  • view.getTranslationX():获取View在X轴方向的偏移量。
  • view.getTranslationY():获取View在Y轴方向的偏移量。
1.1.3 获取View当前的位置

View中还有两个方法可以获取View当前的位置:

  • view.getX():获取View在X轴方向的当前位置,返回值为getLeft()+getTranslationX(),当setTranslationX()getLeft()不变,getX()变。
  • view.getY():获取View在Y轴方向当前位置,返回值为getTop()+getTranslationY(),当setTranslationY()getTop()不变,getY()变。

同样的,也可以通过setX()setY()来改变getXgetY()的值,它们相当于设置setTranslationX(int offset)setTranslationY(int offset)

1.1.4 三种获取位置方法总结(以X轴举例)
  • view.getLeft():布局后View相对于父布局的原始距离。
  • view.getTranslationX():布局后如果View有移动,那么可通过此方法获取View移动后的偏移量。
  • view.getX():布局后如果View没有移动,那么此方法获取的值等同于getLeft(),如果View有移动,此方法获取的值等同于getLeft()+getTranslationX()

1.2 View的尺寸

View的尺寸也就是View的宽高,获取方法有两种:

1.2.1 获取宽高:
public final int getWidth() {    return mRight - mLeft;}public final int getHeight() {    return mBottom - mTop;}

从源码中,我们可以看到,实际上View获取宽高时也就是用mRight减去mLeftmBottom减去mTop(所以说mRightmLeftmBottommTop的值永远不会改变)。

1.2.2 获取测量的宽高:
public final int getMeasuredWidth() {    return mMeasuredWidth & MEASURED_SIZE_MASK;}public final int getMeasuredHeight() {    return mMeasuredHeight & MEASURED_SIZE_MASK;}

这里mMeasuredWidth & MEASURED_SIZE_MASK表示的是测量阶段结束之后,View真实的值。而且这个值会在measure()调用了setMeasuredDimensionRaw()函数之后会被设置。所以getMeasuredWidth()的值是measure()阶段结束之后得到的view的原始的值。

1.2.3 1和2中的两种方法比较和区别
  • 我们知道,measure()方法是在layout()方法之前调用的,因此,mMeasuredWidthmMeasuredHeight值在measure()后就被赋值,而getWidth()getHeight()的值需要在layout()之后才能得到。

  • 由1得知,getMeasuredWidth()获取的是view原始的大小,也就是这个view在XML文件中配置或者是代码中设置的大小。getWidth()获取的是这个view最终显示的大小,这个大小有可能等于原始的大小也有可能不等于原始大小。

1.2.4 Activity中无法获取View宽高的解决办法

Activity在onCreate()onStart()onResume()时无法获取View的宽高,解决的办法一般有如下四种:

  • onWindowFocusChanged() :

在Activity或者View的onWindowFocusChanged()中获取,其中hasFocus表示当前窗口(Activity或者View)是否获取窗口,true表示获取:

@Overridepublic void onWindowFocusChanged(boolean hasFocus) {    super.onWindowFocusChanged(hasFocus);    L.i("onWindowFocusChanged : v_view1.getWidth():" + v_view1.getWidth()            + "  v_view1.getHeight():" + v_view1.getHeight());}
  • view.post(runnable):

通过post可以将一个runnable投递到消息队列的尾部,然后等待UI线程Looper调用此runnable的时候,view也已经初始化好了。

    v_view1.post(new Runnable() {        @Override        public void run() {            L.i("post(Runnable) : v_view1.getWidth():" + v_view1.getWidth()                    + "  v_view1.getHeight():" + v_view1.getHeight());        }    });
  • ViewTreeObserver:

使用ViewTreeObserver的众多回调可以完成这个功能,比如使用OnGlobalLayoutListener这个接口,当view树的状态发生改变或者view树内部的view的可见性发生改变时,onGlobalLayout方法将被回调,因此这是获取view的宽高一个很好的时机。需要注意的是,伴随着view树的状态改变等,onGlobalLayout会被调用多次。

    v_view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {        @Override        public void onGlobalLayout() {            L.i("ViewTreeObserver : v_view1.getWidth():" + v_view1.getWidth()                    + "  v_view1.getHeight():" + v_view1.getHeight());        }    });
  • view.measure(int widthMeasureSpec, int heightMeasureSpec)

通过手动对view进行measure来得到view的宽/高,这种情况比较复杂,这里要分情况处理,根据view的layoutparams来分:
MATCH_PARENT:直接放弃,无法measure出具体的宽/高。原因很简单,根据view的measure过程,构造此种MeasureSpec需要知道parentSize,即父容器的剩余空间,而这个时候我们无法知道parentSize的大小,所以理论上不可能测量处view的大小。
WRAP_CONTENT

    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);    v_view1.measure(widthMeasureSpec, heightMeasureSpec);

具体数值(比如宽高都是100dp/px):

    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);    v_view1.measure(widthMeasureSpec, heightMeasureSpec);

2、View的相对屏幕的坐标

下面我们再来看看关于View获取屏幕中位置的一些方法,不过这些方法需要在Activity的onWindowFocusChanged ()方法之后才能使用。


View在屏幕中的坐标

如图所示,View1(绿色)在屏幕中的左上角和右下角坐标分别是(30,100)(440,200),View2(紫色)在屏幕中的左上角和右下角坐标分别是(30,250)(440,800),其中View2可见位置的右下角坐标是(440,720)

下面我们就给出上面这幅图涉及的View的一些坐标方法的结果(结果采用使用方法返回的实际坐标,不依赖上面实际绝对坐标转换,上面绝对坐标只是为了说明例子中的位置而已),如下:

View的方法 View1的结果 View2的结果 结论描述
getLocalVisibleRect() (0, 0, 410, 100) (0, 0, 410, 470) 获取View自身可见的坐标区域,坐标以自己的左上角为原点(0,0),另一点为可见区域右下角相对自己(0,0)点的坐标,其实View2当前height为550,可见height为470。
getGlobalVisibleRect() (30, 100, 440, 200) (30, 250, 440, 720) 获取View在屏幕绝对坐标系中的可视区域,坐标以屏幕左上角为原点(0,0),另一个点为可见区域右下角相对屏幕原点(0,0)点的坐标。
getLocationOnScreen() (30, 100) (30, 250) 坐标是相对整个屏幕而言,Y坐标为View左上角到屏幕顶部的距离。
getLocationInWindow() (30, 100) (30, 250) 如果为普通Activity则Y坐标为View左上角到屏幕顶部(此时Window与屏幕一样大);如果为对话框式的Activity则Y坐标为当前Dialog模式Activity的标题栏顶部到View左上角的距离。

三、View移动自身或者内容的方法

3.1 改变自身的位置

改变自身的位置在方法前面其实已经介绍过了,就是下面几种:

  • view.offsetLeftAndRight(int offset):水平方向挪动View,offset为正则x轴正向移动,移动的是整个View,getLeft()会变的。

  • view.offsetTopAndBottom(int offset):垂直方向挪动View,offset为正则Y轴向下移动,移动的是整个View,getTop()会变的。

  • +view.setTranslationX(int offset):水平方向挪动View,offset为正则x轴正向移动,移动的是整个View,getLeft()不会改变。

  • view.setTranslationY(int offset):水平方向挪动View,offset为正则Y轴向下移动,移动的是整个View,getTop()不会改变。

  • view.layout(int left, int top, int right, int bottom):重新布局View在父布局中的位置,此方法会改变getLeft()等方法的值。

  • LayoutParams:通过设置View的margin值来改变自身的位置:

      LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mtv.getLayoutParams();  layoutParams.leftMargin = 20;  layoutParams.bottomMargin = 20;  mtv.setLayoutParams(layoutParams);
  • 动画:通过设置View的margin值来改变自身的位置:

3.2 自身内容的滚动

滚动相关的方法只是改变View中内容的位置,而整体View在屏幕中的位置不会移动!

  • view.scrollTo(int x, int y)将View中内容(不是整个View)滑动到相应的位置,参考坐标原点为ParentView左上角,x,y表示滑动到的左上角坐标,为正则向xy轴反方向移动,反之同理。

  • view.scrollBy(int x, int y)将View中内容(不是整个View)相对滑动x,y的距离,为正则向xy轴反方向移动,反之同理。

  • view.setScrollX(int value):实质为scrollTo(int x, int y),只是改变X轴方向的内容。

  • view.setScrollY(int value):实质为scrollTo(int x, int y),只是改变Y轴方向的内容。

  • getScrollX()/getScrollY():获取当前滑动位置偏移量。

  • Scroller:通过Scroller类也可以实现View的滑动,并且Scroller效果看起来更加顺滑自然,此类我们会在后续介绍。

scrollTo()和scrollBy()方法特别注意:如果你给一个ViewGroup调用scrollTo()方法滚动的是ViewGroup里面的内容,如果想滚动一个ViewGroup则再给他嵌套一个外层,滚动外层即可。

四、总结

  1. 我们知道了Android中屏幕各区域的划分以及获取屏幕各区域的方法。
  2. 我们知道了View在父布局位置的布局方式以及获取位置的方法。
  3. 我们知道了View得到宽高的两种方法的异同点。
  4. 我们知道了View在屏幕中的位置以及得到屏幕中位置坐标的方法。
  5. 我们知道了改变View自身位置和内容的方法。

参考资料

Android应用坐标系统全面详解

更多相关文章

  1. 如何获取和安装Android(安卓)L开发者预览版
  2. Android(安卓)多线程:使用Thread和Handler (从网络上获取图片)
  3. 通过View.post()获取View的宽高引发的两个问题:1post的Runnable何
  4. android多线程详解之Handler
  5. Android(安卓)View的位置参数
  6. 微信授权APP第三方登陆(Android)
  7. 个人信息界面(二)
  8. 应用程序如何获取系统权限
  9. [置顶] android 从资源中获取数组

随机推荐

  1. 上班摸鱼系列|Python开发命令行斗地主
  2. 使用Python进行数据降维|线性降维
  3. 一些思考和阶段小结
  4. 类和函数傻傻分不清楚?三个例子讲明白
  5. 数据分析师还是算法工程师|用数据多角度解
  6. 使用Python进行统计建模
  7. 【决战西二旗】|理解Sort算法
  8. COVID-19每日据整理|04-01
  9. Python告诉你想开一家美食店该怎么做
  10. 技术解析|如何绘制密度分布图