如何使用适用于 Android V2 的 Google Maps 处理地图移动端?

Posted

技术标签:

【中文标题】如何使用适用于 Android V2 的 Google Maps 处理地图移动端?【英文标题】:How can I handle map move end using Google Maps for Android V2? 【发布时间】:2012-11-22 00:46:45 【问题描述】:

我想在地图中心更改后立即对地址进行地理编码。

如何使用新的 android 版 Google 地图 V2 处理地图移动端? (我说的是用户用手指拖动地图的情况)

【问题讨论】:

onTouchEvent 方法对你没有帮助? 谷歌还没有这个内置的!疯了.. 【参考方案1】:

查看新的地图 API。

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

developers.google.com sample

【讨论】:

完美解决方案!【参考方案2】:

这是确定拖动开始和拖动结束事件的可能解决方法:

您必须扩展 SupportMapFragment 或 MapFragment。在 onCreateView 中,您必须将 MapView 包装在自定义的 FrameLayout 中(在下面的示例中,它是“TouchableWrapper”类),您可以在其中拦截触摸事件并识别地图是否被点击。如果您的“onCameraChange”被调用,只需检查地图视图是否被按下(在下面的示例中,这是变量“mMapIsTouched”)。

示例代码:

更新1:

在 getView() 中返回原始创建的视图 使用 dispatchTouchEvent() 代替 onInterceptTouchEvent()

自定义框架布局:

private class TouchableWrapper extends FrameLayout 

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) 

        switch (ev.getAction()) 
            case MotionEvent.ACTION_DOWN:
                mMapIsTouched = true;
                break;
            case MotionEvent.ACTION_UP:
                mMapIsTouched = false;
                break;
        

        return super.dispatchTouchEvent(ev);

    


在您自定义的 MapFragment 中:

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

在您的相机更改回调方法中:

private final OnCameraChangeListener mOnCameraChangeListener = 
        new OnCameraChangeListener() 

    @Override
    public void onCameraChange(CameraPosition cameraPosition) 
        if (!mMapIsTouched) 
            refreshClustering(false);
        
    
;

【讨论】:

嘿@Alexey Zakharov,请查看 Google 地图问题跟踪器中的主题:code.google.com/p/gmaps-api-issues/issues/… 确实如此。在您确定 ACTION_UP 事件后,不保证会调用 onCameraChanged()。无论如何,我也希望谷歌能尽快解决这个问题! @AZ13 我收到错误“MyMapFragment 类型的方法 getActivity() 未定义”。如何声明 MyMapFragment? @AZ13 我检查了您的实现,并且必须承认该事件仍未在每次地图移动时触发。查看demo project I prepared using your code。 很好,但你实际上不需要实现自定义的框架布局,只需在框架布局上做setOnTouchListener(new View.OnTouchListener() ... 并在其中添加事件处理代码。【参考方案3】:

过时请改用新的地图 API。查看 punksta 的回答。

在使用了上面AZ13的解决方案后,遇到了cmets中提到的问题,我创建了以下解决方案,可以更可靠地解决问题。但是,由于我在 onRelease 事件之后使用计时器来确定地图是否仍在动画中,因此此解决方案存在轻微延迟。

代码可以通过这个链接在 Github 上找到:https://github.com/MadsFrandsen/MapStateListener

我的解决方案可以通过以下方式从活动中使用:

new MapStateListener(mMap, mMapFragment, this) 
  @Override
  public void onMapTouched() 
    // Map touched
  

  @Override
  public void onMapReleased() 
    // Map released
  

  @Override
  public void onMapUnsettled() 
    // Map unsettled
  

  @Override
  public void onMapSettled() 
    // Map settled
  
;

【讨论】:

对我来说似乎工作正常,这是对@Az13 解决方案的一个很好的增强:) @Mads Frandsen,可以分享您如何将您的库与 Marker setOnMarkerClickListener 结合起来?我尝试使用您的库,当按下任何标记时,代码将始终执行 onMapSettled 功能 @simeh 是的,我认为任何接触都会算作不安。您可能会在将其设置为 unsettled 之前检查是否有任何移动 - 这将避免 onMapSettled 调用,当它只是一个点击。如果到那时您还没有解决问题,我也许可以在本周末或下周末检查我的(旧)代码。 @Mads Frandsen,我设法通过将此库与另一个检测单点触摸的代码 sn-p 相结合来解决这个问题。感谢您的回复 很好,但是如果你可以将它上传到 Maven 或类似 jitpack.io 上会更好,这样我们就可以在 gradle 上使用它。【参考方案4】:

我会尝试onCameraChangeListener。每次摄像机移动完成时都会调用监听器。侦听器还将为您提供新位置。在我的测试中,监听器在拖动过程中经常被调用,也许有更好的解决方案。

