谷歌地图Android API v2 - 检测地图上的触摸

75

我找不到有关如何拦截新版Google Maps API v2地图触摸的示例。

我需要知道用户何时触摸地图,以便停止一个线程(将地图居中显示在我的当前位置周围)。


3
如果有人回答了你的问题,请将你的问题标记为已回答。此外,你明确地说“点击地图”,所以不需要因为猿或CommonsWare无法读懂你的想法而大发雷霆。 - Maarten
1
我甚至可以将其标记为已回答,但我写的是“map touch”,而不是地图“click”。@ape在评论中建议了另一个解决我的问题的线程(https://dev59.com/nmYr5IYBdhLWcg3wYZOD),但我不能使用它,正如我在评论中所写的那样。我既不能在这个线程上得到解决方案,也不能在链接的线程上得到解决方案。我应该开一个新问题吗? - Gaucho
你讲的非常难懂,我很难跟上。如果你自己的回答是最有帮助的,甚至可以接受它以展示给其他人。 - Kate Gregory
我是StackOverflow的新手。我能做到! - Gaucho
为什么不使用CameraChangeListener? - Melbourne Lopes
显示剩余2条评论
11个回答

95

@ape在这里写了如何拦截地图点击的答案,但我需要拦截触摸事件,他建议查看其回答评论中的以下链接:如何处理Google Map API v2中地图的onTouch事件?。那个解决方案似乎是一个可行的解决方法,但建议的代码不完整。因此,我重新编写并测试了它,并且现在它可以工作了。这就是工作代码:

我创建了MySupportMapFragment.java类。

import com.google.android.gms.maps.SupportMapFragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

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;
    }
}

我甚至创建了TouchableWrapper.java类:

import android.content.Context;
import android.view.MotionEvent;
import android.widget.FrameLayout;

public class TouchableWrapper extends FrameLayout {

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                  MainActivity.mMapIsTouched = true;
                  break;

            case MotionEvent.ACTION_UP:
                  MainActivity.mMapIsTouched = false;
                  break;
        }
        return super.dispatchTouchEvent(event);
    }
}

在布局中,我是这样声明的:

<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"
/>

只是为了在主活动中进行测试,我仅写了以下内容:

public class MainActivity extends FragmentActivity {
    public static boolean mMapIsTouched = false;

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

3
顶起,因为那不是用户所需的,但那就是他所要求的。而那正是我所需要的 :) - Thibault D.
@Gaucho,我必须对那个解决方案进行一些改进,主要是使用自定义监听器而不是使用公共静态变量。请参见下面的解决方案。 - Dimitar Darazhanski
@Dimitar:现在我没有时间测试它。我会告诉你的。谢谢。 - Gaucho
非常棒的解决方案.. :) - Rajshree Tiwari
@DimitarDarazhanski - 你的实现非常好。谢谢!http://dimitar.me/how-to-detect-a-user-pantouchdrag-on-android-map-v2/comment-page-1/ - AdamHurwitz

49

这里有一个简单的解决方案,可以根据用户选择(在地图上点击选项)来获取位置:

googleMap.setOnMapClickListener(new OnMapClickListener() {
  @Override
  public void onMapClick(LatLng arg0) {
    // TODO Auto-generated method stub
    Log.d("arg0", arg0.latitude + "-" + arg0.longitude);
  }
});

3
只有在您轻触地图时,此过程才会起作用;但是如果您用更大的力量触摸地图并开始缩放,则不会调用onMapClick方法。为此,需要进行改进。 - Md. Sajedul Karim
1
@Md.SajedulKarim,您可以通过googleMap.getUiSettings().setAllGesturesEnabled(false)禁用所有手势,然后监听该敲击事件,在重新启用手势后。 - Oleg Novosad
2
setOnMapClickListener无法识别。我应该导入什么? - ONE_FE

35

现在支持许多新功能 :)

这是开发者笔记(Issue 4636):

2016年8月发布引入了一组新的相机变化监听器,用于相机运动开始、进行中和结束事件。您还可以看到相机移动的原因,无论是由用户手势、内置API动画还是开发人员控制的移动引起的。有关详细信息,请参阅相机更改事件指南: https://developers.google.com/maps/documentation/android-api/events#camera-change-events

