怎样判断一个点是否落在View内,判断是否手指移动到一个View中

在3.0时代之前,要判断一个点是否落在 View 上只需要两步:第一步:得到 View 的 Rect,第二步:判断点是否再这个 Rect 内。

但从 Android 3.0 开始这样的简单日子就结束了。

原因在于,Google 为 Android 3.0 提供了一套新的动画框架:Property Animation 。View/ViewGroup为此获得了强大的动画能力,但代价是View/ViewGroup的实现比以前更复杂了。3.0 前的 View/ViewGroup 在被画到画布前只会经过一次矩阵变换(如果用户不自定义的话);但现在,View/ViewGroup 的矩阵变换变为两次,并且一些新的方法和字段被添加到 View/ViewGroup 类,这些都增加了 View/ViewGroup 类的复杂程度。

Android 3.0前的View类并不持有matrix,只有canvas才持有matrix,View只有通过修改canvas的matrix才能完成一些矩阵的基 本操作(移位、旋转、缩放)。但从3.0开始,为了更好的用户体验,Google让View也亲自持有 matrix(由于这个matrix是为 property animation 服务的,因此就称它为 property matrix),这让 View 在每次 Draw 时增加了一层新的矩阵变换。但仅仅在『绘制』时增加一层转换是不够的,因为绘制只是输出,View还要处理输入(比如touch event),因此View还需要为触摸处理加一层矩阵变换。简而言之,所有和输入、输出相关的操作都需要加一层 property matrix 矩阵变换。

具体来说, View 持有的 property matrix 在 DispatchDraw 和 DispatchTouchEvent 分别被计算一次:DispatchDraw 时会用 Bitmap 乘以这个矩阵;DispatchTouchEvent 则会用 MotionEvent 的坐标(x,y)乘以这个矩阵的逆矩阵(Matrix.invert)。这可以理解为,显示的时候 view 要处理的是自己的bitmap,而在处理 motionevent 的时候,view需要处理的是 motion event。

再回到最初的问题:怎样判断某个点落在一个View里。

View 提供一个方法 getHitRect() ,文档说它的作用是 Hit rectangle in parent's coordinates。但实际上这个方法得到的结果是错误的。那么 getHitRect() 有什么问题呢,先看看 getHitRect() 的实现:

/**
 * Hit rectangle in parent's coordinates
 *
 * @param outRect The hit rectangle of the view.
 */
public void getHitRect(Rect outRect) {
    updateMatrix();
    final TransformationInfo info = mTransformationInfo;
    if (info == null || info.mMatrixIsIdentity || mAttachInfo == null) {
        outRect.set(mLeft, mTop, mRight, mBottom);
    } else {
        final RectF tmpRect = mAttachInfo.mTmpTransformRect;
        tmpRect.set(-info.mPivotX, -info.mPivotY,
                getWidth() - info.mPivotX, getHeight() - info.mPivotY);
        info.mMatrix.mapRect(tmpRect);
        outRect.set((int) tmpRect.left + mLeft, (int) tmpRect.top + mTop,
                (int) tmpRect.right + mLeft, (int) tmpRect.bottom + mTop);
    }
}

getHitRect 的做法是:计算出view在矩阵变换前的rect,然后再对这个rect进行一次矩阵变换,得到当前在parent的rect。 因此关键在 getHitRect() 是如何得到原始 rect 的,它以:

left:   -info.mPivotX,
top:    -info.mPivotY,
right:  getWidth() - info.mPivotX,
bottom: getHeight() - info.mPivotY);

这样的数据作为原始 rect 。

这样做是错的。原因有两个:

  1. 没有引入 mScrollX 和 mScrollY
  2. -info.mPivotX 不能作为 left; -info.mPivotY 不能作为 top; getWidth() - info.mPivotX 不能作为 right; getHeight() - info.mPivotY) 也不能作为 bottom。

第二个错误简单的想象一下就能得到答案:假设在窗口坐标 (0, 0, 2, 2) 有一个矩形,然后以 [1, 1] 为中轴缩放 0.5 倍,这样就得到了一个 (0.5, 0.5, 1.5, 1.5) 的矩形,显然 getHitRect() 不能得到正确的结果。

因此,怎样判断某个点是否落在在一个 View 里就得重新写一个方法,代码如下:

/**
 * Returns true if a child view contains the specified point when transformed
 * into its coordinate space.
 */
private boolean isTransformedTouchPointInView(float x, float y, View child,
                                              PointF outLocalPoint) {
    // get x, y offset
    float localX = x + getScrollX() - child.getLeft();
    float localY = y + getScrollY() - child.getTop();

    // restore location
    final float[] localXY = new float[2];
    localXY[0] = localX;
    localXY[1] = localY;
    final Matrix inverseMatrix = new Matrix();
    child.getMatrix().invert(inverseMatrix);
    inverseMatrix.mapPoints(localXY);
    localX = localXY[0];
    localY = localXY[1];

    // fill out data
    final boolean isInView = pointInView(child, localX, localY);
    if (isInView && outLocalPoint != null) {
        outLocalPoint.set(localX, localY);
    }
    return isInView;
}

/**
* Determines whether the given point, in local coordinates is inside the view.
*/
private static boolean pointInView(View view, float localX, float localY) {
    return localX >= 0 && localX < (view.getRight() - view.getLeft())
        && localY >= 0 && localY < (view.getBottom()- view.getTop());
}

上面的方法将点(x,y)用view的inverse matrix进行了一次变换,然后再判断变换后的点是否落在view的rect内。

刚接触到 Property Animation 时我觉得它真是一个非常cool的结构,至少从使用者的角度来看是。但现在我认为它并不是一个成熟的方案,因为一些复杂的情况[注1] google 并没有做考虑,所以,在使用 Property Animation 和与 Property Animation 有关的 api 时一定要小心。


注1: 另一个复杂的情况是对View用多点触摸进行scale操作。由于 MotionEvent 没有 getRawX(int) 方法,它就只能用 getX(int) 来模拟 getRawX(int) ,getX(int)使用的是相对于ViewGroup的坐标,这会导致scale计算出错。

转载于:https://www.cnblogs.com/ccharp/p/5663470.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值