问题
I have scoured the web in search of an answer to a question that has been bugging me for days. I am making a widget that allows a user to pan the entire widget by dragging (around the Y axis only) within the viewing area. I then have a smaller view (like a button, listens to onTouchEvent) inside here that the user can also drag. The idea is that the user will drag this view up or down and if they want to go higher or lower they can pan the entire widget and then continue dragging the view higher or lower. Now, I'm having two issues with this. First off, after dragging the main view (whole widget) the view that I would like to then move still resides in the area it once was before translating the entire canvas.
So for example, if I move the entire canvas by 50 in the Y position, I then want to move the smaller view but have to touch in the old position rather than the offsetted one (by 50). So it looks like I don't click the smaller view where it is now, but where it used to be (I hope this makes sense).
Next issue, after I move the smaller view. I am not able to trigger anymore touch events. I'm assuming that's because it was moved outside of its parent (which is still viewable since I set clipChildren to false) and its "z-order" is lower than the other views in the layout. Is there a way to always position this on top? I am using Android API 17. Even if I set this smaller view (that I am moving when touched and ACTION_MOVE event is triggered) as the last child in the main layout, I need to be able to drag it out of here (which I can) and continue to drag it on a new onTouchEvent. ANY help is greatly appreciated. I added some layout xml and code snippets below to help.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false">
<RelativeLayout
android:id="@+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:clipChildren="false"
android:clipToPadding="false">
<ImageView
android:id="@+id/ref_image"
android:layout_width="wrap_content"
android:layout_height="216dp"
android:src="@drawable/my_ref_image"
android:layout_centerInParent="true"
android:clipChildren="false"/>
<RelativeLayout
android:id="@+id/selection_view"
style="@style/MyStyle"
android:layout_alignTop="@id/ref_image"
android:layout_marginTop="116dp"
android:clipChildren="false"
android:clipToPadding="false">
<RelativeLayout
android:id="@+id/selection_area"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:clipChildren="false"
android:clipToPadding="false">
<ImageView
android:id="@+id/underlay_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/translucent_image"
android:layout_centerInParent="true"/>
<ImageView
android:id="@+id/point_area"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/point_image"
android:layout_centerInParent="true"/>
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
So the snippets of code that is of interest:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (DEBUG) {
Log.d(TAG, TAG + "::onDraw: ");
}
canvas.translate(0, backgroundPositionY);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
Log.d(TAG, TAG + "::onTouchEvent: parent touch event");
switch (action) {
case MotionEvent.ACTION_DOWN: {
final float y = ev.getY();
// Remember where we started
lastTouchY = y;
if (DEBUG) {
Log.d(TAG, TAG + "::onTouchEvent: parent ACTION_DOWN");
}
return true;
}
case MotionEvent.ACTION_MOVE: {
final float y = ev.getY();
// Calculate the distance moved
final float dy = y - lastTouchY;
backgroundPositionY += dy
// Remember this touch position for the next move event
lastTouchY = y;
// Invalidate to request a redraw
invalidate();
break;
}
case MotionEvent.ACTION_UP: {
lastTouchY = ev.getY();
}
}
return false;
}
// called upon initialization (selectionArea refs view id = selection_area)
private void initPointListener() {
this.setClipChildren(false);
this.setClipToPadding(false);
if (selectionArea != null) {
selectionArea .setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
RelativeLayout.LayoutParams pointParams = (RelativeLayout.LayoutParams) selectionArea.getLayoutParams();
final int action = motionEvent.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: {
pointLastTouchY = motionEvent.getY();
}
break;
}
case MotionEvent.ACTION_MOVE: {
float moveY = motionEvent.getY();
final float dy = moveY - pointLastTouchY;
pointPosY += dy;
selectionArea.setY(pointPosY);
break;
}
}
return true;
}
});
}
}
Note that I have tried setting the padding and I have referenced the following on SO with no good results:
How to translate the canvas and still get the touch events on the correct places
Android : click / touch event not working after canvas translate
回答1:
Alright, well... I found a work around. Basically, my parent view (the widget itself) could always see touch events. But the children could not given the circumstances I mentioned above. So, what I did was keep track of the x and y positions of the child view I was dragging relative to the viewing area. Then whenever I received a touch event I would check to see if I touched the child area or not. If I happened to trigger the onTouchEvent for the child when it was in its old location, I would ignore it if it didn't pass this check. Its sloppy, but its the only way I could get it to work. Now I have a pannable view and a draggable object
回答2:
I'm not sure did I understand everything on your problem right but maybe this could help you View with horizontal and vertical pan/drag and pinch-zoom You should try the code from Alex's answer. The code is doing some extra things but you could just take scalelistener (for zoom) off and ofcourse the panning for X-axis. That code is also moving childviews "inside" the canvas so then you shouldn't have to translate click positions after onTouch called.
回答3:
The better solution is the leave your view in place. Then apply a matrix transformation to your view and the inverted matrix to MotionEvents. This way you can always get your view properly and klugelessly delegated your events, and all the panning takes place in the drawing of the scene. Moving the view around will also hit a basically unpassable problem that you cannot get the MotionEvents that start outside of the bounds of your real view. So if you zoom out, and press something outside the the area where the screen would be it ignores that and doesn't dispatch the touch events there.
Also, it goes insane on some implementations, I think galaxy tab 10, it was just horribly fubar and impossible wrong. It does the invalidation of the view a bit differently than other tablets and refreshed the wrong parts of the view.
来源:https://stackoverflow.com/questions/31145595/android-no-touch-events-after-translating-canvas