【讨论】:

我找到了那个方法,但正如你所说,它经常触发。我只需要在用户停止拖动地图并放下手指时进行地理编码。所以看来我也需要自己处理触摸。但是新地图 api 中没有 onTouch 处理程序。 @AlexeyZakharov 是对的,它被触发太频繁导致动画卡住 AZ13 的答案非常好。【参考方案5】:

最简单的方法是使用 setOnCameraIdleListener 方法来处理地图片段上触摸侦听器的移动结束状态。 请看下面的例子:

mMap.setOnCameraMoveStartedListener(new GoogleMap.OnCameraMoveStartedListener() 
    @Override
    public void onCameraMoveStarted(int i) 
        mapPin.startAnimation(animZoomOut);
    
);

mMap.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() 
    @Override
    public void onCameraIdle() 
        mapPin.startAnimation(animZoomIn);
    
);

【讨论】:

【参考方案6】:

从 play-services-maps 9.4.0 开始,您可以简单地使用 GoogleMap.OnCameraMoveStartedListenerGoogleMap.OnCameraMoveListenerGoogleMap.OnCameraIdleListener

如果出于某种原因,您想使用现在已弃用的旧 API,您可以使用 onCameraChangeListener。但你必须注意两件事:

    onCameraChange() 可能会在您拖动地图时被调用多次或仅调用一次(在拖动停止时)。 onCameraChange() 的最后一次调用中的摄像头位置可能与最终摄像头位置略有不同。

以下代码将这两个问题都考虑在内:

private static final int MESSAGE_ID_SAVE_CAMERA_POSITION = 1;
private static final int MESSAGE_ID_READ_CAMERA_POSITION = 2;
private CameraPosition lastCameraPosition;
private Handler handler;
private GoogleMap googleMap;

public void onMapReady(GoogleMap theGoogleMap) 
    googleMap = theGoogleMap;

    handler = new Handler() 
        public void handleMessage(Message msg) 
            if (msg.what == MESSAGE_ID_SAVE_CAMERA_POSITION) 
                lastCameraPosition = googleMap.getCameraPosition();
             else if (msg.what == MESSAGE_ID_READ_CAMERA_POSITION) 
                if (lastCameraPosition.equals(googleMap.getCameraPosition())) 
                    Log.d(LOG, "Camera position stable");
                
            
        
    ;

    googleMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() 
        @Override
        public void onCameraChange(CameraPosition cameraPosition) 
            handler.removeMessages(MESSAGE_ID_SAVE_CAMERA_POSITION);
            handler.removeMessages(MESSAGE_ID_READ_CAMERA_POSITION);
            handler.sendEmptyMessageDelayed(MESSAGE_ID_SAVE_CAMERA_POSITION, 300);
            handler.sendEmptyMessageDelayed(MESSAGE_ID_READ_CAMERA_POSITION, 600);
        
    );

【讨论】:

【参考方案7】:

On camera idle is what you should use now

 googleMap.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() 
        @Override
        public void onCameraIdle() 
           //Called when camera movement has ended, there are no pending animations and the user has stopped interacting with the map.
        
 );

【讨论】:

【参考方案8】:

只要用户拖动地图,我就必须将我的标记设置为居中。我已经使用 Stas Shakirov 回答

实现了它

MapDragListenerFragment.class

