给出一个嵌套的视图结构,例如:
如何禁用灰色外围的输入,以使即使用户在手指上握住手指但触摸黄色视图,触摸事件仍会在dispatchTouchEvent()
中注册,以便该视图不受阻碍?
编辑: 为了进一步解释,我需要在灰色区域使用某种手掌剔除系统。在黄色区域中,用户可以用手指绘画。所有这些都可以正常工作,但是在某些带有无边框显示器的手机上,您可能会不小心触摸灰色区域,该区域会被注册为输入,从而破坏了图形。
仅当用户一次在多个位置触摸屏幕时才会发生这种情况。在这一点上,可能有人会责怪用户购买这种mm头,但我已经亲自尝试过,不小心碰到边缘并防止事件正常进行很容易。
发布此问题后,我想出了一个使用多点触摸事件的有点棘手的解决方案。它比以前更好,但是因为它实际上不是多点触摸事件,所以有时会过时并完全停止注册输入。此外,最好能最终在黄色框中捕获实际的多点触摸事件(例如做出放大到绘图的手势)。
到目前为止,我的解决方案的基本前提是:
onDraw()
中的所有区域。MotionEvent
中的dispatchTouchEvent()
然后:
// ... event:MotionEvent, pointerCoordsOut:MotionEvent.PointerCoords
for (pidx in 0 until event.pointerCount) {
event.getPointerCoords(pidx, pointerCoordsOut)
if (inYellowArea(pointerCoordsOut.x, pointerCoordsOut.y)) {
//pointerCoordsOut now has (x,y) that I need
}
}
最后,调整代码以接受ACTION_*
和ACTION_POINTER_*
事件,并使它们做一些合理的事情。在演示案例中这很容易,但是我认为这是解决方案最终失败的地方。
因此,我仍然希望有一个正确的解决方案,以解决边界上的手掌不适感(如果该事件根本不存在),而在我看来,这是一个复杂手势的一部分,我正在尝试解密。
编辑:
仍然开放征求意见。
答案 0 :(得分:3)
已更新:
布局:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/darker_gray"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/orange"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="64dp"
android:layout_marginEnd="64dp"
android:layout_marginStart="64dp"
android:layout_marginTop="64dp"
android:background="@android:color/holo_orange_dark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
代码:
class MainActivity : AppCompatActivity() {
private val touchableRect = Rect()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
root.post {
orange.getGlobalVisibleRect(touchableRect)
}
root.setOnTouchListener { v, event ->
Log.d("logi", "root touched : ${event.actionMasked}")
false
}
orange.setOnTouchListener { v, event ->
Log.d("logi", "orange touched : ${event.actionMasked}")
true
}
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
val isConsumed = ev?.let {
if (touchableRect.contains(it.x.toInt(), it.y.toInt())) {
orange.dispatchTouchEvent(it)
} else {
true
}
}
return isConsumed ?: true
}
}
答案 1 :(得分:0)
潜在的问题有两个:首先,除非有适当的编码来重定向手势,否则在手势持续时间内,绘图区域外部的触摸将永远无法到达绘图区域。其次,屏幕上可能有多个指针,因此必须保持笔直。
在以下解决方案中,从主活动的绘图区域调用dispatchTouchEvent()
。这样可以确保绘图区域可以看到所有事件,即使是在其边界之外发出的事件。 MainActivity
对绘图区域进行了总括呼叫,尽管在某些情况下可以更智能地调度呼叫。
绘图区域是一个简单的自定义视图,该视图对事件进行排序并在与其他事件密切相关的事件上起作用,而忽略其他事件。请参阅代码中的注释。自定义视图仅允许用户用手指绘制。
如果您想尝试一下,主要活动,布局和自定义视图都可以作为一个简单的应用程序一起工作。
MainActivity.java
public class MainActivity extends AppCompatActivity {
private View mDrawingArea;
private final int[] mDrawingAreaLocation = new int[2];
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View layout = findViewById(R.id.layout);
mDrawingArea = findViewById(R.id.drawing_area);
layout.post(new Runnable() {
@Override
public void run() {
mDrawingArea.getLocationOnScreen(mDrawingAreaLocation);
}
});
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
// Everything goes to the drawing area. We could do some bounds checking and send
// everything within the bounds to the drawing area and everything else to
// super.dispatchTouchEvent().
// Adjust location to be view-relative.
event.offsetLocation(-mDrawingAreaLocation[0], -mDrawingAreaLocation[1]);
return mDrawingArea.dispatchTouchEvent(event);
}
}
MyView.java
public class MyView extends View {
private final Rect mViewBounds = new Rect();
private static final int NO_POINTER_CAPTURED = -1;
private int mCapturedPointer = NO_POINTER_CAPTURED;
private final Path mPath = new Path();
private final Paint mPaint = new Paint();
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint.setStrokeWidth(10);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
}
@Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewBounds.left = 0;
mViewBounds.top = 0;
mViewBounds.right = w;
mViewBounds.bottom = h;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// Coordinates will be relative to the view.
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
// This is the start of the gesture and the first pointer down.
// Check to see if it is in our drawing area and capture it if it is.
if (mViewBounds.contains((int) event.getX(), (int) event.getY())) {
mCapturedPointer = event.getPointerId(event.getActionIndex());
mPath.reset();
mPath.moveTo(event.getX(), event.getY());
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
// This is the second, third, etc. pointer down. We are just going to
// consider capturing it if we don't have another pointer already captured.
if (mCapturedPointer == NO_POINTER_CAPTURED) {
int x = (int) event.getX(event.getActionIndex());
int y = (int) event.getY(event.getActionIndex());
// The pointer must be within the drawing area.
if (mViewBounds.contains(x, y)) {
mPath.reset();
mPath.moveTo(x, y);
mCapturedPointer = event.getPointerId(event.getActionIndex());
}
}
break;
case MotionEvent.ACTION_MOVE:
// See if our captured pointer is moving. If it is, do some prep for drawing.
if (mCapturedPointer != NO_POINTER_CAPTURED) {
int pointerCount = event.getPointerCount();
for (int ptrIndex = 0; ptrIndex < pointerCount; ptrIndex++) {
if (event.getPointerId(ptrIndex) == mCapturedPointer) {
mPath.lineTo(event.getX(ptrIndex), event.getY(ptrIndex));
invalidate();
break;
}
}
}
break;
case MotionEvent.ACTION_POINTER_UP:
// Release the captured pointer when it leaves the surface.
if (event.getPointerId(event.getActionIndex()) == mCapturedPointer) {
mCapturedPointer = NO_POINTER_CAPTURED;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mCapturedPointer = NO_POINTER_CAPTURED;
default:
break;
}
// We want to look at everything.
return true;
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mPath, mPaint);
}
}
activity_main.xml
<FrameLayout
android:id="@+id/parentGroup"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_red_light"
android:padding="32dp"
tools:context=".MainActivity">
<com.example.android.rejectperipheraltouches.MyView
android:id="@+id/drawing_area"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:background="#FDD835"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</FrameLayout>
使用触摸监听器
以上代码重定向了活动中dispatchTouchEvent()
中的事件,但对于更复杂的布局可能需要其他逻辑。一种替代方法是直接从触摸侦听器为父视图调用工程图视图的onTouchEvent()
。因此,如果直接触摸绘图视图,则所有事件都会正常进行。但是,如果初始触摸是包围视图,则这些触摸将被重定向到工程图视图。这可能比dispatchTouchEvent()
解决方案更干净。
MainActivity.java(可选)
public class MainActivity extends AppCompatActivity {
private View mDrawingArea;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDrawingArea = findViewById(R.id.drawing_area);
findViewById(R.id.parentGroup).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
event.offsetLocation(-mDrawingArea.getLeft(), -mDrawingArea.getTop());
return mDrawingArea.onTouchEvent(event);
}
});
}
}