Android OnLongClickListener not firing on MapView - java

I just registered an OnLongClickListener on my my MapView on an Android app I'm currently writing. For some reason however the onLongClick event doesn't fire.
Here's what I've written so far:
public class FriendMapActivity extends MapActivity implements OnLongClickListener {
private static final int CENTER_MAP = Menu.FIRST;
private MapView mapView;
private MapController mapController;
//...
private boolean doCenterMap = true;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.friendmapview);
this.mapView = (MapView) findViewById(R.id.map_view);
this.mapController = mapView.getController();
mapView.setBuiltInZoomControls(true);
mapView.displayZoomControls(true);
mapView.setLongClickable(true);
mapView.setOnLongClickListener(new OnLongClickListener() {
public boolean onLongClick(View v) {
//NEVER FIRES!!
return false;
}
});
//...
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_3:
mapController.zoomIn();
break;
case KeyEvent.KEYCODE_1:
mapController.zoomOut();
break;
}
return super.onKeyDown(keyCode, event);
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int actionType = ev.getAction();
switch (actionType) {
case MotionEvent.ACTION_MOVE:
doCenterMap = false;
break;
}
return super.dispatchTouchEvent(ev);
}
...
}
May overlays which I'm adding cause the problem?? Any suggestions?

I ran into the same problem and there is a simple solution to your problem actually; it's because you're using the wrong type of listener.
You should use the OnMapLongClickListener() object from the OnMapLongClickListener interface.
Hopefully everything should work properly :)
Please tell me if it works.

I just ran into this problem. I tried the solution above, but it doesn't quite work 100% in that we want the long press action to fire, even if the user is still holding a finger down.
This is how I implemented a solution, using a handler and a delayed task -
As a side note, I used a similar type implementation, but in reverse, to hide/show zoom controls on touch/etc..
private Handler mHandler = new Handler();
private final Runnable mTask = new Runnable() {
#Override
public void run() {
// your code here
}
};
#Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// record the start time, start the timer
mEventStartTime = ev.getEventTime();
mHandler.postDelayed(mTask, LONG_PRESS_TIME);
} else if (ev.getAction() == MotionEvent.ACTION_UP) {
// record the end time, dont show if not long enough
mEventEndTime = ev.getEventTime();
if (mEventEndTime - mEventStartTime < LONG_PRESS_TIME) {
mHandler.removeCallbacks(mTask);
}
} else {
// moving, panning, etc .. up to you whether you want to
// count this as a long press - reset timing to start from now
mEventStartTime = ev.getEventTime();
mHandler.removeCallbacks(mTask);
mHandler.postDelayed(mTask, LONG_PRESS_TIME);
}
return super.onTouchEvent(ev);
}

In the mean time I found the "solution" (or workaround, call it as you like) by myself. The way I worked through this issue is by using a GestureDetector and forwarding all touch events to that object by implementing an according OnGestureListener interface.
I've posted some code on my blog if anyone is interested:
http://juristr.com/blog/2009/12/mapview-doesnt-fire-onlongclick-event/
Don't ask me why this didn't work by hooking up the OnLongClickListener directly on the MapView. If someone has an explanation let me know :)
UPDATE:
My previously suggested solution using a GestureDetector posed some drawbacks. So I updated the blog post on my site.

In WebView framework code performLongClick() is used to handle long press event, this is how Android copy Text Feature is implemented in Browser, that is why onLongClick is not been fired.

Related

How do I get an image view to continually move across the screen when a button is held down?

