视图真实位置是可以响应点击事件的,需要区分 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>