本文介绍Android实用知识如下:
- Android坐标系
- 视图坐标系
- scrollTo()、scrollBy()
- getScrollX()、getScrollY()
- 触摸点坐标
- getLocationOnScree()、getLocationInWindow()
一、Android坐标系
Android的坐标系,如下图,手机垂直时,手机左上角为坐标原点,
- 水平方向是x轴,水平向右为x轴正方向
- 垂直方向是y轴,垂直向下为y轴正方向
二、视图坐标系
视图坐标系为Android view相对于父控件的位置,坐标系原点为父控件左上角,x,y轴方向与Android坐标系相同。如下图,虚线框是子view原位置,发生位移之前的位置:
-
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的内容向坐标轴正方向移动
我们写个Demo验证一下,截图如下:
看源码找原因:
/**
* 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
为什么走上面那两个方法?跟踪测试、布局、绘制三大流程:
四、getScrollX()、getScrollY()
在第三部分我们分析过了,下面直接说知识点:
getScrollX()、getScrollY():获取view显示的内容在x轴、y轴滚动的距离:
- 为正值,view的内容向坐标轴负方向移动(水平向左、垂直向上为坐标轴负方向)
- 为负值,view的内容向坐标轴正方向移动(水平向右、垂直向下为坐标轴正方向)
我们在自定义控件滚动事件、嵌套滑动时,经常分不清正负的含义。
五、触摸点坐标
触摸事件MotionEvent:
getX()、getY():以view的左上角为原点的视图坐标系,获取的触摸位置getRawX()、getRawY():这两个函数获得的X/Y值是绝对坐标,以屏幕左上角为原点
六、getLocationOnScree()、getLocationInWindow()
- view.getLocationOnScreen(int[] location):获取在当前屏幕内的绝对坐标
- view.getLocationInWindow(int[] location):一个控件在其父窗口(Window)中的坐标位置