android 地图:如何长按地图?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1678493/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 03:27:12  来源:igfitidea点击:

android maps: How to Long Click a Map?

androidandroid-mapviewlong-click

提问by vamsibm

How do I long click on a mapview so that a place marker appears at that point on the map?

如何长按地图视图以便在地图上的那个点出现地点标记?

I tried a couple ways without success:

我尝试了几种方法但没有成功:

1) Using setOnLongClickListeneron the MapvViewwhich never detected the longclicks.

1)setOnLongClickListenerMapvView从未检测到长按的情况下使用。

2) My other idea was to extend MapViewto override dispatchTouchEvent.. Create a GestureDetector to respond to longpress callback. But I was stuck midway here as I could not get a handle to my subclassed Mapview. i.e.

2)我的另一个想法是扩展MapView覆盖dispatchTouchEvent..创建一个GestureDetector来响应longpress回调。但是我被困在这里,因为我无法处理我的子类 Mapview。IE

MyMapview mymapview; //MyMapView extends MapView

//results in a classcast exception
mymapView = (MyMapView) findViewById(R.id.map);

3) The only other way I know how to try this is: Detect a MotionEvent.ACTION_DOWNand post a delayed runnable to a handler and detect longpress if the two other events: acton_move or an action_up, have not happened.

3)我知道如何尝试的唯一另一种方法是:检测 aMotionEvent.ACTION_DOWN并将延迟的可运行对象发布到处理程序,如果其他两个事件:acton_move 或 action_up 未发生,则检测长按。

Can someone provide thoughts on any of these methods to detect long presses?

有人可以提供有关这些检测长按的方法中的任何一种的想法吗?

回答by David Almilli

I've found an even easier way. Just make an overlay as the first overlay in the list that does not draw anything and use it to recognize gestures using the GestureDetector. It should then return true if it handled the event so it doesn't get propagated.

我找到了一个更简单的方法。只需将叠加作为列表中不绘制任何内容的第一个叠加,并使用它来识别使用 GestureDetector 的手势。如果它处理了事件,那么它应该返回 true,这样它就不会被传播。

    List<Overlay> overlays = mapView.getOverlays();
    overlays.clear();
    overlays.add(new MapGestureDetectorOverlay(new MyOnGestureListener()));

And here's the class:

这是课程:

public class MapGestureDetectorOverlay extends Overlay implements OnGestureListener {
 private GestureDetector gestureDetector;
 private OnGestureListener onGestureListener;

 public MapGestureDetectorOverlay() {
  gestureDetector = new GestureDetector(this);
 }

 public MapGestureDetectorOverlay(OnGestureListener onGestureListener) {
  this();
  setOnGestureListener(onGestureListener);
 }

 @Override
 public boolean onTouchEvent(MotionEvent event, MapView mapView) {
  if (gestureDetector.onTouchEvent(event)) {
   return true;
  }
  return false;
 }

 @Override
 public boolean onDown(MotionEvent e) {
  if (onGestureListener != null) {
   return onGestureListener.onDown(e);
  }
  return false;
 }

 @Override
 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
   float velocityY) {
  if (onGestureListener != null) {
   return onGestureListener.onFling(e1, e2, velocityX, velocityY);
  }
  return false;
 }

 @Override
 public void onLongPress(MotionEvent e) {
  if (onGestureListener != null) {
   onGestureListener.onLongPress(e);
  }
 }

 @Override
 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
   float distanceY) {
  if (onGestureListener != null) {
   onGestureListener.onScroll(e1, e2, distanceX, distanceY);
  }
  return false;
 }

 @Override
 public void onShowPress(MotionEvent e) {
  if (onGestureListener != null) {
   onGestureListener.onShowPress(e);
  }
 }

 @Override
 public boolean onSingleTapUp(MotionEvent e) {
  if (onGestureListener != null) {
   onGestureListener.onSingleTapUp(e);
  }
  return false;
 }

 public boolean isLongpressEnabled() {
  return gestureDetector.isLongpressEnabled();
 }

 public void setIsLongpressEnabled(boolean isLongpressEnabled) {
  gestureDetector.setIsLongpressEnabled(isLongpressEnabled);
 }

 public OnGestureListener getOnGestureListener() {
  return onGestureListener;
 }

 public void setOnGestureListener(OnGestureListener onGestureListener) {
  this.onGestureListener = onGestureListener;
 }
}