I have searched high and low for an answer to this and can't find one anywhere that works.
For my university assignment i need to create an adventure game in android studio. I want it so when I click down and hold an arrow button (so in the case given its the up button), that the ImageView (player) will continually move across the screen until I release the button. Ive tried OnTouchListeners and mouse events using ACTION_UP and ACTION_DOWN and that works but not for what i need as it still only moves one step when clicked.
ImageView IV_player;
Button ButtonUp;
IV_player = (ImageView) findViewById(R.id.IV_player);
ButtonUp = (Button) findViewById(R.id.ButtonUp);
ButtonUp.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
IV_player.setY(IV_player.getY() - 32);
}
});
Treat your touch listener like a state machine. When an ACTION_DOWN event occurs, start doing whatever action you want to do. When an ACTION_UP/ACTION_CANCEL event occurs stop your action. So how do you go about implementing this?
Your state flag can be a simple boolean:
boolean shouldCharacterMove = false;
Define your touch listener for the view.
ButtonUp.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
setShouldCharacterMove(true);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
setShouldCharacterMove(false);
break;
}
return true;
}
});
Before we define setShouldCharacterMove we need to figure out a way to move items. We can do this through a Runnable that runs after X milliseconds.
private final Runnable characterMoveRunnable = new Runnable() {
#Override
public void run() {
float y = IV_player.getTranslationY();
IV_player.setTranslationY(y + 5); // Doesn't have to be 5.
if (shouldCharacterMove) {
IV_player.postDelayed(this, 16); // 60fps
}
}
};
Now we can define setShouldCharacterMove:
void setShouldCharacterMove(boolean shouldMove) {
shouldCharacterMove = shouldMove;
IV_player.removeCallbacks(characterMoveRunnable);
if (shouldMove) {
IV_player.post(characterMoveRunnable);
}
}

Tapping / Swiping not detected in Android Studio Google Glass emulator

I'm running an emulator for google glass as seen here, works pretty flawlessly by showing settings, main display and even my activity (which I pretend to be an interactive static card).
http://mobilevangelist.com/2014/01/02/gdk-and-the-android-emulator/
I've seen that the motion gestures are captured using onKeyUp or onKeyDown events but neither are working and I don't understand why.
Here is my code.
public class LiveCardMenuActivity extends Activity {
private TextView textView;
#Override //isn't catching a thing, even with onKeyDown (mouse taps or slides in the emulator)
public boolean onKeyUp(int keycode, KeyEvent event){
Log.d("tag","keyUp");
if(keycode == KeyEvent.KEYCODE_DPAD_CENTER){
Log.d("tag","keypadcenter");
textView.setText("tap");
}else if(keycode == KeyEvent.KEYCODE_BACK){
Log.d("tag","swipedown");
textView.setText("down");
}
return true;
}
#Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
setContentView(R.layout.live_card);
//does successfully, I can see the layout in the emulator
//and I can swipe it to the left (returning to the main display successfully)
textView = (TextView) findViewById(R.id.textView);
Log.d("tag","attached to window");
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.live_card, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_stop:
// Stop the service which will unpublish the live card.
stopService(new Intent(this, LiveCardService.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
#Override
public void onOptionsMenuClosed(Menu menu) {
super.onOptionsMenuClosed(menu);
// Nothing else to do, finish the Activity.
finish();
}
}
Can someone help me on this one? Tyvm!
Try calling setFocusable(true) on your content view.
Though I don't know much about how this emulator works, this is how you might get this working for standard Android.
You're using onKeyUp() when you need to be using onKeyDown(). Here's an example I'm using in my Glass project (but I have a physical Google Glass, not using an emulator):
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch( keyCode ) {
case KeyEvent.KEYCODE_DPAD_CENTER:
Log.e("GESTURE_EVENT", "PostVideoActivity.onKeyDown() TAP/KEYCODE_DPAD_CENTER");
// DO SOMETHING
return true;
case KeyEvent.KEYCODE_BACK:
Log.e("GESTURE_EVENT", "PostVideoActivity.onKeyDown() SWIPE/KEYCODE_BACK");
// DO SOMETHING
return true;
default:
Log.wtf("GESTURE_EVENT", "PostVideoActivity.onKeyDown() DEFAULT -- SHOULDNT BE HERE!");
return false;
}
}
edit Maybe this is an issue with the emulator? I doubt it though but you can test this theory by creating some dummy Android project for a phone/phone emulator that is known to work. If this snippet doesn't even work for the other Android emulation then maybe its something else?

How to get click, double tap, and long click gestures for a view inside a gridview?

I have ImageViews inside of a GridView, I had been using an OnItemClickListener along with an OnItemLongClickListener set on the GridView to open the image on a larger page and to delete the item respectively. Now, I have to implement rearranging of the ImageViews in the GridView, so I plan to move the deletion function to a double tap gesture, (please do not lecture me on android style guidelines (including the possibility of contextual actionbars, which I suggested), as this is what my boss asks for to emulate functions inside our ios app) in order to reserve long click for the drag and drop. I set an OnTouchListener on each view in the getView of my custom adapter, feeding a GestureDetecter with a listener extending SimpleOnGestureListener the given MotionEvent with onTouchEvent. I know what to do up to that point, but when I included (onDown of course, to get other callbacks) onDoubleTap, onSingleTapConfirmed, and onLongPressed all taps were interpreted as long clicks. And when I removed the both callback methods to be replaced with their listener counterparts once again (ie OnItemClickListeners) I received those two gestures but not the double tap, which makes sense, as double taps start out as a single tap unless you wait for a bit less than a second to confirm them as singles rather than potential doubles. I also tried placing the OnItemClickListener, but not the OnItemLongClickListener, with the callback in the extended SimpleOnGestureListener. In this case, only long presses were ever interpreted, but other gestures caused no response. Here is my code as it stands now, and do note that I returned false in the onTouchEvent in order to allow others (itemclicklisteners) to consume the events following the attempts made in the GestureDetector.
public class MainBoardGridAdapter extends GenericBoardGridAdapter implements OnItemLongClickListener {
private class Ges extends GestureDetector.SimpleOnGestureListener {
int pos;
public Ges(View v) {
pos = (Integer) v.getTag();
}
#Override
public boolean onDown(MotionEvent me) {
//this does get called but none of these methods below
return true;
}
#Override
public boolean onDoubleTap(MotionEvent me) {
new DeleteConfirmationPrompt(c, "board") {
#Override
protected boolean onDeleteConfirmed() {
// delete the visionboard
return deleteBoard(pos);
}
}; // Constructor shows dialog
return false;
}
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
MainBoardGridAdapter.super.flagForUpdate(pos);
if (listener != null) {
listener.onBoardClick(pos, getName(pos));
} else {
Intent intent = new Intent(c, VisionBoardActivity.class);
intent.putExtra(VisionBoardActivity.EXTRA_VISION_BOARD_NAME, getName(pos));
frag.startActivityForResult(intent, MyBoardsFragment.REQUEST_EDIT);
}
return false;
}
}
#Override
public boolean onItemLongClick(AdapterView<?> parent, View v,
final int pos, long id) {
Toast.makeText(c, "Long", Toast.LENGTH_LONG).show();
return false;
}
// called by getView of extended adapter
#Override
public void onImageLoaded(ImageView iv, String data, View root) {
iv.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
(new GestureDetector(c, (new Ges(v)))).onTouchEvent(event);
return false;
}
});
}
}
And in the Activity, gv is my GridView:
gv.setOnItemLongClickListener(gridAdapter);
Also note that I had been using true in the return value in the GestureDetector methods, until trying the current configuration.There was no difference to be seen.
Thank you for your valuable time and help, I hope that someone will be able to point out what I am doing incorrectly.
-Jackson

