Google Maps Android API v2 - 检测地图上的触摸

Posted

技术标签:

【中文标题】Google Maps Android API v2 - 检测地图上的触摸【英文标题】:Google Maps Android API v2 - detect touch on map 【发布时间】:2012-12-10 09:11:58 【问题描述】:

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

我需要知道用户何时触摸地图以停止线程(地图以我当前位置为中心)。

【问题讨论】:

如果有人回答了您的问题,请将您的问题标记为已回答。此外,您明确地说“点击地图”,因此无需因为无法读懂您的想法而对猿或 CommonsWare 进行抨击。 我什至可以将其标记为已回答,但我写的是“地图触摸”,而不是地图“点击”。 @ape 在评论中提出了另一个可以解决我的问题的线程(***.com/questions/13722869/…),但我不能使用它,就像我在 cmets 上写的那样。我无法在此线程上获得解决方案,也无法在链接的线程上获得解决方案。我应该打开另一个问题吗? 您的答案应该是一个答案,而不是编辑到问题中。你真的很难跟上。如果您自己的答案是对您最有帮助的答案,您甚至可以接受它以向其他人展示这一点。 我是 *** 的新手。我能做到! Why not implement onCameraChange(CameraPosition position)? 【参考方案1】:

@ape 在这里写了一个关于如何拦截地图点击的答案,但我需要拦截触摸,然后他在其答案的评论中建议了以下链接,How to handle onTouch event for map in Google Map API v2?。

该解决方案似乎是一种可能的解决方法,但建议的代码不完整。出于这个原因,我重写并测试了它,现在它可以工作了。

这是工作代码:

我创建了 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_
          android:layout_
          android:layout_alignParentBottom="true"
          android:layout_below="@+id/buttonBar"
          class="com.myFactory.myApp.MySupportMapFragment"
/>

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

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

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

【讨论】:

投了赞成票,因为这不是用户需要的,而是他要求的。这就是我需要的:) @Gaucho,我不得不对该解决方案进行一些改进,主要是通过使用自定义侦听器而不是使用公共静态变量。请参阅下面的解决方案。 @Dimitar:现在我没有时间测试它。我会告诉你。谢谢。 很好的解决方案.. :) @DimitarDarazhanski - 你的实现效果很好。谢谢! dimitar.me/how-to-detect-a-user-pantouchdrag-on-android-map-v2/…【参考方案2】:

这是一个根据用户选择获取位置的简单解决方案(点击地图上的选项):

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

【讨论】:

这个过程在你平滑地触摸地图时有效,但是当你用力触摸地图并开始缩放时,因此不会调用onMapClick方法。 @Md.SajedulKarim 您可以使用 googleMap.getUiSettings().setAllGesturesEnabled(false) 禁用所有手势;然后在重新启用手势后听那个点击。 setOnMapClickListener 无法识别。我应该导入什么?【参考方案3】:

现在支持此功能以及更多功能:)

这是开发者说明(问题 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

这是来自文档页面的代码 sn-p

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();
    

【讨论】:

【参考方案4】:

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

<FrameLayout
    android:id="@+id/map_touch_layer"
    android:layout_
    android:layout_ />

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.
        
    );

【讨论】:

这根本行不通。在 ACTION_DOWN 上返回 false 将导致 ACTION_UP 被中断,并且不会为它调用 onTouch 短小精悍,线索丰富【参考方案5】:

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

mapfragment 需要是 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);
        
    

【讨论】:

【参考方案6】:

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
    

【讨论】:

非常感谢 ndsmyter 的回答。 onMapClick 在您点击地图时会拦截,但在您在地图上移动手指时它不起作用。我不仅需要拦截地图点击,还需要拦截地图平移。你知道怎么做吗? Map Touch 不是“地图点击”,所以这个问题没有回答。由于用户在地图上触摸,我需要拦截地图移动,但我找不到拦截此操作的有效方法。我认为我不能使用 setOnCameraChangeListener 因为我仍然使用 animateCamera 方法来更新我的代码中的相机位置,然后我只需要一个侦听器来在地图平移期间拦截地图上的触摸。 我认为您需要onMarkerDragListener? developers.google.com/maps/documentation/android/reference/com/… 亲爱的@ape , onMarkerDragListener 拦截标记的拖动,而不是没有标记的地图的平移。当用户触摸地图进行平移时,我需要中断。 好的,我想这有帮助吗? ***.com/questions/13722869/…【参考方案7】:
  // 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
            
        
    );

【讨论】:

【参考方案8】:

我从接受的答案中获取了这个想法,并通过转换为 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_
        android:layout_>
        <fragment
                  android:layout_
                  android:layout_
                  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
            
        

【讨论】:

这是更简单、更好的解决方案。谢谢!【参考方案9】:

致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);
        
    

【讨论】:

【参考方案10】:

我有一个与TouchableWrapper 不同的更简单的解决方案,它适用于play-services-maps:10.0.1 的最新版本。此解决方案仅使用地图事件,不使用自定义视图。不使用已弃用的函数,并且可能支持多个版本。

首先,您需要一个标志变量来存储地图是由动画还是由用户输入移动(此代码假定每个不是由动画触发的相机移动都是由用户触发的)

GoogleMap googleMap;
boolean movedByApi = false;

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

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

这会迫使您实现onMapReadyonFinishonCancel 方法。并且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);

我希望这会有所帮助!

【讨论】:

如果用户在动画过程中触摸地图,这不起作用。【参考方案11】:

@Gaucho MySupportMapFragment 显然会被其他一些片段或活动使用(其中可能有比地图片段更多的视图元素)。那么如何将这个事件分派到下一个要使用它的片段。我们是否需要重新编写一个接口来做到这一点?

【讨论】:

以上是关于Google Maps Android API v2 - 检测地图上的触摸的主要内容,如果未能解决你的问题,请参考以下文章