public class MapDragListenerFragment extends Fragment implements OnMapReadyCallback, GoogleMap.OnMapLoadedCallback 

    private Context mContext;
    private SupportMapFragment supportMapFragment;
    private Marker centerMarker;
    private LatLng mapCenterLatLng;
    private TextView tvLocationName;
    private Button btnFinalizeDestination;
    private GoogleMap mGoogleMap;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) 
        return inflater.inflate(R.layout.fragment_map_drag_listener, container, false);
    

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) 
        super.onViewCreated(view, savedInstanceState);
        mContext = getActivity();

        tvLocationName = (TextView) view.findViewById(R.id.tv_location_name);
    

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) 
        super.onActivityCreated(savedInstanceState);

        FragmentManager fm = getActivity().getSupportFragmentManager();//getChildFragmentManager();//
        supportMapFragment = (SupportMapFragment) fm.findFragmentById(R.id.map_container);
        if (supportMapFragment == null) 
            //// FIXME: 2/13/2017 crashes at casting to TouchableMapFragment
            supportMapFragment = SupportMapFragment.newInstance();
            fm.beginTransaction().replace(R.id.map_container, supportMapFragment).commit();
        
        supportMapFragment.getMapAsync(this);

    

    @Override
    public void onMapReady(GoogleMap googleMap) 

        if (googleMap != null) 
            mGoogleMap = googleMap;

            centerMarker = mGoogleMap.addMarker(new MarkerOptions().position(mGoogleMap.getCameraPosition().target)
                    .title("Center of Map")
                    .icon(BitmapDescriptorFactory.fromResource(R.drawable.end_green)));

            mGoogleMap.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() 
                @Override
                public void onCameraIdle() 
                    mapCenterLatLng = mGoogleMap.getCameraPosition().target;

                    animateMarker(centerMarker,mapCenterLatLng,false);

                    Toast.makeText(mContext, "The camera has stopped moving.",
                            Toast.LENGTH_SHORT).show();

                    String address = getCompleteAddressString(mapCenterLatLng.longitude,mapCenterLatLng.longitude);
                    tvLocationName.setText(address);
                
            );
            mGoogleMap.setOnCameraMoveStartedListener(new GoogleMap.OnCameraMoveStartedListener() 
                @Override
                public void onCameraMoveStarted(int reason) 
                    if (reason == GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE) 
                        ///tvLocationName.setText("Lat " + mapCenterLatLng.latitude + "  Long :" + mapCenterLatLng.longitude);
                        Toast.makeText(mContext, "The user gestured on the map.",
                                Toast.LENGTH_SHORT).show();
                     else if (reason == GoogleMap.OnCameraMoveStartedListener
                            .REASON_API_ANIMATION) 
                        Toast.makeText(mContext, "The user tapped something on the map.",
                                Toast.LENGTH_SHORT).show();
                     else if (reason == GoogleMap.OnCameraMoveStartedListener
                            .REASON_DEVELOPER_ANIMATION) 
                        Toast.makeText(mContext, "The app moved the camera.",
                                Toast.LENGTH_SHORT).show();
                    
                
            );
            mGoogleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() 
                @Override
                public void onCameraMove() 
                    Toast.makeText(mContext, "The camera is moving.",
                            Toast.LENGTH_SHORT).show();
                
            );
            mGoogleMap.setOnCameraMoveCanceledListener(new GoogleMap.OnCameraMoveCanceledListener() 
                @Override
                public void onCameraMoveCanceled() 
                    Toast.makeText(mContext, "Camera movement canceled.",
                            Toast.LENGTH_SHORT).show();
                
            );

            mapCenterLatLng = mGoogleMap.getCameraPosition().target;// it should be done on MapLoaded.

            if (ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION) !=
                    PackageManager.PERMISSION_GRANTED &&
                    ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.ACCESS_COARSE_LOCATION) !=
                            PackageManager.PERMISSION_GRANTED) 
                return;
            
            mGoogleMap.setMyLocationEnabled(true);
            mGoogleMap.animateCamera(CameraUpdateFactory.zoomTo(15));

            mGoogleMap.setOnMapLoadedCallback(this);
            mGoogleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() 
                @Override
                public void onCameraMove() 

                
            );
        
    

    public void animateMarker(final Marker marker, final LatLng toPosition,
                              final boolean hideMarker) 
        final Handler handler = new Handler();
        final long start = SystemClock.uptimeMillis();
        Projection proj = mGoogleMap.getProjection();
        Point startPoint = proj.toScreenLocation(marker.getPosition());
        final LatLng startLatLng = proj.fromScreenLocation(startPoint);
         final long duration = 500;
        final Interpolator interpolator = new LinearInterpolator();
        handler.post(new Runnable() 
            @Override
            public void run() 
                long elapsed = SystemClock.uptimeMillis() - start;
                float t = interpolator.getInterpolation((float) elapsed
                        / duration);
                double lng = t * toPosition.longitude + (1 - t)
                        * startLatLng.longitude;
                double lat = t * toPosition.latitude + (1 - t)
                        * startLatLng.latitude;
                marker.setPosition(new LatLng(lat, lng));
                if (t < 1.0) 
                    // Post again 16ms later.
                    handler.postDelayed(this, 16);
                 else 
                    if (hideMarker) 
                        marker.setVisible(false);
                     else 
                        marker.setVisible(true);
                    
                
            
        );
    

在哪里 fragment_map_drag_listener.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_
    android:layout_
    android:orientation="vertical">

    <RelativeLayout
        android:layout_
        android:layout_
        android:layout_weight="1">

        <fragment
            android:id="@+id/map_container"
            android:name="com.google.android.gms.maps.SupportMapFragment"
            android:layout_
            android:layout_ />

        <ImageView
            android:id="@+id/iv_center_overlay"
            android:layout_
            android:layout_
            android:visibility="gone"
            android:layout_centerInParent="true"
            android:src="@drawable/start_blue" />
    </RelativeLayout>


    <TextView
        android:id="@+id/tv_location_name"
        android:layout_
        android:layout_
        android:padding="4dp"
        android:text="Location Name" />
</LinearLayout>

在哪里 MapDragListenerActivity

