If you are impatient, you can skip directly to the solution.

The new implementation of the Android Map (v2) is great in many ways and their support for map fragments is awesome, but they fall a bit short in a couple of minor but very necessary implementations. That makes the maps API a bit incomplete.

I am referring in particular to the ability to distinguish between a user dragging (panning) the map, verses a programmatic move of the map.

That looks like a very basic need that a lot of Android programmers would face when dealing with the new map implementation. I have a number of apps, where I would like to update the map after the user has panned the map to a new location and I want to do that ONLY after they had stopped moving the map. I don’t want to trigger multiple updates while they are in the process of panning.

As you can deduce so far, there is no straight up way to implement this very basic behavior… or at least I could not find one. So, if you have found one, please let me know… I am only human and I might have missed something!

There are a couple of listeners implemented that kind of give you something to work with at a first glance, but they are both incomplete.

We have onCameraChangeListener, which gets called every time the map moves, zooms in/out or the camera angle changes, but it is completely useless in helping us implement the above needed behavior. It does not tell you in any way if the map was moved due to a user interaction or due to some other event triggering the map’s “camera change”. Furthermore, if you implement this listener you will notice that it gets triggered multiple times during a map panning. So, this makes it completely inefficient, especially when you are fetching data over the network all the time. The implementation of this listener could have been a bit more complete, if the only added a boolean value to the GoogleMap.OnCameraChangeListener indicating if the action was done by a human or not. Just like they implemented the SeekBar.OnSeekBarChangeListener.

Furthermore, you might think that this situation can be remedied with the map’s setOnMapClickListener, but you would be wrong, as you cannot use it in any way to distinguish between a touch down and a touch up events.

That finally leads us down the path of implementing this basic map behavioral need ourselves! I would like to think that Google does this to us, because they know we are better programmers than those stinking iOS dudes, who are used to getting anything they need handed on a platter of APIs:-) But may be I am wrong again…

So, a basic search led me to a couple of stackoverflow posts that try to tackle this issue:

How to handle onTouch event for map in Google Map API v2?
Google maps android api v2 – detect touch on map

But that sill did not completely solve my problem due to a couple of reasons. One is that they use an ugly static boolean variable to communicate back to the map activity if the map was touched or not. Furthermore they use that boolean in the setOnCameraChangeListener to decide whether to update the map or not, which flat out does not work. And the reason it does not work is because in most of the cases the setOnCameraChangeListener listener is triggered before the boolean value was updated, resulting in a failure of the code after that to execute.

So I took all this and made a few small but significant changes, namely using a custom interface to trigger the map to update at the right time. The TouchableWrapper class declares a UpdateMapAfterUserInterection interface, which is implemented by the Map Activity. That gives us a very fine control over the map events and what we want to do with them. Now a complete disclaimer… This all works very well, but it is a hacky way of doing things. Extending the map fragment, then putting a frame layout on top of if to intercept user interaction and then passing it over to the map activity just feels (and is) hacky. But it is the best solution I could find or think of for now. Please share if you have a better solution!

Here is the complete implementation:

TouchableWrapper class defining the UpdateMapAfterUserInterection interface


public  class TouchableWrapper extends FrameLayout {

	private long lastTouched = 0;
	private static final long SCROLL_TIME = 200L; // 200 Milliseconds, but you can adjust that to your liking
	private UpdateMapAfterUserInterection updateMapAfterUserInterection;

	public TouchableWrapper(Context context) {
		super(context);
		// Force the host activity to implement the UpdateMapAfterUserInterection Interface
		try {
			updateMapAfterUserInterection = (ActivityMapv2) context;
        } catch (ClassCastException e) {
            throw new ClassCastException(context.toString() + " must implement UpdateMapAfterUserInterection");
        }
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		switch (ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			lastTouched = SystemClock.uptimeMillis();
			break;
		case MotionEvent.ACTION_UP:
			final long now = SystemClock.uptimeMillis();
			if (now - lastTouched > SCROLL_TIME) {
				// Update the map
				updateMapAfterUserInterection.onUpdateMapAfterUserInterection();
			}
			break;
		}
		return super.dispatchTouchEvent(ev);
	}

	// Map Activity must implement this interface
    public interface UpdateMapAfterUserInterection {
        public void onUpdateMapAfterUserInterection();
    }
}

MySupportMapFragment class


public class MySupportMapFragment extends SupportMapFragment{
	public View mOriginalContentView;
	public TouchableWrapper mTouchView;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
		mOriginalContentView = super.onCreateView(inflater, parent, savedInstanceState);
		mTouchView = new TouchableWrapper(getActivity());
		mTouchView.addView(mOriginalContentView);
		return mTouchView;
	}

	@Override
	public View getView() {
		return mOriginalContentView;
	}
}

The layout for the map activity


<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mapFragment"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_below="@+id/buttonBar"
    class="com.myFactory.myApp.MySupportMapFragment"
    />

And finally the Map Activity


