1.背景
学习自定义控件,了解Android各种坐标系及一些API的坐标含义绝对算一个小而不可忽视的技能;所谓Android自定义View那几大主要onXXX()方法的重写实质大多数都是在处理坐标逻辑运算。所以,我们一起学习下Android坐标系。
2.Android坐标系
2.1Android屏幕区域划分
通过上图我们可以很直观的看到Android对于屏幕的划分定义。下面我们就给出这些区域里常用区域的一些坐标或者度量方式。如下:
//获取手机屏幕区域高度
public int getWindowArea() {
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int widthPixels = metrics.widthPixels;
int heightPixels = metrics.heightPixels;
return heightPixels;
}
//获取应用区域高度
public int getApplicationArea() {
Rect rect = new Rect();
getWindow().getDecorView().
getWindowVisibleDisplayFrame(rect);
int width = rect.width();
int height = rect.height();
return height;
}
//获取状态栏高度
public int getstatusBarArea() {
Rect rect = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
int statusBarHeight = rect.top;
return statusBarHeight;
}
//获取view绘制区域高度
public int getDrawArea() {
Rect rect = new Rect();
getWindow().findViewById(Window.ID_ANDROID_CONTENT)
.getDrawingRect(rect);
int width = rect.width();
int height = rect.height();
return height;
}
//获取标题栏高度
public int gettitleBarArea() {
return getApplicationArea()-getDrawArea();
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
android.util.Log.i("TAG","onWindowFocusChanged");
if (hasFocus) {
//获取view绘制区域(即不包括标titlebar的内容区域)距离手机屏幕顶部的距离
android.util.Log.i("TAG","屏幕区域高度:"+getWindowArea());
android.util.Log.i("TAG","应用区域高度:"+getApplicationArea());
android.util.Log.i("TAG","view绘制区域高度:"+getDrawArea());
android.util.Log.i("TAG","状态栏高度:"+getstatusBarArea());
android.util.Log.i("TAG","标题栏高度:"+gettitleBarArea());
}
}
...
onWindowFocusChanged():当Activity的当前Window获得或失去焦点时会被回调此方法。当回调了这个方法时表示Activity是完全对用户可见的(只是可见,还一片黑呼呼的,有待draw..)。当对话框弹起/消失及Activity新创建及回退等都会调用此方法。此时能获取屏幕区域的准确信息。
2.2Android View绝对相对坐标系
2.2.1View的坐标
通过上图我们可以很直观的给出View一些坐标相关的方法解释,不过必须要明确的是上面这些方法必须要在layout之后才有效,如下:
|View的静态坐标方法 | 解释
|-------------
|getLeft() |返回View自身左边到父布局左边的距离
|getTop() |返回View自身顶边到父布局顶边的距离
|getRight()| 返回View自身右边到父布局左边的距离
|getBottom()| 返回View自身底边到父布局顶边的距离
|getX()| 返回值为getLeft()+getTranslationX(),当setTranslationX()时getLeft()不变,getX()变。
|getY() |返回值为getTop()+getTranslationY(),当setTranslationY()时getTop()不变,getY()变。
2.2.2MotionEvent坐标
手指触摸屏幕时MotionEvent提供的一些方法解释,如下:
|MotionEvent坐标方法| 解释
|-------------
|getX() |当前触摸事件距离当前View左边的距离
|getY() |当前触摸事件距离当前View顶边的距离
|getRawX() |当前触摸事件距离整个屏幕左边的距离
|getRawY() |当前触摸事件距离整个屏幕顶边的距离
2.3Android View滑动相关坐标系
|View的滑动方法 | 效果及描述
|-------------
|offsetLeftAndRight(int offset) |水平方向挪动View,offset为正则x轴正向移动,移动的是整个View,getLeft()会变的。
|offsetTopAndBottom(int offset)|垂直方向挪动View,offset为正则y轴正向移动,移动的是整个View,getTop()会变的。
|scrollTo(int x, int y)|将View中内容(不是整个View)滑动到相应的位置,参考坐标原点为ParentView左上角,x,y为正则向x,y轴反方向移动,反之同理。
|scrollBy(int x, int y)|在scrollTo()的基础上继续滑动xy。
|setScrollX(int value)|实质为scrollTo(),只是只改变Y轴滑动。
|setScrollY(int value)|实质为scrollTo(),只是只改变X轴滑动。
|getScrollX()/getScrollY()|获取当前滑动位置偏移量。
2.3.1为什么scrollTo和scrollBy滚动的是View的内容而非View本身?
/**
* 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);
}
因为View在ViewGroup中的位置是由LayoutParams的margin等参数决定的,要想滚动View或者说要想改变View的位置只需要改变LayoutParams的相关参数就可以。但是scrollTo和scrollBy改变的只是mScrollX和mScrollY的值,这两个值对于改变View在ViewGroup里面的位置是毫无关系的;这就排除了scrollTo或者scrollBy滚动的是View本身了。
为什么说View滚动的是内容?
/**
* Draws the background onto the specified canvas.
* @param canvas Canvas on which to draw the background
*/
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
setBackgroundBounds();
// Attempt to use a display list if requested.
if (canvas.isHardwareAccelerated() && mAttachInfo != null
&& mAttachInfo.mHardwareRenderer != null) {
mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
final RenderNode renderNode = mBackgroundRenderNode;
if (renderNode != null && renderNode.isValid()) {
setBackgroundRenderNodeProperties(renderNode);
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
return;
}
}
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
上面的代码可以看出mScrollX和mScrollY这两个变量正式交给显示View内容的Canvas来操作的!所以我们说scrollTo或者scrollBy滚动的是View的内容,而不是改变View在parentView显示的位置关系!
2.3.2 mScrollX和mScrollY的变换规律
/**
* The offset, in pixels, by which the content of this view is scrolled
* horizontally.
* {@hide}
*/
@ViewDebug.ExportedProperty(category = "scrolling")
protected int mScrollX;
/**
* The offset, in pixels, by which the content of this view is scrolled
* vertically.
* {@hide}
*/
@ViewDebug.ExportedProperty(category = "scrolling")
protected int mScrollY;
/**
* Return the scrolled left position of this view. This is the left edge of
* the displayed part of your view. You do not need to draw any pixels
* farther left, since those are outside of the frame of your view on
* screen.
*
* @return The left edge of the displayed part of your view, in pixels.
*/
public final int getScrollX() {
return mScrollX;
}
/**
* Return the scrolled top position of this view. This is the top edge of
* the displayed part of your view. You do not need to draw any pixels above
* it, since those are outside of the frame of your view on screen.
*
* @return The top edge of the displayed part of your view, in pixels.
*/
public final int getScrollY() {
return mScrollY;
}