Android实用知识系列一:图解坐标系

1,777 阅读4分钟

本文介绍Android实用知识如下:

  • Android坐标系
  • 视图坐标系
  • scrollTo()、scrollBy()
  • getScrollX()、getScrollY()
  • 触摸点坐标
  • getLocationOnScree()、getLocationInWindow()

一、Android坐标系

Android的坐标系,如下图,手机垂直时,手机左上角为坐标原点

  • 水平方向是x轴,水平向右为x轴正方向
  • 垂直方向是y轴,垂直向下为y轴正方向

截屏2022-05-28 上午2.17.30.png

二、视图坐标系

视图坐标系为Android view相对于父控件的位置,坐标系原点为父控件左上角,x,y轴方向与Android坐标系相同。如下图,虚线框是子view原位置,发生位移之前的位置:

截屏2022-05-28 下午2.49.14.png

  • getLeft()、getTop()、getRight()、getBottom():子view原位置相对于父view的距离,发生位移之前的view,它的左边、上边、右边、下边的原位置

  • getTranslationX()、getTranslationY():view在x轴、y轴发生的位移距离,相对于自身原位置位移正值:view向坐标轴正方向发生了位移,负值:view向坐标轴负方向发生了位移。

  • getX()、getY():view的现在位置(可能发生了位移)相对于父view的距离

三、scrollTo()、scrollBy()

view.scrollTo()、view.scrollBy(),都只是移动view显示的内容(不包含view的背景),view的位置没有发生改变

view.scrollTo(x, y):以view的左上角为起始点,把view的内容移动一段距离。

  • x、y为正值,view的内容向坐标轴负方向移动(水平向左、垂直向上为坐标轴负方向)
  • x、y为负值,view的内容向坐标轴正方向移动(水平向右、垂直向下为坐标轴正方向)

view.scrollBy(dx, dy):基于view显示的内容已经移动的距离,把view的内容再移动一小段距离。

  • dx、dy为正值,view的内容向坐标轴负方向移动
  • dx、dy为负值,view的内容向坐标轴正方向移动

截屏2022-05-28 下午3.37.20.png

我们写个Demo验证一下,截图如下:

image.png

看源码找原因:

/**
 * Set the scrolled position of your view. This will cause a call to
 * {@link #onScrollChanged(int, int, int, int)} and the view will be
 * invalidated.
 * @param x the x position to scroll to
 * @param y the y position to scroll to
 */
public void scrollTo(int x, int y) {
    if (mScrollX != x || mScrollY != y) {
        int oldX = mScrollX;
        int oldY = mScrollY;
        mScrollX = x;
        mScrollY = y;
        invalidateParentCaches();
        onScrollChanged(mScrollX, mScrollY, oldX, oldY);
        if (!awakenScrollBars()) {
            postInvalidateOnAnimation();
        }
    }
}

/**
 * Move the scrolled position of your view. This will cause a call to
 * {@link #onScrollChanged(int, int, int, int)} and the view will be
 * invalidated.
 * @param x the amount of pixels to scroll by horizontally
 * @param y the amount of pixels to scroll by vertically
 */
public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
}

scrollBy()调用的是scrollTo(),scrollTo()方法改变了mScrollX、mScrollY这两个值,然后调用了postInvalidateOnAnimation(),请求刷新界面。

mScrollX、mScrollY这两个变量改变了,导致界面上子View移动了,我们猜测这两个变量一定是在view绘制的时候用到了。

我们全局搜索使用这两个变量的地方,在View的draw和updateDisplayListIfDirty方法发现使用了这两个变量,下面列出一些关键代码:

   boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        //。。。。。省略很多无关代码


        if (drawingWithRenderNode) {
            //这里会调用:canvas.translate(-mScrollX, -mScrollY);
            renderNode = updateDisplayListIfDirty();

        }

        int sx = 0;
        int sy = 0;
        if (!drawingWithRenderNode) {
            computeScroll();
            sx = mScrollX;
            sy = mScrollY;
        }


        if (offsetForScroll) {
            canvas.translate(mLeft - sx, mTop - sy);
        } else {
            if (!drawingWithRenderNode) {
                canvas.translate(mLeft, mTop);
            }
        }

        //。。。。。省略很多无关代码
    }


    public RenderNode updateDisplayListIfDirty() {
                    //。。。。。省略很多无关代码


                    //view的画布canvas使用了mScrollX和mScrollY,view的内容是canvas画出来的东西,都会被移动
                    canvas.translate(-mScrollX, -mScrollY);
                    mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;

                    
                    //这里调用view的dispatchDraw或者draw,绘制view的内容
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        dispatchDraw(canvas);
                        drawAutofilledHighlight(canvas);
                        if (mOverlay != null && !mOverlay.isEmpty()) {
                            mOverlay.getOverlayView().draw(canvas);
                        }
                        if (isShowingLayoutBounds()) {
                            debugDrawFocus(canvas);
                        }
                    } else {
                        draw(canvas);
                    }



                //。。。。。省略很多无关代码

    }

canvas在view绘制之前,使用了mScrollX和mScrollY,画布发生了位移,之后调用draw方法,界面上看到子view就移动了。

canvas.translate(-mScrollX, -mScrollY),canvas传递的负值,我们scrollTo(-100,200),到了这里,就会是canvas.translate(100,200),画布向右下方(x轴的正方向和y轴的正方向)移动。

移动view显示的内容(不包含背景),实际是canvas.translate(-mScrollX, -mScrollY),view的位置没有发生改变,子view的位置也没有改变,变的是view的画布Canvas

为什么走上面那两个方法?跟踪测试、布局、绘制三大流程:

image.png

四、getScrollX()、getScrollY()

在第三部分我们分析过了,下面直接说知识点:

getScrollX()、getScrollY():获取view显示的内容在x轴、y轴滚动的距离:

  • 为正值,view的内容向坐标轴负方向移动(水平向左、垂直向上为坐标轴负方向)
  • 为负值,view的内容向坐标轴正方向移动(水平向右、垂直向下为坐标轴正方向)

我们在自定义控件滚动事件、嵌套滑动时,经常分不清正负的含义。

五、触摸点坐标

触摸事件MotionEvent:

  • getX()、getY():以view的左上角为原点的视图坐标系,获取的触摸位置
  • getRawX()、getRawY():这两个函数获得的X/Y值是绝对坐标,以屏幕左上角为原点

截屏2022-05-28 下午10.01.56.png

六、getLocationOnScree()、getLocationInWindow()

  • view.getLocationOnScreen(int[] location):获取在当前屏幕内的绝对坐标
  • view.getLocationInWindow(int[] location):一个控件在其父窗口(Window)中的坐标位置

致谢

# Android 坐标系