此外,请查看发行说明: https://developers.google.com/maps/documentation/android-api/releases#august_1_2016

以下是文档页面中的代码片段

public class MyCameraActivity extends FragmentActivity implements
        OnCameraMoveStartedListener,
        OnCameraMoveListener,
        OnCameraMoveCanceledListener,
        OnCameraIdleListener,
        OnMapReadyCallback {

    private GoogleMap mMap;

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

        SupportMapFragment mapFragment =
            (SupportMapFragment) getSupportFragmentManager()
                    .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);
    }

    @Override
    public void onMapReady(GoogleMap map) {
        mMap = map;

        mMap.setOnCameraIdleListener(this);
        mMap.setOnCameraMoveStartedListener(this);
        mMap.setOnCameraMoveListener(this);
        mMap.setOnCameraMoveCanceledListener(this);

        // Show Sydney on the map.
        mMap.moveCamera(CameraUpdateFactory
                .newLatLngZoom(new LatLng(-33.87365, 151.20689), 10));
    }

    @Override
    public void onCameraMoveStarted(int reason) {

        if (reason == OnCameraMoveStartedListener.REASON_GESTURE) {
            Toast.makeText(this, "The user gestured on the map.",
                           Toast.LENGTH_SHORT).show();
        } else if (reason == OnCameraMoveStartedListener
                                .REASON_API_ANIMATION) {
            Toast.makeText(this, "The user tapped something on the map.",
                           Toast.LENGTH_SHORT).show();
        } else if (reason == OnCameraMoveStartedListener
                                .REASON_DEVELOPER_ANIMATION) {
            Toast.makeText(this, "The app moved the camera.",
                           Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void onCameraMove() {
        Toast.makeText(this, "The camera is moving.",
                       Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onCameraMoveCanceled() {
        Toast.makeText(this, "Camera movement canceled.",
                       Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onCameraIdle() {
        Toast.makeText(this, "The camera has stopped moving.",
                       Toast.LENGTH_SHORT).show();
    }
}

10

我在布局中创建了一个空的FrameLayout,覆盖在MapFragment之上。然后在这个视图上设置了一个onTouchListener,以便知道何时触摸地图,但返回false,以便将触摸传递给地图。

<FrameLayout
    android:id="@+id/map_touch_layer"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

mapTouchLayer.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            Utils.logDebug(TAG, "Map touched!");
            timeLastTouched = System.currentTimeMillis();
            return false; // Pass on the touch to the map or shadow layer.
        }
    });

1
这根本行不通。在ACTION_DOWN上返回false将导致ACTION_UP被中断,并且onTouch不会被调用。 - Hamzeh Soboh
短小简洁,具有良好的线索。 - Nirmal

8

Gaucho提供了很好的答案,看到许多人点赞,我想可能需要另一个实现:

我需要使用一个监听器,以便在触摸时做出反应,而不必不断地检查它。

我把所有东西都放在一个类中,可以像这样使用:

mapFragment.setNonConsumingTouchListener(new TouchSupportMapFragment.NonConsumingTouchListener() {
    @Override
    public void onTouch(MotionEvent motionEvent) {
        switch (motionEvent.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                // map is touched
                break;
            case MotionEvent.ACTION_UP:
                // map touch ended
                break;
            default:
                break;
            // use more cases if needed, for example MotionEvent.ACTION_MOVE
        }
    }
});

地图片段需要是TouchSupportMapFragment类型,并且在布局xml中需要添加此行:

<fragment class="de.bjornson.maps.TouchSupportMapFragment"
...

以下是类的示例:

package de.bjornson.maps;

import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import com.google.android.gms.maps.SupportMapFragment;

public class TouchSupportMapFragment extends SupportMapFragment {
    public View mOriginalContentView;
    public TouchableWrapper mTouchView;
    private NonConsumingTouchListener mListener;

    @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;
    }

    public void setNonConsumingTouchListener(NonConsumingTouchListener listener) {
        mListener = listener;
    }

    public interface NonConsumingTouchListener {
        boolean onTouch(MotionEvent motionEvent);
    }

    public class TouchableWrapper extends FrameLayout {

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

        @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
            if (mListener != null) {
                mListener.onTouch(event);
            }
            return super.dispatchTouchEvent(event);
        }
    }
}

7

https://developers.google.com/maps/documentation/android/reference/com/google/android/gms/maps/GoogleMap.OnMapClickListener

请查看此链接。实现该接口并填写onMapClick() 方法或您所需的任何方法,并将onMapClickListener 设置为正确的实现。

public class YourActivity extends Activity implements OnMapClickListener {
    @Override
    protected void onCreate(Bundle icicle) { 
        super.onCreate(icicle);
        ...
        my_map.setOnMapClickListener(this)        
        ...
    }

    public void onMapClick (LatLng point) {
        // Do Something
    }
}

1
非常感谢ndsmyter的回答。onMapClick会在您点击地图时拦截,但当您在地图上移动手指时它不起作用。我需要拦截不仅是地图点击,还有地图平移。您知道如何做吗? - Gaucho
2
Map Touch 不是 "地图单击",所以问题还没有得到解答。我需要截取用户在地图上触摸而导致的地图移动,并且我找不到一种有效的方法来拦截这个操作。我认为不能使用 setOnCameraChangeListener,因为我仍然在我的代码中使用 animateCamera 方法来更新相机位置,所以我只需要一个监听器来在地图平移期间拦截地图上的触摸。 - Gaucho
亲爱的 @ape,onMarkerDragListener拦截标记的拖动,而不是没有标记的地图的平移。我需要在用户触摸地图以进行平移时获得一个中断信号。 - Gaucho
2
好的,我猜这会有所帮助吧?https://dev59.com/nmYr5IYBdhLWcg3wYZOD - adarsh
完美!像魔法一样好用!至少对于我的使用情况来说是这样。 - Taslim Oseni
显示剩余6条评论

3
我从已接受的答案中获取灵感,并改进了它,将其转换为 Kotlin,并添加了构造函数,允许在标记中声明可触摸包装器,并使用可设置的回调属性进行触摸检测,以消除直接耦合到活动的关系,从而使其更容易地被重用。
class TouchableWrapper : FrameLayout {

    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    var onTouch: ((event :MotionEvent) -> Unit)? = null

    override fun dispatchTouchEvent(event: MotionEvent): Boolean {
        onTouch?.invoke(event)
        return super.dispatchTouchEvent(event)
    }
}

然后在你的布局文件中:

    <com.yourpackage.views.TouchableWrapper
        android:id="@+id/viewMapWrapper"
        android:layout_height="match_parent"
        android:layout_width="match_parent">
        <fragment
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:id="@+id/map"
                  tools:context=".MapsActivity"
                  android:name="com.google.android.gms.maps.SupportMapFragment"/>
    </com.yourpackage.views.TouchableWrapper>

然后像这样设置你的回调函数:

        findViewById<TouchableWrapper>(R.id.viewMapWrapper)
            .onTouch = {
            if (MotionEvent.ACTION_DOWN == it.action) {
                  //Handle touch down on the map
            }
        }

这是更简单且更好的解决方案。谢谢! - Parth Patel

2
  // Initializing
    markerPoints = new ArrayList<LatLng>();

    // Getting reference to SupportMapFragment of the activity_main
    SupportMapFragment sfm = (SupportMapFragment)getSupportFragmentManager().findFragmentById(R.id.map);

    // Getting Map for the SupportMapFragment
    map = sfm.getMap();

    // Enable MyLocation Button in the Map
    map.setMyLocationEnabled(true);

    // Setting onclick event listener for the map
    map.setOnMapClickListener(new OnMapClickListener() {

        @Override
        public void onMapClick(LatLng point) {

            // Already two locations
            if(markerPoints.size()>1){
                markerPoints.clear();
                map.clear();
            }

            // Adding new item to the ArrayList
            markerPoints.add(point);

            // Creating MarkerOptions
            MarkerOptions options = new MarkerOptions();

            // Setting the position of the marker
            options.position(point);


            if(markerPoints.size()==1){
                options.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN));
            }else if(markerPoints.size()==2){
                options.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED));
            }

            // Add new marker to the Google Map Android API V2
            map.addMarker(options);

            // Checks, whether start and end locations are captured
            if(markerPoints.size() >= 2){
                LatLng origin = markerPoints.get(0);
                LatLng dest = markerPoints.get(1);

            //Do what ever you want with origin and dest
            }
        }
    });

