Я не могу найти пример того, как перехватить привязку карты к новому API Google Maps v2.
Мне нужно знать, когда пользователь прикасается к карте, чтобы остановить поток (центрирование карты вокруг моего текущего местоположения).
Я не могу найти пример того, как перехватить привязку карты к новому API Google Maps v2.
Мне нужно знать, когда пользователь прикасается к карте, чтобы остановить поток (центрирование карты вокруг моего текущего местоположения).
@ape написал здесь ответ о том, как перехватывать клики по карте, но мне нужно перехватить штрихи, а затем он предложил следующую ссылку в комментарии своего ответа How для обработки события onTouch для карты в Google Map API v2?.
Это решение кажется возможным, но предлагаемый код был неполным. По этой причине я переписал и протестировал его, и теперь он работает.
Здесь это рабочий код:
Я создал класс 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);
}
}
Вот простое решение для получения местоположения на основе выбора пользователя (опция "Щелчок" на карте)
googleMap.setOnMapClickListener(new OnMapClickListener() {
@Override
public void onMapClick(LatLng arg0) {
// TODO Auto-generated method stub
Log.d("arg0", arg0.latitude + "-" + arg0.longitude);
}
});
Эта функция и многие другие теперь поддерживаются:)
это примечание разработчика (выпуск 4636):
В выпуске в августе 2016 года представлен набор новых слушателей смены камеры для запуска, текущего и конечного событий движения камеры. Вы также можете узнать, почему камера движется, вызвана ли она жестов пользователя, встроенной анимацией 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();
}
}
Я создал пустой пул 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.
}
});
См. эту ссылку. Внесите интерфейс и заполните метод 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
}
}
У Gaucho есть отличный ответ, и, увидев множество upvotes, я понял, что может возникнуть необходимость в другой реализации:
Мне нужно было использовать прослушиватель, чтобы я мог реагировать на ощупь и не нужно постоянно его проверять.
Я помещаю все в один класс, который можно использовать следующим образом:
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
}
}
});
где mapfragment должен быть типа 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);
}
}
}
// 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
}
}
});
Для Mono любители:
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);
}
}
}
У меня более простое решение, отличное от TouchableWrapper
, и это работает с последней версией play-services-maps:10.0.1
. Это решение использует только события карт и не использует пользовательские представления. Не использует устаревшие функции и, вероятно, будет поддерживать несколько версий.
Сначала вам понадобится переменная флага, которая хранится, если карта перемещается анимацией или пользовательским вводом (это указывает, что каждый движок камеры, который не запускается анимацией, запускается пользователем)
GoogleMap googleMap;
boolean movedByApi = false;
Ваш фрагмент или активность должны реализовывать GoogleMap.OnMapReadyCallback
, GoogleMap.CancelableCallback
public class ActivityMap extends Activity implements OnMapReadyCallback, GoogleMap.CancelableCallback{
...
}
и это заставляет вас реализовать методы onMapReady
, onFinish
, onCancel
. И объект googleMap в onMapReady
должен установить eventlistener для перемещения камеры
@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;
}
И, наконец, его beter, если вы создадите общую функцию для перемещения карты
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
}
}
@Gaucho MySupportMapFragment, очевидно, будет использоваться каким-либо другим эффектом или активностью (где может быть больше элементов представления, чем фрагмент карты). Итак, как можно отправить это событие в следующий фрагмент, где он будет использоваться. Нужно ли нам снова написать интерфейс?