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

我找不到一个关于如何在新的 Google Maps API v2中拦截地图触摸的例子。

我需要知道当用户触摸地图,以停止一个线程(地图的中心周围我当前的位置)。

81540 次浏览

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

@ ape 在这里写了一个关于如何拦截地图点击的答案,但我需要拦截点击,然后他在答案的评论中建议了以下链接,如何在 GoogleMapAPI 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);
}
}

对于 单核细胞增多症爱好者:

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

下面是一个基于用户选择(点击地图上的选项)获取位置的简单解决方案:

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

我创建了一个空的 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.
}
});
  // 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
}
}
});

@ Gaucho MySupportMapFragment 显然将被其他一些区段或活动使用(其中可能有比映射片段更多的视图元素)。因此,如何将这个事件分派到下一个要使用它的片段。我们是否需要再次编写一个接口来完成这项工作?

高乔给出了一个很好的答案,看到这么多的赞成票,我觉得可能需要另一个实现:

我需要它的 找个倾听者,所以我可以反应的触摸,而不是检查它不断。

我把所有这些放在一个类中,可以这样使用:

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

现在支持这个特性以及更多特性:)

以下为发展商须知(第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();
}
}

我有一个不同于 TouchableWrapper的更简单的解决方案,这与 play-services-maps:10.0.1的最新版本一起工作。此解决方案只使用映射事件,不使用自定义视图。不使用不推荐的函数,并且可能支持多个版本。

首先,您需要一个标志变量来存储动画或用户输入正在移动的地图(这个代码假设每一个没有被动画触发的相机移动都是由用户触发的)

GoogleMap googleMap;
boolean movedByApi = false;

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

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

这迫使您实现方法 onMapReadyonFinishonCancelonMapReady中的 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);

希望这个能帮上忙!

我从已接受的答案中吸取了这个想法,并通过转换到 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
}
}