1
对于Mono爱好者:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Widget;
using Android.Gms.Maps;

namespace apcurium.MK.Booking.Mobile.Client.Controls
{
    public class TouchableMap : SupportMapFragment
    {
        public View mOriginalContentView;

        public TouchableWrapper Surface;

        public override View OnCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)
        {
            mOriginalContentView = base.OnCreateView(inflater, parent, savedInstanceState);
            Surface = new TouchableWrapper(Activity);
            Surface.AddView(mOriginalContentView);
            return Surface;
        }

        public override View View
        {
            get
            {
                return mOriginalContentView;
            }
        }
    }

    public class TouchableWrapper: FrameLayout {

        public event EventHandler<MotionEvent> Touched;

        public TouchableWrapper(Context context) :
        base(context)
        {
        }

        public TouchableWrapper(Context context, IAttributeSet attrs) :
        base(context, attrs)
        {
        }

        public TouchableWrapper(Context context, IAttributeSet attrs, int defStyle) :
        base(context, attrs, defStyle)
        {
        }

        public override bool DispatchTouchEvent(MotionEvent e)
        {
            if (this.Touched != null)
            {
                this.Touched(this, e);
            }

            return base.DispatchTouchEvent(e);
        }
    }
}

1
我有一个更简单的解决方案,与 TouchableWrapper 不同,并且适用于最新版本的 play-services-maps:10.0.1 。此解决方案仅使用地图事件,而不使用自定义视图。不使用已弃用的函数,可能支持多个版本。
首先,您需要一个标志变量来存储地图是通过动画还是用户输入移动的(此代码假定每个非由动画触发的相机移动都是由用户触发的)。
GoogleMap googleMap;
boolean movedByApi = false;

