[关闭]
@946898963 2019-12-17T13:44:23.000000Z 字数 2887 阅读 842

Android事件转换

Android绘图


我们点击一个TextView的左上角,假如这个TextView在他的父控件的中间位置,那我们点击的x/y应该是多少呢? 在它父控件那,这两个值可能是100/100,而在TextView上打印就会是1/1了,也就是说在事件分发给子控件之前会有一次事件的剪裁过程,下面通过Demo对这个过程进行说明。

Demo:

  1. public class TransformViewGroup extends LinearLayout {
  2. private View mFirstView;
  3. public MyViewGroup(Context context, AttributeSet attrs) {
  4. super(context, attrs);
  5. }
  6. @Override
  7. protected void onFinishInflate() {
  8. super.onFinishInflate();
  9. mFirstView = getChildAt(0);
  10. }
  11. @Override
  12. public boolean dispatchTouchEvent(MotionEvent ev) {
  13. boolean handled = false;
  14. float x = ev.getX();
  15. float y = ev.getY();
  16. System.out.println("父控件:x = " + x + ", y = " + y);
  17. if(x < mFirstView.getLeft() || x > mFirstView.getRight()) return true;
  18. if(y < mFirstView.getTop() || y > mFirstView.getBottom()) return true;
  19. int offetX = getScrollX() - mFirstView.getLeft();
  20. int offetY = getScrollY() - mFirstView.getTop();
  21. ev.offsetLocation(offetX, offetY);
  22. handled = mFirstView.dispatchTouchEvent(ev);
  23. ev.offsetLocation(-offetX, -offetY);
  24. return handled;
  25. }
  26. }

代码很简单,我们完全重写了android默认的事件分发机制。
现在我们来分析一下这段的代码。

  1. if(x < mFirstView.getLeft() || x > mFirstView.getRight()) return true;
  2. if(y < mFirstView.getTop() || y > mFirstView.getBottom()) return true;

主要作用就是防止该事件没有发生到该view身上,而强制分发出去了。

重点是下面这段代码:

  1. int offetX = getScrollX() - mFirstView.getLeft();
  2. int offetY = getScrollY() - mFirstView.getTop();
  3. 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源码:

  1. private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
  2. View child, int desiredPointerIdBits) {
  3. final boolean handled;
  4. ...
  5. ...
  6. ...
  7. // If the number of pointers is the same and we don't need to perform any fancy
  8. // irreversible transformations, then we can reuse the motion event for this
  9. // dispatch as long as we are careful to revert any changes we make.
  10. // Otherwise we need to make a copy.
  11. final MotionEvent transformedEvent;
  12. if (newPointerIdBits == oldPointerIdBits) {
  13. if (child == null || child.hasIdentityMatrix()) {
  14. if (child == null) {
  15. handled = super.dispatchTouchEvent(event);
  16. } else {
  17. final float offsetX = mScrollX - child.mLeft;
  18. final float offsetY = mScrollY - child.mTop;
  19. event.offsetLocation(offsetX, offsetY);
  20. handled = child.dispatchTouchEvent(event);
  21. event.offsetLocation(-offsetX, -offsetY);
  22. }
  23. return handled;
  24. }
  25. transformedEvent = MotionEvent.obtain(event);
  26. } else {
  27. transformedEvent = event.split(newPointerIdBits);
  28. }
  29. // Perform any necessary transformations and dispatch.
  30. if (child == null) {
  31. handled = super.dispatchTouchEvent(transformedEvent);
  32. } else {
  33. final float offsetX = mScrollX - child.mLeft;
  34. final float offsetY = mScrollY - child.mTop;
  35. transformedEvent.offsetLocation(offsetX, offsetY);
  36. if (! child.hasIdentityMatrix()) {
  37. transformedEvent.transform(child.getInverseMatrix());
  38. }
  39. handled = child.dispatchTouchEvent(transformedEvent);
  40. }
  41. // Done.
  42. transformedEvent.recycle();
  43. return handled;
  44. }

参考链接:

android事件如何分发给子view

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注