public class MapDragListenerActivity extends AppCompatActivity 

    private Context mContext;
    private static final String TAG = MapDragListenerFragment.class.getSimpleName();
    private MapDragListenerFragment mapDragListenerFragment;

    private Button selectPlaceBtn;
    public static final int PLACE_AUTOCOMPLETE_REQUEST_CODE = 1219;

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

        mContext = MapDragListenerActivity.this;

        mapDragListenerFragment = new MapDragListenerFragment();
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.frame_container,//where frame_container is a FrameLayout
                        mapDragListenerFragment,
                        MapyFragment.class.getSimpleName()).commit();


        selectPlaceBtn = (Button) findViewById(R.id.btn_select_place);

        selectPlaceBtn.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                try 
                    Intent intent = new PlaceAutocomplete.IntentBuilder(
                            PlaceAutocomplete.MODE_FULLSCREEN).build(MapDragListenerActivity.this);
                    startActivityForResult(intent, PLACE_AUTOCOMPLETE_REQUEST_CODE);
                 catch (GooglePlayServicesRepairableException e) 
                    e.printStackTrace();
                 catch (GooglePlayServicesNotAvailableException e) 
                    e.printStackTrace();
                
            
        );
    

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) 
        super.onActivityResult(requestCode, resultCode, data);

        if(requestCode == PLACE_AUTOCOMPLETE_REQUEST_CODE)
            if (resultCode == RESULT_OK) 
                Place place = PlaceAutocomplete.getPlace(mContext, data);
                if(mapDragListenerFragment != null && mapDragListenerFragment.isVisible())
                    mapDragListenerFragment.updateMarkerAtPosition(
                            place.getLatLng() ,place.getName().toString());

                Log.i(TAG, "Place:" + place.toString());
             else if (resultCode == PlaceAutocomplete.RESULT_ERROR) 
                Status status = PlaceAutocomplete.getStatus(mContext, data);
                Log.i(TAG, status.getStatusMessage());
             else if (requestCode == RESULT_CANCELED) 

            
        
    

activity_map_drag_listener.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_
    android:layout_
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_select_place"
        android:layout_
        android:layout_
        android:text="Select Place" />

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

</LinearLayout>

【讨论】:

【参考方案9】:
@Override
public boolean onTouchEvent(MotionEvent event, MapView mapView)

    if(event.getAction() == MotionEvent.ACTION_MOVE)
        return true;

    return false;

【讨论】:

这个问题被标记为低质量,因为如果它的长度和内容。您能否补充说明这是如何解决问题的,并可能引用所使用的功能?【参考方案10】:

我认为地图中的onclick事件是:ma​​p.setOnMapClick... 但是 event drag 是: ma​​p.onCameraChangeListener 因为我在这两个函数中都调用了 log.e 并且它显示为 onClick 视图和 onDrag 视图。所以只是为你使用它们。

【讨论】:

【参考方案11】:

在 Xamarin Android 中使用处理程序内部类的增强解决方案,基于 Tobus 答案:

public void OnMapReady(GoogleMap googleMap)

        _googleMap = googleMap;

        if (_googleMap != null)
        
            _cameraPositionHandler = new CameraPositionlHandler(_googleMap);

            _googleMap.CameraChange += OnCameraChanged; 

        


void OnCameraChanged (object sender, GoogleMap.CameraChangeEventArgs e)
   
    _cameraPositionHandler.RemoveMessages(MESSAGE_ID_SAVE_CAMERA_POSITION);
    _cameraPositionHandler.RemoveMessages(MESSAGE_ID_READ_CAMERA_POSITION);                 
    _cameraPositionHandler.SendEmptyMessageDelayed(MESSAGE_ID_SAVE_CAMERA_POSITION, 300);
    _cameraPositionHandler.SendEmptyMessageDelayed(MESSAGE_ID_READ_CAMERA_POSITION, 600);


具有以下内部类:

    private class CameraPositionlHandler :  Handler 
    
        private CameraPosition _lastCameraPosition;
        private GoogleMap _googleMap;

        public CameraPositionlHandler (GoogleMap googleMap)
        
            _googleMap = googleMap;
        

        public override void HandleMessage(Message msg) 
        
            if (_googleMap != null) 
            
                if (msg.What == MESSAGE_ID_SAVE_CAMERA_POSITION) 
                    _lastCameraPosition = _googleMap.CameraPosition;
                 else if (msg.What == MESSAGE_ID_READ_CAMERA_POSITION) 
                    if (_lastCameraPosition.Equals(_googleMap.CameraPosition)) 
                        Console.WriteLine("Camera position stable");
                        //do what you want
                    
                
            
        
    

【讨论】:

以上是关于如何使用适用于 Android V2 的 Google Maps 处理地图移动端?的主要内容,如果未能解决你的问题,请参考以下文章