Here's another curiosity I came across today while programming on my Android project. I was creating a MapView for displaying some interesting stuff on it.
Update: This post got a bit lengthy due to different problems that popped up while I was writing the post. So if you prefer to jump directly due to the final solution, feel free
to do so.
Now there are different events the user may interact when having a map on the screen like just simple clicks or even moves when navigating on the map. So first of all I have my MapActivity class which again loads a MapView on it, defined in some layout xml file. So far so good. For reacting on click or move events I had to override the MapActivities "dispatchOnTouchEvent" method.
public class MyMapActivity extends MapActivity {
...
@Override
public void onCreate(...){
super.onCreate(...);
setContentView(R.layout.mymapviewlayout);
this.mapView = (MapView)findViewById(R.id.myMapView);
this.mapView.set....
...
this.mapView.setClickable(true);
...
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int actionType = ev.getAction();
switch (actionType) {
case MotionEvent.ACTION_MOVE:
//react properly
break;
}
return super.dispatchTouchEvent(ev);
}
...
}
That worked pretty well. Now comes the strange behavior. I wanted to differentiate the action of a simple click (press down with immediate release) from a longer click (press down, hold a while and then release again). A fast search on the Android docs revealed the "setOnLongClickListener(OnLongClickListener)" method. Pretty nice, so I just needed to implement the listener an that's it? Actually that didn't work I adjusted my view s.t. it sets the necessary flags and implements the correct listeners...
public class MyMapActivity extends MapActivity {
...
@Override
public void onCreate(...){
...
this.mapView.setClickable(true);
this.mapView.setLongClickable(true);
//direct listener implementation
mapView.setOnLongClickListener(new OnLongClickListener() {
public boolean onLongClick(View v) {
//react
return false;
}
});
...
}
...
}
...however with no success. The event just didn't fire. I also tried to adapt the overridden dispatchTouchEvent method s.t. it forwards the event to the MapView like
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int actionType = ev.getAction();
switch (actionType) {
case MotionEvent.ACTION_MOVE:
//react properly
break;
}
return this.mapView.dispatchTouchEvent(ev);
}
..which would sound plausible, however with no success.
So the final solution I came up with was to add a
GestureDetector to my MapActivity and delegate all touch events to that object. The according OnGestureListener implements a couple of event handlers, under which also the "onLongPress(...)" event handler. So I had to change my implementation to the following
public class MyMapActivity extends MapActivity implements OnGestureListener {
...
private GestureDetector gestureDetector;
@Override
public void onCreate(...){
super.onCreate(...);
setContentView(R.layout.mymapviewlayout);
this.gestureDetector = new GestureDetector(this);
this.gestureDetector.setIsLongpressEnabled(true);
this.mapView = (MapView)findViewById(R.id.myMapView);
this.mapView.set....
...
this.mapView.setClickable(true);
...
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int actionType = ev.getAction();
switch (actionType) {
case MotionEvent.ACTION_MOVE:
//react properly
break;
}
return gestureDetector.onTouchEvent(ev);
}
...
//other methods of the OnGestureListener interface
public void onLongPress(MotionEvent e) {
ActivityUtils.showToast(this, "Pushed down", 3000);
}
...
}
The result after doing a "long click" on the map:
That looks like it works :) . Don't ask me why it didn't work by just attaching the OnLongClickListener on the map and dispatching events to the MapView. It's now 11:40 PM and I think it's time to have a bit of sleep.
Update:Indeed, it was late last night. Today morning when I launched my MapView the onLongClick worked, but everything else didn't work any more including move events click events etc. The reason is obvious: I forward everything to my GestureDetector wherefore the MapView will never get the events.
Due to a suggestion of Paul (see comments), I checked the inheritance hierarchy of the MapView and it inherits from ViewGroup. The LongClick event is defined on the level of the View object and for some reason the MapView doesn't react on it nor it lets you override the behavior.
So the final solution to the problem is to handle it on your own. What I did is to create my custom MapView which inherits from MapView. On that class I've overriden the onTouch event and made some kind of hack on it to achieve a long-touch event. The approach was basically to measure the time between the MotionEvent.ACTION_DOWN and the MotionEvent.ACTION_UP event.
public class MyMap extends MapView{
...
public MyMapView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if(ev.getAction() == MotionEvent.ACTION_DOWN){
//record the start time
startTime = ev.getEventTime();
}else if(ev.getAction() == MotionEvent.ACTION_UP){
//record the end time
endTime = ev.getEventTime();
}
//verify
if(endTime - startTime > 1000){
//we have a 1000ms duration touch
//propagate your own event
return true; //notify that you handled this event (do not propagate)
}
}
}
What I left out here (since I don't want you to steal your own creativity ;) ) is to handle moves on the map which may also otherwise result in a long click. It's quite simple to fix that.
Questions? Thoughts? Hit me up
on Twitter