View 坐标系

视图真实位置是可以响应点击事件的,需要区分 Canvas 的 Metrics 变换(补间动画)

View 的原始位置主要由四个顶点来决定,分别对应 View 的四个属性:mLeft、mTop、mRight、mBottom,我们可以通过 get 方法来得到这四个参数,而且 width = right - left、hight = bottom - top。子视图的原始位置在父视图的 onLayout 方法中确定,主要受父视图的 Padding、当前 View的 Margin 以及父视图的布局规则影响

View 的真实位置通过 X 坐标和 Y 坐标来表示,我们可以通过 getX 和 getY 方法来获取,换算关系如下:x = left + translationX;y = top + translationY;其中 translation 是 View 左上角相对于父容器的距离(位移属性动画也是通过改变 translation 来实现)

目前所说的 x、y、left、right、top、bottom、translationX、translationY 都是相对于父容器的坐标

mScrollX、mScrollY:视图内容相对于视图起始坐标的偏移量

Scroll 变量会影响当前 View 和子 View 的内容绘制和事件处理(通过 Canvas 平移实现)

computeScroll();
final int restoreCount = canvas.save();
canvas.scale(scale, scale);
canvas.translate(-mScrollX, -mScrollY);

// Temporarily remove the dirty mask
int flags = mPrivateFlags;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;

// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
    dispatchDraw(canvas);
    drawAutofilledHighlight(canvas);
    if (mOverlay != null && !mOverlay.isEmpty()) {
        mOverlay.getOverlayView().draw(canvas);
    }
} else {
    draw(canvas);
}

Scroll 不会影响子视图的真实位置,不会更改子视图的 X、Y、Left、Right、Top、Bottom 信息,但是子视图的点击区域确实改变了,左上角相对于父容器的距离变了为 x - mScrollX、y - mScrollY,这是因为事件处理框架在分发事件时考虑了 Scroll 信息

这里引出一个问题,子视图的真实位置到底是啥,从用户角度看,应该算上 Scroll 的距离,但是从编程角度来看,应该不算 Scroll,只是事件处理框架和内容绘制逻辑对 Scroll 做了兼容

/**
 * Returns true if a child view contains the specified point when transformed
 * into its coordinate space.
 * Child must not be null.
 * @hide
 */
protected boolean isTransformedTouchPointInView(float x, float y, View child,
        PointF outLocalPoint) {
    final float[] point = getTempPoint();
    point[0] = x;
    point[1] = y;
    transformPointToViewLocal(point, child);
    final boolean isInView = child.pointInView(point[0], point[1]);
    if (isInView && outLocalPoint != null) {
        outLocalPoint.set(point[0], point[1]);
    }
    return isInView;
}

public void transformPointToViewLocal(float[] point, View child) {
    point[0] += mScrollX - child.mLeft;
    point[1] += mScrollY - child.mTop;

    if (!child.hasIdentityMatrix()) {
        child.getInverseMatrix().mapPoints(point);
    }
}

Scroll 变量不会影响本视图的背景

if ((scrollX | scrollY) == 0) {
	 background.draw(canvas);
} else {
     //由于绘制背景时,Canvas又移回了原位置,所以scroll变量不影响背景绘制
	 canvas.translate(scrollX, scrollY);
	 background.draw(canvas);
	 canvas.translate(-scrollX, -scrollY);
}

由于 Scroll 偏移是由框架处理的,不会改变原始坐标位置,所以该变量并不影响自定义 View 的布局和测量工作。Scroll 变量也不影响自定义 View 的绘制流程,Scroll 偏移也已经由系统框架处理好了,绘制是通过 Canvas 的 translate 完成的,事件分发在计算子 View 位置时也考虑了 Scroll 信息

在自定义 View 中,由于 MotionEvent 事件的 getX 和 getY 是相对于本视图的位置,所以我们在处理 MotionEvent 时一般不需要考虑位置信息(mScrollX、mScrollY、x、y、left、top、translationX、translationY)

View滑动:Scroller、ScrollTo、ScrollBy、补间动画、属性动画

Scroller用法:

mScroller.startScroll()
mScroller.computeScrollOffset()
view.computeScroll()

@Override
public void computeScroll() {
    super.computeScroll();
    if (mScroller.computeScrollOffset()) {
        scrollTo(mScroller.getCurrX(), 0);
        invalidate();
    }
}

margin 信息会影响子 View 的原始坐标,Demo 如下:

public class MainActivity extends AppCompatActivity {
  HandlerThread handlerThread = new HandlerThread("work");
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final TextView textView = findViewById(R.id.aaa);
    final LinearLayout linearLayout = findViewById(R.id.ccc);
    linearLayout.scrollTo(-40, -40);
    textView.setTranslationX(20);
    handlerThread.start();
    Handler handler = new Handler(handlerThread.getLooper());
    handler.postDelayed(new Runnable() {
      @Override
      public void run() {
        //offer: 50.0 30 20.0
        Log.e("offer", textView.getX() + " " + textView.getLeft() + " " + textView.getTranslationX()
        );
      }
    }, 5000);
    textView.setOnTouchListener(new View.OnTouchListener() {
      @Override
      public boolean onTouch(View v, MotionEvent event) {
        //点击TextView右上角
        //offer: 8.0407715 6.09375
        Log.e("offer", event.getX() + " " + event.getY());
        return false;
      }
    });
  }
}

布局文件如下(其中TextView设置了margin信息):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.wzh.sample.marginwillinfluencex.MainActivity">

    <LinearLayout
        android:id="@+id/ccc"
        android:background="#00ff00"
        android:layout_width="200dp"
        android:layout_height="200dp">
        <TextView
            android:id="@+id/aaa"
            android:layout_marginLeft="10dp"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="#333333"
            android:text="Hello World!" />
    </LinearLayout>

</LinearLayout>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

little-sparrow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值