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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/vicwudi/article/details/82084950

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表示获取:

@Override
public 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应用坐标系统全面详解

猜你喜欢

转载自blog.csdn.net/vicwudi/article/details/82084950
今日推荐