@946898963
2019-12-17T21:44:23.000000Z
字数 2887
阅读 956
Android绘图
我们点击一个TextView的左上角,假如这个TextView在他的父控件的中间位置,那我们点击的x/y应该是多少呢? 在它父控件那,这两个值可能是100/100,而在TextView上打印就会是1/1了,也就是说在事件分发给子控件之前会有一次事件的剪裁过程,下面通过Demo对这个过程进行说明。
Demo:
public class TransformViewGroup extends LinearLayout {
private View mFirstView;
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mFirstView = getChildAt(0);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
float x = ev.getX();
float y = ev.getY();
System.out.println("父控件:x = " + x + ", y = " + y);
if(x < mFirstView.getLeft() || x > mFirstView.getRight()) return true;
if(y < mFirstView.getTop() || y > mFirstView.getBottom()) return true;
int offetX = getScrollX() - mFirstView.getLeft();
int offetY = getScrollY() - mFirstView.getTop();
ev.offsetLocation(offetX, offetY);
handled = mFirstView.dispatchTouchEvent(ev);
ev.offsetLocation(-offetX, -offetY);
return handled;
}
}
代码很简单,我们完全重写了android默认的事件分发机制。
现在我们来分析一下这段的代码。
if(x < mFirstView.getLeft() || x > mFirstView.getRight()) return true;
if(y < mFirstView.getTop() || y > mFirstView.getBottom()) return true;
主要作用就是防止该事件没有发生到该view身上,而强制分发出去了。
重点是下面这段代码:
int offetX = getScrollX() - mFirstView.getLeft();
int offetY = getScrollY() - mFirstView.getTop();
ev.offsetLocation(offetX, offetY);
这段代码是从Android源码中复制的,通过下面的图来理解这段代码。
如图, scrollX代表这绿色部分在屏幕外面的部分,假如这里是50,left是蓝色部分距离他父控件(绿色部分)左边的值,即getLeft这里是100,当我们触摸屏幕的时候,坐标是从屏幕的左上角开始计算。来看看offsetX 如果还是拿这张图来说的话, offsetX = 50 - 100 = -50 。offsetY的值也相同。
说到这里,我们大概明白MotionEvent.offsetLocation的作用了,它的作用就是根据你的两个参数去偏移坐标。这里,我们的x偏移了-50,计算一下,如果将这个剪裁后的事件分发给子view,那对于子view而言,点击的位置就是0了。
下面通过log来验证咱们的猜想。
通过log可以看出,事件的坐标的确是经过了剪裁。
最后,附录上Android源码:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...
...
...
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
参考链接: