Как я могу обработать конец перемещения карты с помощью Google Maps для Android V2?

Я хочу геокодировать адрес, как только центр карты будет изменен.

Как я могу справиться с перемещением карты с новыми Google Maps для Android V2? (я про случай, когда пользователь перетаскивает карту пальцем)


person Alexey Zakharov    schedule 04.12.2012    source источник
comment
метод onTouchEvent вам не помогает?   -  person Yahor10    schedule 04.12.2012
comment
Google до сих пор не имеет этого встроенного! безумный..   -  person M. Usman Khan    schedule 26.08.2019


Ответы (11)


Вот возможный обходной путь для определения событий начала и конца перетаскивания:

Вы должны расширить 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);
        }
    }
};
person AZ13    schedule 05.12.2012
comment
Привет, @Alexey Zakharov, пожалуйста, загляните также в Thread в системе отслеживания ошибок Google Maps: code.google.com/p/gmaps-api-issues/issues/ - person AZ13; 07.12.2012
comment
Это правда. Не гарантируется, что onCameraChanged() будет вызвана после того, как вы определили событие ACTION_UP. В любом случае, я также надеюсь, что Google очень скоро исправит эту проблему! - person AZ13; 07.12.2012
comment
@ AZ13 Я получаю сообщение об ошибке. Метод getActivity() не определен для типа MyMapFragment. Как вы объявляете MyMapFragment? - person Gaucho; 30.12.2012
comment
@ AZ13 Я изучил вашу реализацию и должен признать, что событие все еще не запускается при каждом движении карты. Посмотрите демонстрационный проект, который я подготовил с использованием вашего кода. - person JJD; 17.09.2013
comment
Хорошо, но на самом деле вам не нужно реализовывать настраиваемый макет кадра, достаточно сделать setOnTouchListener(new View.OnTouchListener() ... на макете кадра с кодом обработки событий. - person Aphex; 21.04.2014
comment
Это не будет работать в реальном мире. События касания происходят в режиме реального времени, а onCameraChange не синхронизировано, оно может появиться даже через несколько секунд ПОСЛЕ события. Таким образом, ваши логические состояния во многих случаях будут неправильными (я проверял это). Я экспериментирую с близким решением, используя ACTION_MOVE для установки логического значения, которое сбрасывается в onCameraChange. Это может работать лучше. Однако я не думаю, что это решение вообще будет работать идеально, идеальное решение требует изменения самого фрагмента для вызова дополнительного события. - person John; 08.12.2014
comment
@John @AZ13 @Alex Zakharov, поставь getMap().moveCamera(CameraUpdateFactory.newCameraPosition(getMap().getCameraPosition())); после mMapIsTouched = false;. Это заставляет onCameraChange всегда выполняться после dispatchTouchEvent. :D - person Ascension; 05.05.2015
comment
@ AZ13, да, Google очень скоро это исправит, эта проблема существует уже более 3 лет, и угадайте, что еще ничего. - person Eduardo; 27.10.2015
comment
чрезвычайно полезный ответ. Я хочу, чтобы это было встроено! - person Zach; 11.01.2017

Проверьте новые карты 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();
}

пример с сайта developmenters.google.com

person punksta    schedule 05.08.2016
comment
Идеальное решение! - person Blablablabli; 16.11.2016

УСТАРЕЛО Вместо этого используйте новый API карт. См. ответ от punksta.

После использования решения AZ13, описанного выше, и столкнувшись с проблемой, упомянутой в комментариях, я создал следующее решение, которое решает проблему более надежно. Однако, поскольку я использую таймер после события 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
  }
};
person Mads Frandsen    schedule 21.04.2014
comment
у меня работает нормально, это хорошее улучшение решения @Az13 :) - person Javier; 17.09.2014
comment
@Mads Frandsen, можно поделиться, как бы вы объединили свою библиотеку с Marker setOnMarkerClickListener? Я пытался использовать вашу библиотеку, и при нажатии на любой маркер код всегда будет выполнять функцию onMapSettled. - person Bih Cheng; 20.05.2015
comment
@simeh Да, я думаю, что любое прикосновение будет считаться неурегулированным. Вероятно, вы могли бы проверить, было ли какое-либо движение, прежде чем установить его как неустановленное - это позволит избежать вызова onMapSettled, когда это просто щелчок. Возможно, я смогу просмотреть свой (старый) фрагмент кода в эти выходные или в следующие, если к тому времени вы еще не решили проблему. - person Mads Frandsen; 11.06.2015
comment
@Mads Frandsen, мне удалось решить эту проблему, объединив эту библиотеку с другим фрагментом кода, который обнаруживает одно касание. Спасибо за ответ - person Bih Cheng; 12.06.2015
comment
Хорошо, но было бы намного лучше, если бы вы могли загрузить его в Maven или на jitpack.io, чтобы мы могли использовать его в gradle. - person Cristian Gomez; 23.06.2016

Я бы попробовал onCameraChangeListener. Слушатель вызывается каждый раз, когда движение камеры заканчивается. Слушатель также даст вам новое местоположение. В моих тестах прослушиватель вызывался довольно часто во время перетаскивания, возможно, есть лучшее решение.

person Janusz    schedule 04.12.2012
comment
Я нашел этот метод, но, как вы сказали, он срабатывает очень часто. Мне нужно геокодировать только тогда, когда пользователь перестанет перетаскивать карту и уберет палец. Так что кажется, что мне тоже нужно справляться с прикосновениями. Но в новом API карты нет обработчика onTouch. - person Alexey Zakharov; 04.12.2012
comment
@AlexeyZakharov прав, он срабатывает слишком часто, из-за чего анимация зависает. Ответ AZ13 действительно хороший. - person TypingPanda; 01.08.2014

Самый простой способ — использовать метод 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);
    }
});
person Eric    schedule 02.12.2017

Начиная с play-services-maps 9.4.0, вы можете просто использовать GoogleMap.OnCameraMoveStartedListener, GoogleMap.OnCameraMoveListener и GoogleMap.OnCameraIdleListener.

Если по какой-то причине вы хотите использовать старый API, который сейчас устарел, вы можете использовать onCameraChangeListener. Но вы должны знать о двух вещах:

  1. onCameraChange() может вызываться много раз, пока вы перетаскиваете карту, ИЛИ только один раз (когда перетаскивание остановлено).
  2. Положение камеры в последнем вызове 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);
        }
    });
}
person Tobus    schedule 06.06.2016


Я должен анимировать свой маркер в центр, пока пользователь перетаскивает карту. Я реализовал это, используя ответ Стаса Шакирова.

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_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

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

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


    <TextView
        android:id="@+id/tv_location_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        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_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_select_place"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Select Place" />

    <FrameLayout
        android:id="@+id/frame_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
person AndroidGeek    schedule 02.10.2017

Я думаю, что событие onclick на карте: map.setOnMapClick... Но перетаскивание события: map.onCameraChangeListener, потому что я вызываю < strong>log.e в обеих этих функциях, и он отображается как представление onClick и представление onDrag . Так что просто используйте их для вас.

person nobjta_9x_tq    schedule 06.08.2015

Расширенное решение с внутренним классом Handler в 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
                    }
                }
            }
        }
    }
person iMacX    schedule 06.07.2016

person    schedule
comment
Этот вопрос помечен как низкокачественный из-за его длины и содержания. Не могли бы вы добавить объяснение того, как это решает проблему, и, возможно, указать используемые функции? - person Popnoodles; 17.06.2014