您的片段或活动必须实现GoogleMap.OnMapReadyCallbackGoogleMap.CancelableCallback

public class ActivityMap extends Activity implements OnMapReadyCallback, GoogleMap.CancelableCallback{
    ...
}

这将迫使您实现方法 onMapReady onFinish onCancel 。在 onMapReady 中,googleMap对象必须为相机移动设置事件监听器。
@Override
public void onMapReady(GoogleMap mMap) {
    //instantiate the map
    googleMap = mMap;

    [...]  // <- set up your map

    googleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() {
        @Override
        public void onCameraMove() {
            if (movedByApi) {
                Toast.makeText(ActivityMap.this, "Moved by animation", Toast.LENGTH_SHORT).show();

                [...] // <-- do something whe you want to handle api camera movement
            } else {
                Toast.makeText(ActivityMap.this, "Moved by user", Toast.LENGTH_SHORT).show();

                [...] // <-- do something whe you want to handle user camera movement
            }
        }
    });
}
@Override
public void onFinish() {
    //is called when the animation is finished
    movedByApi = false;
}
@Override
public void onCancel() {
    //is called when the animation is canceled (the user drags the map or the api changes to a ne position)
    movedByApi = false;
}

最后,最好创建一个通用函数来移动地图。

public void moveMapPosition(CameraUpdate cu, boolean animated){
    //activate the flag notifying that the map is being moved by the api
    movedByApi = true;
    //if its not animated, just do instant move
    if (!animated) {
        googleMap.moveCamera(cu);
        //after the instant move, clear the flag
        movedByApi = false;
    }
    else
        //if its animated, animate the camera
        googleMap.animateCamera(cu, this);
}

或者说,每次移动地图时,在动画之前激活标志

movedByApi = true;
googleMap.animateCamera(cu, this);

我希望这可以帮到你!


如果用户在动画期间触摸地图,则此操作无效。 - Tom Bevelander

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接