回答by I82Much

Best way I know to do this is to use the open source mapview-overlay-managerand use its gesture listener which provides a callback for

我所知道的最好的方法是使用开源的mapview-overlay-manager并使用它的手势监听,它为

public void onLongPress(MotionEvent e, ManagedOverlay overlay)

回答by Faeez Kadiri

回答by CircuitBreaker716

This works with your MapActivity and will allow you to:

这适用于您的 MapActivity 并允许您:

  1. Set your own time threshold for what constitutes a long press on the map (0.8 seconds is good for me).
  2. Not interpret a scroll, multi-touch, or non-multi-touch event as a long press. It also let's you set a tolerance for someone's finger to ever so slightly move on the screen as they are holding down a long press.
  1. 为地图上的长按设置您自己的时间阈值(0.8 秒对我来说很好)。
  2. 不要将滚动、多点触控或非多点触控事件解释为长按。它还可以让您为某人的手指在按住长按时在屏幕上稍微移动设置一个容差。


This solution is based on Roger Kristiansen's solution. I added x and y point detection so that scrolling is not seen as a long press. This solution is not as elegant as Roger's because I use class variables and put the code in my existing MapActivity instead of extending MapView and creating a listener like he did. But if you want to stick with his code but have better non-multi-touch support, just take the x and y point stuff out of mine and add it to his.

此解决方案基于Roger Kristiansen 的解决方案。我添加了 x 和 y 点检测,这样滚动就不会被视为长按。这个解决方案不像 Roger 的那样优雅,因为我使用类变量并将代码放在我现有的 MapActivity 中,而不是像他那样扩展 MapView 并创建一个侦听器。但是如果你想坚持他的代码但有更好的非多点触控支持,只需从我的 x 和 y 点的东西中取出并添加到他的。



Class variables set at the top of my MapActivity:

在我的 MapActivity 顶部设置的类变量:

//variable for determining long press and then automatically adding a pin to the map
private int minMillisecondThresholdForLongClick = 800;
private long startTimeForLongClick = 0;
private float xScreenCoordinateForLongClick;
private float yScreenCoordinateForLongClick;
private float xtolerance=10;//x pixels that your finger can be off but still constitute a long press
private float ytolerance=10;//y pixels that your finger can be off but still constitute a long press
private float xlow; //actual screen coordinate when you subtract the tolerance
private float xhigh; //actual screen coordinate when you add the tolerance
private float ylow; //actual screen coordinate when you subtract the tolerance
private float yhigh; //actual screen coordinate when you add the tolerance

Add this function in your MapActivity:

在您的 MapActivity 中添加此功能:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        /* We want to capture the place the user long pressed on the map and add a marker (pin) on the map at that lat/long.
         * This solution:
         *  1. Allows you to set the time threshold for what constitutes a long press
         *  2. Doesn't get fooled by scrolling, multi-touch, or non-multi-touch events

         * Thank you Roger Kind Kristiansen for the main idea
         */     

        //get the action from the MotionEvent: down, move, or up
        int actionType = ev.getAction();

        if (actionType == MotionEvent.ACTION_DOWN) {
            //user pressed the button down so let's initialize the main variables that we care about:
            // later on when the "Action Up" event fires, the "DownTime" should match the "startTimeForLongClick" that we set here  
            // the coordinate on the screen should not change much during the long press
            startTimeForLongClick=ev.getEventTime();
            xScreenCoordinateForLongClick=ev.getX();
            yScreenCoordinateForLongClick=ev.getY();

        } else if (actionType == MotionEvent.ACTION_MOVE) {
            //For non-long press actions, the move action can happen a lot between ACTION_DOWN and ACTION_UP                    
            if (ev.getPointerCount()>1) {
                //easiest way to detect a multi-touch even is if the pointer count is greater than 1
                //next thing to look at is if the x and y coordinates of the person's finger change.
                startTimeForLongClick=0; //instead of a timer, just reset this class variable and in our ACTION_UP event, the DownTime value will not match and so we can reset.                        
            } else {
                //I know that I am getting to the same action as above, startTimeForLongClick=0, but I want the processor
                //to quickly skip over this step if it detects the pointer count > 1 above
                float xmove = ev.getX(); //where is their finger now?                   
                float ymove = ev.getY();
                //these next four values allow you set a tiny box around their finger in case
                //they don't perfectly keep their finger still on a long click.
                xlow = xScreenCoordinateForLongClick - xtolerance;
                xhigh= xScreenCoordinateForLongClick + xtolerance;
                ylow = yScreenCoordinateForLongClick - ytolerance;
                yhigh= yScreenCoordinateForLongClick + ytolerance;
                if ((xmove<xlow || xmove> xhigh) || (ymove<ylow || ymove> yhigh)){
                    //out of the range of an acceptable long press, reset the whole process
                    startTimeForLongClick=0;
                }
            }

        } else if (actionType == MotionEvent.ACTION_UP) {
            //determine if this was a long click:
            long eventTime = ev.getEventTime();
            long downTime = ev.getDownTime(); //this value will match the startTimeForLongClick variable as long as we didn't reset the startTimeForLongClick variable because we detected nonsense that invalidated a long press in the ACTION_MOVE block

            //make sure the start time for the original "down event" is the same as this event's "downTime"
            if (startTimeForLongClick==downTime){ 
                //see if the event time minus the start time is within the threshold
                if ((eventTime-startTimeForLongClick)>minMillisecondThresholdForLongClick){ 
                    //make sure we are at the same spot where we started the long click
                    float xup = ev.getX();                  
                    float yup = ev.getY();
                    //I don't want the overhead of a function call:
                    xlow = xScreenCoordinateForLongClick - xtolerance;
                    xhigh= xScreenCoordinateForLongClick + xtolerance;
                    ylow = yScreenCoordinateForLongClick - ytolerance;
                    yhigh= yScreenCoordinateForLongClick + ytolerance;
                    if ((xup>xlow && xup<xhigh) && (yup>ylow && yup<yhigh)){ 

                        //**** safe to process your code for an actual long press **** 
                        //comment out these next rows after you confirm in logcat that the long press works
                        long totaltime=eventTime-startTimeForLongClick;
                        String strtotaltime=Long.toString(totaltime);                               
                        Log.d("long press detected: ", strtotaltime);


                        /* 
                        //Now get the latitude/longitude of where you clicked.  Replace all the code below if you already know how to translate a screen coordinate to lat/long.  I know it works though.

                        //*****************
                        //I have my map under a tab so I have to account for the tab height and the notification bar at the top of the phone.  
                        // Maybe there are other ways so just ignore this if you already know how to get the lat/long of the pixels that were pressed.
                        int TabHeightAdjustmentPixels=tabHost.getTabWidget().getChildAt(0).getLayoutParams().height;
                        int EntireTabViewHeight = tabHost.getHeight();
                        Display display = getWindowManager().getDefaultDisplay(); 
                        int EntireScreenHeight = display.getHeight();
                        int NotificationBarHeight=EntireScreenHeight-EntireTabViewHeight;
                        //the projection is mapping pixels to where you touch on the screen.
                        Projection proj = mapView.getProjection();
                        GeoPoint loc = proj.fromPixels((int)(ev.getX(ev.getPointerCount()-1)), (int)(ev.getY(ev.getPointerCount()-1)-TabHeightAdjustmentPixels-NotificationBarHeight)); 
                        int longitude=loc.getLongitudeE6();             
                        int latitude=loc.getLatitudeE6();
                        //*****************

                        //**** here's where you add code to: 
                        // put a marker on the map, save the point to your SQLite database, etc
                        */

                    }
                }
            }

        }


        return super.dispatchTouchEvent(ev);
    }

回答by Jason Van Anden

This mapview-overlay-manager library is super. The reason onLongPress gets triggered when 'scrolling' is because the library does not take into account multitouch. You can work around this by rejecting onLongPress if there is more than one pointer involved as so:

这个 mapview-overlay-manager 库是超级的。在“滚动”时触发 onLongPress 的原因是该库没有考虑多点触控。如果涉及多个指针,您可以通过拒绝 onLongPress 来解决此问题:

public void onLongPress(MotionEvent motionEvent, ManagedOverlay arg1) {

    if (motionEvent.getPointerCount() > 1) return;

    ... your logic here ...
}

回答by Enrique Diaz

