/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.arakhne.afc.ui.android.zoom; import java.lang.ref.WeakReference; import org.arakhne.afc.ui.android.event.PointerEventAndroid; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; /** This class provides a general behavior to support touch events. * <p> * This manager tries to detected scaling, move and click gestures and * avoid to generate unecessary events. * * @author $Author: sgalland$ * @version $Name$ $Revision$ $Date$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @deprecated see JavaFX API */ @Deprecated class TouchManager { /** Constant that is the identifier representing of an invalid pointer * on the droid device. */ public static final int INVALID_POINTER_ID = -1; /** Scale gesture detector. */ private final ScaleGestureDetector scaleGestureDetector; /** Manager of the scaling events. */ private final ScaleGestureManager scaleGestureManager; /** Move gesture detector. */ private final MoveGestureManager moveGestureDetector; /** Listener on clicks. */ private final ClickListener clickListener; /** View managed by this touch manager. */ private final WeakReference<ZoomableView> view; /** Indicates if a special gesture was activated. */ private boolean isSpecialGestureActivated = false; /** * @param view is the view associated to this manager. */ public TouchManager(ZoomableView view) { assert(view!=null); this.view = new WeakReference<>(view); this.scaleGestureManager = new ScaleGestureManager(); this.scaleGestureDetector = new ScaleGestureDetector(view.getContext(), this.scaleGestureManager); this.moveGestureDetector = new MoveGestureManager(); this.clickListener = new ClickListener(); } /** Initialize the manager. * <p> * This function must be invoked just after the construction and * before any Android event. */ public void init() { ZoomableView view = getView(); view.setClickable(true); view.setLongClickable(true); view.setOnLongClickListener(this.clickListener); view.setOnClickListener(this.clickListener); } /** Invoked to treat a touch event. * * @param ev * @return a flag with {@code 0x1} if the event is consumed, * {@code 0x2} if the View's touchEvent function must be invoked. */ public byte onTouchEvent(MotionEvent ev) { ZoomableView view = getView(); byte consumed = 0; // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. if (!view.isEnabled()) { this.isSpecialGestureActivated = false; if (view.isClickable() || view.isLongClickable()) { consumed |= 1; } } else { int action = ev.getAction() & MotionEvent.ACTION_MASK; // Reset the special gesture flag when touch down if (action==MotionEvent.ACTION_DOWN) { this.isSpecialGestureActivated = false; } // Let the ScaleGestureDetector inspect all events. if (this.scaleGestureDetector.onTouchEvent(ev)) { consumed |= 1; } // Let the move gesture detector inspect all events if (this.moveGestureDetector.onTouchEvent(ev, !(this.isSpecialGestureActivated || this.scaleGestureDetector.isInProgress()), this.scaleGestureDetector.isInProgress(), this.scaleGestureDetector.getFocusX(), this.scaleGestureDetector.getFocusY())) { consumed |= 1; } //logTouchEvent(ev.getAction()); // Let the inherited function inspect all events for click and long click events. if (!this.scaleGestureDetector.isInProgress() && !this.moveGestureDetector.isInProgress()) { if (action==MotionEvent.ACTION_POINTER_UP && !this.isSpecialGestureActivated) { consumed |= 2; } } else { this.isSpecialGestureActivated = true; } } return consumed; } /*private void logTouchEvent(int action) { String n; switch(action) { case MotionEvent.ACTION_CANCEL: n = "CANCEL"; break; case MotionEvent.ACTION_DOWN: n = "DOWN"; break; case MotionEvent.ACTION_MOVE: n = "MOVE"; break; case MotionEvent.ACTION_POINTER_DOWN: n = "PDOWN"; break; case MotionEvent.ACTION_POINTER_UP: n = "PUP"; break; case MotionEvent.ACTION_SCROLL: n = "SCROLL"; break; case MotionEvent.ACTION_UP: n = "UP"; break; default: n = "???"; break; } Log.d("TM", "type="+n+"; isSpecialTouch="+this.isSpecialGestureActivated +"; isScale="+this.scaleGestureDetector.isInProgress() +"; isMove="+this.moveGestureDetector.isInProgress()); }*/ /** Replies the view associated to this manager. * * @return the view. */ protected ZoomableView getView() { return this.view.get(); } /** Change the listener on long click. * * @param l is the new listener * @return the listener that must be used by the view. */ public final OnLongClickListener setOnLongClickListener(OnLongClickListener l) { if (l!=this.clickListener) this.clickListener.onLongClickListener = l; return this.clickListener; } /** Change the listener on click. * * @param l is the new listener * @return the listener that must be used by the view. */ public final OnClickListener setOnClickListener(OnClickListener l) { if (l!=this.clickListener) this.clickListener.onClickListener = l; return this.clickListener; } /** This class provides listening functions on the View click and * long click events. * * @author $Author: sgalland$ * @version $Name$ $Revision$ $Date$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class ClickListener implements OnLongClickListener, OnClickListener { public OnLongClickListener onLongClickListener = null; public OnClickListener onClickListener = null; private boolean isClickEnabled = true; /** */ public ClickListener() { // } /** Reset the internal flags of this click listener. */ public void reset() { this.isClickEnabled = true; } @SuppressWarnings("synthetic-access") @Override public boolean onLongClick(View v) { boolean b = false; this.isClickEnabled = false; if (getView().isLongClickable() && !TouchManager.this.scaleGestureDetector.isInProgress() && !TouchManager.this.moveGestureDetector.isInProgress()) { if (this.onLongClickListener!=null) { b = this.onLongClickListener.onLongClick(v); } if (!b) { PointerEventAndroid e = TouchManager.this.moveGestureDetector.lastPointerEvent; if (e!=null) { e.unconsume(); e.setWhen(System.currentTimeMillis()); getView().onLongClick(e); b = e.isConsumed(); } } } return b; } @SuppressWarnings("synthetic-access") @Override public void onClick(View v) { if (getView().isClickable() && !TouchManager.this.scaleGestureDetector.isInProgress() && !TouchManager.this.moveGestureDetector.isInProgress()) { if (this.isClickEnabled) { if (this.onClickListener!=null) { this.onClickListener.onClick(v); } PointerEventAndroid e = TouchManager.this.moveGestureDetector.lastPointerEvent; if (e!=null) { e.unconsume(); e.setWhen(System.currentTimeMillis()); getView().onClick(e); } } } this.isClickEnabled = true; } } // class ClickListener /** This class detects scale gesting. * It is based on the standard Android scaling gesture detector. * <p> * It invoked {@link TouchManager#onScale(float, float, float)} when * a scaling gesture is detected. * * @author $Author: sgalland$ * @version $Name$ $Revision$ $Date$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @see ScaleGestureDetector */ private class ScaleGestureManager extends ScaleGestureDetector.SimpleOnScaleGestureListener { /** */ public ScaleGestureManager() { // } @Override public boolean onScale(ScaleGestureDetector detector) { ZoomableView v = getView(); if (v.onScale( detector.getFocusX(), detector.getFocusY(), detector.getScaleFactor())) { v.repaint(); } return true; } } // class ScaleGestureManager /** This class detects move gestures. * * @author $Author: sgalland$ * @version $Name$ $Revision$ $Date$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class MoveGestureManager { /** Coordinate of the last touch. */ private float lastTouchX = Float.NaN; /** Coordinate of the last touch. */ private float lastTouchY = Float.NaN; /** Indicates if the move gesture is activated (by a touch down for example). */ private boolean isActivated = false; /** Indicates if the move gesture is in progress, ie. * touch down and move events were found. */ private boolean inProgress = false; /** Identifier of the current active pointer. */ private int activePointerId = INVALID_POINTER_ID; public PointerEventAndroid lastPointerEvent = null; /** */ public MoveGestureManager() { // } /** Replies if a move gesture is in progress. * * @return <code>true</code> if a move gesture is in progress; * otherwise <code>false</code>. */ public boolean isInProgress() { return this.inProgress; } /** Invoked for detecting touch gestures. * * @param ev * @param canInvokeViewCallbacks indicates if this manager is able to invoke the view callbacks. * @param isScaleGestureInProgression indicates if the scale gesture is under progress. * @param scaleGestureX is the X coordinate of the scale gesture, if under progress; otherwise the value is indetermined. * @param scaleGestureY is the Y coordinate of the scale gesture, if under progress; otherwise the value is indetermined. * @return <code>true</code> if the event is consumed; * <code>false</code> otherwise. */ @SuppressWarnings("synthetic-access") public boolean onTouchEvent(MotionEvent ev, boolean canInvokeViewCallbacks, boolean isScaleGestureInProgression, float scaleGestureX, float scaleGestureY) { int pointerIndex, pointerId; float x, y; int action = ev.getActionMasked(); ZoomableView view = getView(); if (isScaleGestureInProgression) { x = scaleGestureX; y = scaleGestureY; } else { x = ev.getX(); y = ev.getY(); } this.lastPointerEvent = new PointerEventAndroid( TouchManager.this, x, y, ev); boolean consumed = false; switch (action) { case MotionEvent.ACTION_DOWN: // The screen was touched. The touch-down event permits // to initialize the attributes. this.activePointerId = INVALID_POINTER_ID; this.lastPointerEvent.unconsume(); if (canInvokeViewCallbacks) { view.onPointerPressed(this.lastPointerEvent); } consumed = this.lastPointerEvent.isConsumed(); if (!consumed) { this.isActivated = true; this.inProgress = false; this.activePointerId = ev.getPointerId(0); this.lastTouchX = ev.getX(0); this.lastTouchY = ev.getY(0); consumed = true; this.lastPointerEvent.setXY(this.lastTouchX, this.lastTouchY); } TouchManager.this.clickListener.reset(); break; case MotionEvent.ACTION_MOVE: if (this.isActivated) { this.inProgress = true; pointerIndex = ev.findPointerIndex(this.activePointerId); x = ev.getX(pointerIndex); y = ev.getY(pointerIndex); this.lastPointerEvent.setXY(x, y); float dx = view.pixel2logical_size(x - this.lastTouchX); float dy = view.pixel2logical_size(y - this.lastTouchY); this.lastTouchX = x; this.lastTouchY = y; if (view.isMoveDirectionInverted()) { view.translateFocusPoint(dx, dy); } else { view.translateFocusPoint(-dx, -dy); } // Always consume the event on moves consumed = true; } else if (canInvokeViewCallbacks) { this.lastPointerEvent.unconsume(); view.onPointerDragged(this.lastPointerEvent); consumed = this.lastPointerEvent.isConsumed(); } break; case MotionEvent.ACTION_UP: this.lastTouchX = ev.getX(); this.lastTouchY = ev.getY(); if (this.isActivated) { this.isActivated = false; this.inProgress = false; this.activePointerId = INVALID_POINTER_ID; view.repaint(); consumed = true; } else { this.lastPointerEvent.unconsume(); if (canInvokeViewCallbacks) { view.onPointerReleased(this.lastPointerEvent); } consumed = this.lastPointerEvent.isConsumed(); } break; case MotionEvent.ACTION_CANCEL: this.lastTouchX = ev.getX(); this.lastTouchY = ev.getY(); if (this.isActivated) { this.isActivated = false; this.inProgress = false; this.activePointerId = INVALID_POINTER_ID; view.repaint(); consumed = true; } else { this.lastPointerEvent.unconsume(); if (canInvokeViewCallbacks) { view.onPointerReleased(this.lastPointerEvent); } consumed = this.lastPointerEvent.isConsumed(); } break; case MotionEvent.ACTION_POINTER_UP: if (this.isActivated) { pointerIndex = ((ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT); pointerId = ev.getPointerId(pointerIndex); if (pointerId == this.activePointerId) { // This was our active pointer going up. Choose a new // active pointer and adjust accordingly. int newPointerIndex = pointerIndex == 0 ? 1 : 0; this.lastTouchX = ev.getX(newPointerIndex); this.lastTouchY = ev.getY(newPointerIndex); this.activePointerId = ev.getPointerId(newPointerIndex); view.repaint(); consumed = true; } else { this.lastTouchX = ev.getX(); this.lastTouchY = ev.getY(); } } else { this.lastTouchX = ev.getX(); this.lastTouchY = ev.getY(); } break; default: // The other events are ignored. } return consumed; } } // class MoveGestureDetector }