onInterceptTouchEvent only gets ACTION_DOWN

Why do ViewGroup's only get ACTION_DOWN in the onInterceptTouchEvent? According to the docs, as long as false is returned it should receive all the event types.
http://developer.android.com/reference/android/view/ViewGroup.html#onInterceptTouchEvent%28android.view.MotionEvent%29
Point #3.
Sample code:
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new Container(this));
}
private class Container extends LinearLayout {
public Container(Context context) {
super(context);
setBackgroundColor(0xFF0000FF);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(TAG, "onInterceptTouchEvent");
int action = ev.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "onInterceptTouchEvent.ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "onInterceptTouchEvent.ACTION_MOVE");
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
Log.i(TAG, "onInterceptTouchEvent.ACTION_UP");
break;
}
return super.onInterceptTouchEvent(ev);
}
}
}
I'll answer my own question: onInterceptTouchEvent only get called if the parent has a child view which returns "true" from onTouchEvent. Once the child returns true, the parent now has a chance to intercept that event.
I get the same problem. I had read many posts about it:
onInterceptTouchEvent only gets ACTION_DOWN
onInterceptTouchEvent's ACTION_UP and ACTION_MOVE never gets called
onInterceptTouchEvent, onTouchEvent only see ACTION_DOWN
onInterceptTouchEvent never receives action_move
I also had read android doc:
http://developer.android.com/training/gestures/viewgroup.html
http://developer.android.com/reference/android/view/ViewGroup.html#onInterceptTouchEvent(android.view.MotionEvent)
All answers are same. I tried many times, always not get onInterceptTouchEvent
() be called if not down event.
I read source code, I guess that something is changed:
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
According above code, onInterceptTouchEvent(ev) is only be called when MotionEvent.ACTION_DOWN, this is what we tried and found. So, what I guess is, the code is changed, but doc not.
If you want spy or monitor all the events include those been sent to child views, you can override dispatchTouchEvent() like this:
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
MyLog.d(MyLog.DEBUG, "dispatchTouchEvent(): "+event.getAction());
if (isEnabled()) {
MyLog.d(MyLog.DEBUG, "dispatchTouchEvent()2: "+event.getAction());
processEvent(event);//here you get all events include move & up
super.dispatchTouchEvent(event);
return true; //to keep receive event that follow down event
}
return super.dispatchTouchEvent(event);
}
I have the runnable code at: https://github.com/maxyou/gesturebutton/blob/master/src/com/maxproj/gesturebutton/GestureButtonLayout.java
dispatchTouchEvent, onInterceptTouchEvent, requestDisallowInterceptTouchEvent
[Touch event flow]
The official doc
Activity.dispatchTouchEvent(MotionEvent) - This allows your Activity to intercept all touch events before they are dispatched to the window.
ViewGroup.onInterceptTouchEvent(MotionEvent) - This allows a ViewGroup to watch events as they are dispatched to child Views. It is recursively function (from parent to parent)
ViewParent.requestDisallowInterceptTouchEvent(boolean) - Call this upon a parent View to indicate that it should not intercept touch events with onInterceptTouchEvent(MotionEvent).

