如何使用适用于 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 处理地图移动端?的主要内容,如果未能解决你的问题,请参考以下文章

适用于 ios OAuth2、API V2 的 LinkedIn 登录。如何打开已安装的 LinkedIn 应用程序

Google Maps Android api v2 和当前位置

如何在 Docusaurus v2 中嵌入全局 React 组件?

使用地理编码器和 android Google Maps API v2 获取纬度和经度

powershell Powershell尝试捕获,仅适用于v2

Python wave 模块仅适用于 v2.7,不适用于 v3.4 linux