public class ActivityMapv2 extends FragmentActivity implements UpdateMapAfterUserInterection {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  }

// Implement the interface method
public void onUpdateMapAfterUserInterection() {
		// TODO Update the map now
        }
 }
How to detect a user pan/touch/drag on Android Map v2

35 thoughts on “How to detect a user pan/touch/drag on Android Map v2

  • April 21, 2013 at 5:17 pm
    Permalink

    Hi Dimitar,

    This is very good! I’m new to android development and cannot make your code work from the above. Will you publish your solution as an Eclipse Android Application Project to help me please?

    Thanks you again

    Dave

  • April 22, 2013 at 8:57 pm
    Permalink

    @Dave,

    All you need is in the code I published in the post.

    You still need to set up your application with Google Services and the Google Maps API v2 first. Follow the guide here:

    https://developers.google.com/maps/documentation/android/intro

    Once you get the sample map fragment to show up in your project, then use my code above.

    If I sent you a project, it would not work for you. Enabling the Google Maps services is based on a package name and a keystore.

  • April 23, 2013 at 8:01 am
    Permalink

    Hi Dimitar,

    I finally got your solution working and as you said, it works very well! 🙂

    I am wondering how to monitor map interactivity with your solution. I am creating the map in the class ActivityMapv2.

    I am thinking of creating a Boolean variable called MapNotMoving in the class ActivityMapv2. When the value is true I can fetch data over the network using ASYNC. While doing this if MapNotMoving changes to false I must stop fetching data over the network.

    Do you think this is the best way? If you would do it this way where would you setup this variable and in which class?

    Thanks again,
    Dave

  • April 24, 2013 at 10:16 am
    Permalink

    Works like charm! The exact approach I was trying to achieved. Thanks!

    Why don’t you uploaded to a github repo so it is easily tracked by google and easier to find?

  • April 25, 2013 at 9:37 pm
    Permalink

    Awesome blog! Do you have any hints for aspiring writers?
    I’m hoping to start my own site soon but I’m a little lost on
    everything. Would you recommend starting with a free platform
    like WordPress or go for a paid option? There are so many options out there that I’m completely confused .. Any recommendations? Thanks!

  • April 26, 2013 at 6:18 pm
    Permalink

    @Dave,

    I am not sure what the practical need for what you are asking would be. But I don’t think you need any boolean variable. To accomplish this, all you have to do is cancel any current AsyncTasks before you trigger a new one.

  • April 26, 2013 at 6:19 pm
    Permalink

    @Matias,

    Time has been a very valuable commodity for me lately… publishing on GitHub is a good idea… will do at first opportunity.

  • May 9, 2013 at 7:41 am
    Permalink

    You are sooo close to doing it right

    never cast a context to a activity/fragment

    here is the correct way to do it
    Also added more contructors, so you can add it redirect from xml 🙂

    package com.crowdit.places.map;

    import android.content.Context;
    import android.os.SystemClock;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.widget.FrameLayout;

    public class TouchableWrapper extends FrameLayout {

    public TouchableWrapper(Context context, AttributeSet attrs) {
    super(context, attrs);
    }

    public TouchableWrapper(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    }

    public TouchableWrapper(Context context) {
    super( context);
    }

    private long lastTouched = 0;
    private static final long SCROLL_TIME = 200L; // 200 Milliseconds, but you can adjust that to your liking
    private UpdateMapAfterUserInterection updateMapAfterUserInterection;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
    case MotionEvent.ACTION_DOWN:
    lastTouched = SystemClock.uptimeMillis();
    break;
    case MotionEvent.ACTION_UP:
    final long now = SystemClock.uptimeMillis();
    if (now – lastTouched > SCROLL_TIME) {
    // Update the map
    if(updateMapAfterUserInterection != null)
    updateMapAfterUserInterection.onUpdateMapAfterUserInterection();
    }
    break;
    }
    return super.dispatchTouchEvent(ev);
    }

    // Map Activity must implement this interface
    public interface UpdateMapAfterUserInterection {
    public void onUpdateMapAfterUserInterection();
    }

    public void setUpdateMapAfterUserInterection(UpdateMapAfterUserInterection mUpdateMapAfterUserInterection){
    this.updateMapAfterUserInterection = mUpdateMapAfterUserInterection;
    }
    }

  • June 25, 2013 at 4:53 pm
    Permalink

    Thanks for sharing Dimitar. You saved me a lot of time.

    Mike.

  • September 13, 2013 at 2:33 pm
    Permalink

    thanks Dimitar.I have implements your code in my practice but when I zoom it by two finger when i finger off.i return same as when begin.can you for me suggest

  • September 18, 2013 at 5:44 am
    Permalink

    This would be better if you used MotionEvent.ACTION_MOVE xD

  • November 19, 2013 at 9:41 pm
    Permalink

    Thank you very much, this is very helpful.

    Casper or Dimitar,

    Would you mind maybe explaining why it’s better, if it is, to use Casper’s solution? I assume you then have to call setUpdateMapAfterUserInteraction from MySupportMapFragment, right?

    Thanks,

    James

  • November 26, 2013 at 1:30 am
    Permalink

    That seems help. When i stop moving the map, it can do the change at the right time.

    Could you tell me, the google has remove the onTouchEvent from the MapView? Because ,first i try to do the thing in onTouchEvent, but the onTouchEvent never run.

    I have search many try to find out the reason why the onTouchEvent can’t run.

  • January 19, 2014 at 11:04 am
    Permalink

    Your solution is perfect, works like a charm ! Thanks.

  • February 4, 2014 at 6:33 am
    Permalink

    Am I the only one who noticed that the interface is called “UpdateMapAfterUserInterEction” instead of “UpdateMapAfterUserInterAction”? XD

    BTW Thanks Dimitar for the code, you saved my day. And I agree with Casper fixes, I was already implementing them by myself.

  • April 21, 2014 at 12:12 am
    Permalink

    Nice job thank you. Now I have a an special case using your solution where my application crashes. I am using the navigation drawer and I only supoort Android 4.x. In navigation drawer all the activities are fragments, so my map is a fragment inside a fragment. I implement your code + Casper Fix and sadly the event is not working. Then, when I change between options at the navigation drawer the app crashes because a inflating error in the xml file (the fragment xml line exactly)

  • April 21, 2014 at 12:16 am
    Permalink

    Oh, I forgot, I don’t use FragmentActivity and SupportMapFragment classes. I work with Fragment and MapFragment

  • May 1, 2014 at 11:38 pm
    Permalink

    Can someone explain to me what to pass in mTouchView.setUpdateMapAfterUserInterection(mUpdateMapAfterUserInterection);

    I have implemented the interface inside my Fragment which extends a normal android.support.v4.app.Fragment

    Thanks

  • June 25, 2014 at 5:14 am
    Permalink

    good stuff. helped me alot.

  • Pingback:Google map with doubleclick event and pan event in android

  • September 2, 2014 at 7:14 am
    Permalink

    Works like a charm! Thanks!

  • October 28, 2014 at 5:33 am
    Permalink

    Thank you for this, it works really well.

  • October 29, 2014 at 4:23 am
    Permalink

    After updating v4
    if you want to findFragmentById
    – inside FragmentActivity : call getFragmentManager().find…
    – inside Fragment : call getChildFragmentManager().find…

  • October 31, 2014 at 4:01 am
    Permalink

    hi,
    I tried exactly like this but got any results..The touch event never detected.I had to solve a problem same as Dave.But not able to find a solution with the code here ..please help.

  • December 11, 2014 at 1:20 pm
    Permalink

    Hi,

    Can someone please confirm that this code is still working?

    I had this code embedded to my app working like a charm but after an update I only get a white screen instead.

  • January 2, 2015 at 8:06 am
    Permalink

    First of all thank you for this useful sharing.I am using your code in my project.But my ActivityMapv2 class extends fragments not fragmentActivity.I face a problem in class TouchableWrapper.

    updateMapAfterUserInterection = (StationsFragment.getActivity()) context;
    How can I handle this problem?

  • February 18, 2016 at 6:12 am
    Permalink

    Nice Work @Dimitar!
    Nice simple solution. I wouldn’t say its hacky if we are given no other choice.
    I am extending your solution a bit.
    To secure the approach when flinging the map etc. I also implement onCameraChange and set flags for them both when camera is updated and user has done interacting. With this I am building a new listener in the fragment where I can signal the activity:
    User started interaction (animate some views),
    User stopped interaction (animate back)
    Map done panning (fetch data and update clusters etc.)

    Thank you!

  • July 28, 2016 at 2:54 pm
    Permalink

    “That finally leads us down the path of implementing this basic map behavioral need ourselves! I would like to think that Google does this to us, because they know we are better programmers than those stinking iOS dudes, who are used to getting anything they need handed on a platter of APIs:-) But may be I am wrong again…”

    In the iOS Google Maps SDK, there are callback methods for “willMoveMap” and “didMoveMap”

    hahahaha

  • February 27, 2018 at 10:42 pm
    Permalink

    Great job! This implementation looks great on my Google Map.

  • May 22, 2018 at 3:20 am
    Permalink

    hi iam following your code its nice but when moving map iam making invisble to layout and after getting different address i make visible the problem is when i simply touch map the layout gets viisble how to avoid

  • May 22, 2018 at 3:22 am
    Permalink

    hi iam following your code its nice but when moving map iam making invisble to layout and after getting different address i make visible the problem is when i simply touch map the layout gets INviisble how to avoid

  • June 21, 2018 at 2:13 am
    Permalink

    Thank you. Working

  • April 29, 2021 at 1:34 pm
    Permalink

    Anybody know how to make this method distinguish between marker touch and non-marker touch on the underlying map? Is it just handled by returning false to the map and letting the listeners there handle it?

Leave a Reply

Your email address will not be published. Required fields are marked *

*