On zoom event for google maps on android

We're building an application which is using the google maps api for android.
I have my MapController and MapView, and I enable the built-in zoom controls using:
mapView.setBuiltInZoomControls(true);
I would now like to get an event when the user actually zooms on the map, how do I go about that? I can find no such event or any general event where I could detect a change in zoom level.
Update
The mapView.getZoomControls() is deprecated. And the documentation suggests using mapView.setBuiltInZoomControls(bool) instead. This is okay, but I simply cannot figure out how to act on events from the built in zoom controls.
With the Google Maps Android API v2 you can use a GoogleMap.OnCameraChangeListener like this:
mMap.setOnCameraChangeListener(new OnCameraChangeListener() {
private float currentZoom = -1;
#Override
public void onCameraChange(CameraPosition pos) {
if (pos.zoom != currentZoom){
currentZoom = pos.zoom;
// do you action here
}
}
});
You can implement your own basic "polling" of the zoom value to detect when the zoom has been changed using the android Handler.
Using the following code for your runnable event. This is where your processing should be done.
private Handler handler = new Handler();
public static final int zoomCheckingDelay = 500; // in ms
private Runnable zoomChecker = new Runnable()
{
public void run()
{
checkMapIcons();
handler.removeCallbacks(zoomChecker); // remove the old callback
handler.postDelayed(zoomChecker, zoomCheckingDelay); // register a new one
}
};
Start a callback event using
protected void onResume()
{
super.onResume();
handler.postDelayed(zoomChecker, zoomCheckingDelay);
}
and stop it when you're leaving the activity using
protected void onPause()
{
super.onPause();
handler.removeCallbacks(zoomChecker); // stop the map from updating
}
Article this is based on can be found here if you want a longer write up.
I use this library which has an onZoom function listener. http://code.google.com/p/mapview-overlay-manager/. Works well.
As the OP said, use the onUserInteractionEvent and do the test yourself.
Here is a clean solution. Simply add this TouchOverlay private class to your activity and a method called onZoom (that is called by this inner class).
Note, you'll have to add this TouchOverlay to your mapView e.g.
mapView.getOverlays().add(new TouchOverlay());
It keeps track of the zoom level whenever the user touches the map e.g. to double-tap or pinch zoom and then fires the onZoom method (with the zoom level) if the zoom level changes.
private class TouchOverlay extends com.google.android.maps.Overlay {
int lastZoomLevel = -1;
#Override
public boolean onTouchEvent(MotionEvent event, MapView mapview) {
if (event.getAction() == 1) {
if (lastZoomLevel == -1)
lastZoomLevel = mapView.getZoomLevel();
if (mapView.getZoomLevel() != lastZoomLevel) {
onZoom(mapView.getZoomLevel());
lastZoomLevel = mapView.getZoomLevel();
}
}
return false;
}
}
public void onZoom(int level) {
reloadMapData(); //act on zoom level change event here
}
The android API suggests you use ZoomControls which has a setOnZoomInClickListener()
To add these zoom controls you would:
ZoomControls mZoom = (ZoomControls) mapView.getZoomControls();
And then add mZoom to your layout.
You Can use
ZoomButtonsController zoomButton = mapView.getZoomButtonsController();
zoomButton.setOnZoomListener(listener);
hope it helps
I think there might another answer to this question. The Overlay class draw method is called after any zoom and I believe this is called after the zoom level changes. Could you not architect your app to take advantage of this? You could even use a dummy overlay just to detect this if you wanted to. Would this not be more efficient than using a runnable with a delay?
#Override
public boolean draw (Canvas canvas, MapView mapView, boolean shadow, long when) {
int zoomLevel = mapView.getZoomLevel();
// do what you want with the zoom level
return super.draw(canvas, mapView, shadow, when);
}
For pre-V2.0, I made a class which extends MapView that alerts a listener with events when the map region starts changing (onRegionBeginChange) and stops changing (onRegionEndChange).
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
public class ExMapView extends MapView {
private static final String TAG = ExMapView.class.getSimpleName();
private static final int DURATION_DEFAULT = 700;
private OnRegionChangedListener onRegionChangedListener;
private GeoPoint previousMapCenter;
private int previousZoomLevel;
private int changeDuration; // This is the duration between when the user stops moving the map around and when the onRegionEndChange event fires.
private boolean isTouched = false;
private boolean regionChanging = false;
private Runnable onRegionEndChangeTask = new Runnable() {
public void run() {
regionChanging = false;
previousMapCenter = getMapCenter();
previousZoomLevel = getZoomLevel();
if (onRegionChangedListener != null) {
onRegionChangedListener.onRegionEndChange(ExMapView.this, previousMapCenter, previousZoomLevel);
}
}
};
public ExMapView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ExMapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public ExMapView(Context context, String apiKey) {
super(context, apiKey);
init();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
isTouched = event.getAction() != MotionEvent.ACTION_UP;
return super.onTouchEvent(event);
}
#Override
public void computeScroll() {
super.computeScroll();
// If the map region is still changing (user is still scrolling or zooming), reset timer for onRegionEndChange.
if ((!isTouched && !getMapCenter().equals(previousMapCenter)) || (previousZoomLevel != getZoomLevel())) {
// If the region has just begun changing, fire off onRegionBeginChange event.
if (!regionChanging) {
regionChanging = true;
if (onRegionChangedListener != null) {
onRegionChangedListener.onRegionBeginChange(this, previousMapCenter, previousZoomLevel);
}
}
// Reset timer for onRegionEndChange.
removeCallbacks(onRegionEndChangeTask);
postDelayed(onRegionEndChangeTask, changeDuration);
}
}
private void init() {
changeDuration = DURATION_DEFAULT;
previousMapCenter = getMapCenter();
previousZoomLevel = getZoomLevel();
}
public void setOnRegionChangedListener(OnRegionChangedListener listener) {
onRegionChangedListener = listener;
}
public void setChangeDuration(int duration) {
changeDuration = duration;
}
public interface OnRegionChangedListener {
public abstract void onRegionBeginChange(ExMapView exMapView, GeoPoint geoPoint, int zoomLevel);
public abstract void onRegionEndChange(ExMapView exMapView, GeoPoint geoPoint, int zoomLevel);
}
}
I used a mixture of the above. I found that using the timmer to start a thread every half a second cause the map to be really jenky. Probably because I was using a couple of If statments everytime. So I started the thread on a post delay of 500ms from the onUserInteraction. This gives enough time for the zoomLevel to update before the thread starts to run thus getting the correct zoomlevel without running a thread every 500ms.
public void onUserInteraction() {
handler.postDelayed(zoomChecker, zoomCheckingDelay);
}

Categories

Resources