自定义View学习笔记(二)-Android坐标系简介

1.背景

学习自定义控件,了解Android各种坐标系及一些API的坐标含义绝对算一个小而不可忽视的技能;所谓Android自定义View那几大主要onXXX()方法的重写实质大多数都是在处理坐标逻辑运算。所以,我们一起学习下Android坐标系。

2.Android坐标系

2.1Android屏幕区域划分

自定义View学习笔记(二)-Android坐标系简介_第1张图片
device-2016-12-09-142518.png

通过上图我们可以很直观的看到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学习笔记(二)-Android坐标系简介_第2张图片
view的位置坐标和父容器的关系.jpg(图片来源于网络)

自定义View学习笔记(二)-Android坐标系简介_第3张图片
view.getX和view.getTranslationX区别.png(图片来源于网络)

通过上图我们可以很直观的给出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坐标

自定义View学习笔记(二)-Android坐标系简介_第4张图片
MotionEvent坐标.jpg(图片来源于网络)

手指触摸屏幕时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;
}
自定义View学习笔记(二)-Android坐标系简介_第5张图片
mScrollX和mScrollY的变换规律.png(图片来源于网络)

你可能感兴趣的:(自定义View学习笔记(二)-Android坐标系简介)