ambrose,

安布罗斯,

I modified the demo of the library mapview-overlay-manager. to get this code running with a double tap gesture:

我修改了库mapview-overlay-manager的演示。使用双击手势运行此代码:

package de.android1.overlaymanager.demo;

import android.os.Bundle;
import android.widget.Toast;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import com.google.android.maps.MapActivity;

import com.google.android.maps.MapView;
import com.google.android.maps.MapController;
import com.google.android.maps.GeoPoint;

import de.android1.overlaymanager.*;


public class DemoView extends MapActivity {

    MapView mapView;
    MapController mapController;

    OverlayManager overlayManager;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mapView = (MapView) findViewById(R.id.mapview);
        mapView.setBuiltInZoomControls(true);
        mapController = mapView.getController();

        overlayManager = new OverlayManager(getApplication(), mapView);
    }

    @Override
    public void onWindowFocusChanged(boolean b) {

         createOverlayWithListener();

    }


    public void createOverlayWithListener() {
        //This time we use our own marker
        final ManagedOverlay managedOverlay = overlayManager.createOverlay("listenerOverlay", getResources().getDrawable(R.drawable.marker));
        for (int i = 0; i < 40; i = i + 3) {
            managedOverlay.createItem(GeoHelper.geopoint[i], "Item" + i);
        }
        managedOverlay.setOnOverlayGestureListener(new ManagedOverlayGestureDetector.OnOverlayGestureListener() {


            public boolean onZoom(ZoomEvent zoom, ManagedOverlay overlay) {
                return false;
            }


            public boolean onDoubleTap(MotionEvent e, ManagedOverlay overlay, GeoPoint point, ManagedOverlayItem item) {
                Drawable defaultmarker = getResources().getDrawable(R.drawable.marker);     

                ManagedOverlay managedOverlay = overlayManager.createOverlay(defaultmarker);

                //creating some marker:
                managedOverlay.createItem(point);

                //registers the ManagedOverlayer to the MapView
                overlayManager.populate();
                Toast.makeText(getApplicationContext(), "You created a Marker!", Toast.LENGTH_LONG).show();

                return true;
            }


            public void onLongPress(MotionEvent arg0, ManagedOverlay arg1) {
                // TODO Auto-generated method stub

            }


            public void onLongPressFinished(MotionEvent arg0,
                    ManagedOverlay arg1, GeoPoint arg2, ManagedOverlayItem arg3) {
                // TODO Auto-generated method stub

            }


            public boolean onScrolled(MotionEvent arg0, MotionEvent arg1,
                    float arg2, float arg3, ManagedOverlay arg4) {
                // TODO Auto-generated method stub
                return false;
            }


            public boolean onSingleTap(MotionEvent arg0, ManagedOverlay arg1,
                    GeoPoint arg2, ManagedOverlayItem arg3) {
                // TODO Auto-generated method stub
                return false;
            }           
        });
        overlayManager.populate();
    }


    @Override
    protected boolean isRouteDisplayed() {
        return false;
    }
}

Hope it helps.

希望能帮助到你。

回答by MeplatMasher

I also got a ClassCastException when I tried using MyMapView. As a workaround, I created a MyMapView object instead of a MapView object and programmatically added it to the layout, and that worked great.

当我尝试使用 MyMapView 时,我也遇到了 ClassCastException。作为一种解决方法,我创建了一个 MyMapView 对象而不是 MapView 对象,并以编程方式将其添加到布局中,效果很好。

    MyMapView mapView = new MyMapView(this, this.getString(R.string.APIMapKey));
    mapView.displayZoomControls(false);
    mapView.setClickable(true);
    FrameLayout item = (FrameLayout) findViewById(R.id.getlocationmap);
    item.addView((MapView)mapView);

回答by stemadsen

If you're going with the delayed post/message to a handler (a solution I use myself), it can be useful to test if the map has moved, i.e. if mapView.getMapCenter(), mapView.getLatitudeSpan()and mapView.getLongitudeSpan()return the same as when the pointer went down.

如果您将延迟的帖子/消息发送到处理程序(我自己使用的解决方案),则测试地图是否已移动(即 if mapView.getMapCenter()mapView.getLatitudeSpan()mapView.getLongitudeSpan()返回与指针下降时相同的信息会很有用。