谷歌地图Android API v2 - 交互式信息窗口(如原始Android谷歌地图中的窗口)

199

我正在尝试使用新的Google Maps API v2,在单击标记后制作自定义InfoWindow。 我想让它看起来像Google原始地图应用程序中的样子。 就像这样:

Example Image

当我在ImageButton内部时,它不起作用 - 整个InfoWindow被选中,而不仅仅是ImageButton。 我读到这是因为没有View本身,而只有快照,所以无法将单个项目区分开来。
编辑: 在documentation(感谢Disco S2)中提到:
如前一节中所述,信息窗口不是实时视图,而是以图像形式呈现在地图上。 因此,您设置在视图上的任何侦听器都将被忽略,并且您无法区分对视图各个部分的单击事件。 建议不要在自定义信息窗口中放置交互式组件,例如按钮、复选框或文本输入。
但是如果谷歌使用它,肯定有办法解决。 有人有什么想法吗?

“它不起作用”并不能很好地描述您的症状。这里有一个示例项目,展示了如何使用自定义信息窗口和图像:https://github.com/commonsguy/cw-omnibus/tree/master/MapsV2/Popups - CommonsWare
8
@CommonsWare我已经解释了原因。从原始文档中注明:绘制的信息窗口不是实时视图。该视图是作为图像渲染的(使用View.draw(Canvas))...这意味着对视图进行的任何后续更改都不会反映在地图上的信息窗口中。要稍后更新信息窗口,此外,信息窗口将不会遵循任何正常视图的交互性,例如触摸或手势事件。但是,您可以按照下面的部分描述监听整个信息窗口的通用单击事件......在您的示例中只有TextView。 - user1943012
你好,当全屏模式时,你是如何让我的位置按钮出现在操作栏下方的?谢谢! - tkblackbelt
@tkblackbelt 试试使用RelativeLayout - Driss Bounouar
47
我认为这个问题不应该被关闭。这是一个合法的问题,与我面临的问题相似。 - marienke
12
我经常看到这种情况,许多有用的问题和答案被关闭了。一个问题可能并不总是符合标准问题的要求,但如果我们有一个解释清晰的问题和答案,那么没有理由关闭这个问题。人们使用Stackoverflow来交流知识,关闭一个如此活跃和富有成效的问题帖子作为“离题”的做法对我来说似乎不是一个好主意。 - John
7个回答

344
我也曾经为此问题苦苦寻找解决方案,但一直没有成功,所以我自己动手实现了一个并想在这里与大家分享。(请原谅我的糟糕英语)(用英语回答其他捷克人的问题有点疯狂 :-) )
我尝试的第一件事是使用老旧的PopupWindow。它非常简单 - 只需要监听OnMarkerClickListener,然后在标记上方显示一个自定义的PopupWindow。这个解决方案看起来很不错,所以StackOverflow上的其他一些人建议使用它。但是,当您开始移动地图时,此解决方案的问题就会出现。您必须自己移动PopupWindow,这可能是可能的(通过侦听某些onTouch事件),但在我看来,您无法使其足够好看,尤其是在某些慢速设备上。如果您以简单的方式做到这一点,它将从一个位置“跳跃”到另一个位置。您还可以使用一些动画来优化这些跳跃,但是这样,PopupWindow始终会比应该出现在地图上的位置“慢一步”,这是我不喜欢的。
此时,我在考虑其他解决方案。我意识到我实际上并不需要那么多自由-显示具有所有可能性(例如动画进度条等)的自定义视图。我认为甚至Google工程师在Google Maps应用程序中也不是这样做的一个很好的原因。我需要的只是InfoWindow上的一个或两个按钮,在点击时显示按下状态并触发一些操作。因此,我想出了另一个解决方案,它分为两部分:
第一部分: 第一部分是能够捕获按钮上的单击以触发某些操作。我的想法如下:
1.保留对在InfoWindowAdapter中创建的自定义infoWindow的引用。 2.将MapFragment(或MapView)包装在自定义ViewGroup中(我的称为MapWrapperLayout) 3.重写MapWrapperLayout的dispatchTouchEvent,并且(如果当前正在显示InfoWindow),首先将MotionEvents路由到先前创建的InfoWindow。如果它没有使用MotionEvents(例如因为您没有在InfoWindow内的任何可点击区域上单击等),那么(仅在这种情况下)让事件传递到MapWrapperLayout的超类,以便最终传递给地图。
以下是MapWrapperLayout的源代码:
package com.circlegate.tt.cg.an.lib.map;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.Marker;

import android.content.Context;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;

public class MapWrapperLayout extends RelativeLayout {
    /**
     * Reference to a GoogleMap object 
     */
    private GoogleMap map;

    /**
     * Vertical offset in pixels between the bottom edge of our InfoWindow 
     * and the marker position (by default it's bottom edge too).
     * It's a good idea to use custom markers and also the InfoWindow frame, 
     * because we probably can't rely on the sizes of the default marker and frame. 
     */
    private int bottomOffsetPixels;

    /**
     * A currently selected marker 
     */
    private Marker marker;

    /**
     * Our custom view which is returned from either the InfoWindowAdapter.getInfoContents 
     * or InfoWindowAdapter.getInfoWindow
     */
    private View infoWindow;    

    public MapWrapperLayout(Context context) {
        super(context);
    }

    public MapWrapperLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MapWrapperLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    /**
     * Must be called before we can route the touch events
     */
    public void init(GoogleMap map, int bottomOffsetPixels) {
        this.map = map;
        this.bottomOffsetPixels = bottomOffsetPixels;
    }

    /**
     * Best to be called from either the InfoWindowAdapter.getInfoContents 
     * or InfoWindowAdapter.getInfoWindow. 
     */
    public void setMarkerWithInfoWindow(Marker marker, View infoWindow) {
        this.marker = marker;
        this.infoWindow = infoWindow;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean ret = false;
        // Make sure that the infoWindow is shown and we have all the needed references
        if (marker != null && marker.isInfoWindowShown() && map != null && infoWindow != null) {
            // Get a marker position on the screen
            Point point = map.getProjection().toScreenLocation(marker.getPosition());

            // Make a copy of the MotionEvent and adjust it's location
            // so it is relative to the infoWindow left top corner
            MotionEvent copyEv = MotionEvent.obtain(ev);
            copyEv.offsetLocation(
                -point.x + (infoWindow.getWidth() / 2), 
                -point.y + infoWindow.getHeight() + bottomOffsetPixels);

            // Dispatch the adjusted MotionEvent to the infoWindow
            ret = infoWindow.dispatchTouchEvent(copyEv);
        }
        // If the infoWindow consumed the touch event, then just return true.
        // Otherwise pass this event to the super class and return it's result
        return ret || super.dispatchTouchEvent(ev);
    }
}

所有这些都将使InfoView内部的视图“活”起来 - OnClickListeners将开始触发等。

第二部分: 剩下的问题是,显然,您无法在屏幕上看到InfoWindow的任何UI更改。要做到这一点,您必须手动调用Marker.showInfoWindow。现在,如果您对InfoWindow进行了某些永久更改(例如将按钮标签更改为其他内容),那么这就足够了。

但是,显示按下状态的按钮或类似的东西更加复杂。首先的问题是,我至少无法使InfoWindow显示普通按钮的按下状态。即使我长时间按下按钮,它也仍然未被按下。我相信这是地图框架本身处理的内容,它可能确保不在信息窗口中显示任何瞬态状态。但我可能是错的,我没有尝试找出这个问题。

我所做的是另一种恶心的黑客方式-我将一个OnTouchListener附加到按钮上,并在按钮被按下或释放时手动切换其背景到两个自定义可绘制对象-一个处于正常状态的按钮和另一个处于按下状态的按钮。这不是很好,但有效:)。现在,我能够看到按钮在屏幕上在正常状态和按下状态之间切换。

仍然有一个小问题-如果您过快地单击按钮,它不会显示按下状态-它仅保持在正常状态(尽管单击本身已被触发,因此按钮“有效”)。至少这是在我的Galaxy Nexus上的情况。所以我最后做的一件事是稍微延迟了按钮的按下状态。这也相当丑陋,我不确定它在一些旧设备上是否有效,但我怀疑即使地图框架本身也会这样做。您可以自己尝试-当您单击整个InfoWindow时,它会保持在按下状态较长时间,然后再变回正常状态的按钮(同样-至少在我的手机上)。这实际上是在原始的Google Maps应用程序上运行的方式。

无论如何,我写了一个自定义类来处理按钮状态更改和我提到的所有其他事情,所以这里是代码:

package com.circlegate.tt.cg.an.lib.map;

import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

import com.google.android.gms.maps.model.Marker;

public abstract class OnInfoWindowElemTouchListener implements OnTouchListener {
    private final View view;
    private final Drawable bgDrawableNormal;
    private final Drawable bgDrawablePressed;
    private final Handler handler = new Handler();

    private Marker marker;
    private boolean pressed = false;

    public OnInfoWindowElemTouchListener(View view, Drawable bgDrawableNormal, Drawable bgDrawablePressed) {
        this.view = view;
        this.bgDrawableNormal = bgDrawableNormal;
        this.bgDrawablePressed = bgDrawablePressed;
    }

    public void setMarker(Marker marker) {
        this.marker = marker;
    }

    @Override
    public boolean onTouch(View vv, MotionEvent event) {
        if (0 <= event.getX() && event.getX() <= view.getWidth() &&
            0 <= event.getY() && event.getY() <= view.getHeight())
        {
            switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: startPress(); break;

            // We need to delay releasing of the view a little so it shows the pressed state on the screen
            case MotionEvent.ACTION_UP: handler.postDelayed(confirmClickRunnable, 150); break;

            case MotionEvent.ACTION_CANCEL: endPress(); break;
            default: break;
            }
        }
        else {
            // If the touch goes outside of the view's area
            // (like when moving finger out of the pressed button)
            // just release the press
            endPress();
        }
        return false;
    }

    private void startPress() {
        if (!pressed) {
            pressed = true;
            handler.removeCallbacks(confirmClickRunnable);
            view.setBackground(bgDrawablePressed);
            if (marker != null) 
                marker.showInfoWindow();
        }
    }

    private boolean endPress() {
        if (pressed) {
            this.pressed = false;
            handler.removeCallbacks(confirmClickRunnable);
            view.setBackground(bgDrawableNormal);
            if (marker != null) 
                marker.showInfoWindow();
            return true;
        }
        else
            return false;
    }

    private final Runnable confirmClickRunnable = new Runnable() {
        public void run() {
            if (endPress()) {
                onClickConfirmed(view, marker);
            }
        }
    };

    /**
     * This is called after a successful click 
     */
    protected abstract void onClickConfirmed(View v, Marker marker);
}

这是我使用的自定义信息窗口布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center_vertical" >

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_marginRight="10dp" >

        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Title" />

        <TextView
            android:id="@+id/snippet"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="snippet" />

    </LinearLayout>

    <Button
        android:id="@+id/button" 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" />

</LinearLayout>

测试活动布局文件(MapFragment位于MapWrapperLayout内):

<com.circlegate.tt.cg.an.lib.map.MapWrapperLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/map_relative_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <fragment
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="com.google.android.gms.maps.MapFragment" />

</com.circlegate.tt.cg.an.lib.map.MapWrapperLayout>

最后是一个测试活动的源代码,它将所有这些内容粘合在一起:

package com.circlegate.testapp;

import com.circlegate.tt.cg.an.lib.map.MapWrapperLayout;
import com.circlegate.tt.cg.an.lib.map.OnInfoWindowElemTouchListener;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.InfoWindowAdapter;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {    
    private ViewGroup infoWindow;
    private TextView infoTitle;
    private TextView infoSnippet;
    private Button infoButton;
    private OnInfoWindowElemTouchListener infoButtonListener;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final MapFragment mapFragment = (MapFragment)getFragmentManager().findFragmentById(R.id.map);
        final MapWrapperLayout mapWrapperLayout = (MapWrapperLayout)findViewById(R.id.map_relative_layout);
        final GoogleMap map = mapFragment.getMap();

        // MapWrapperLayout initialization
        // 39 - default marker height
        // 20 - offset between the default InfoWindow bottom edge and it's content bottom edge 
        mapWrapperLayout.init(map, getPixelsFromDp(this, 39 + 20)); 

        // We want to reuse the info window for all the markers, 
        // so let's create only one class member instance
        this.infoWindow = (ViewGroup)getLayoutInflater().inflate(R.layout.info_window, null);
        this.infoTitle = (TextView)infoWindow.findViewById(R.id.title);
        this.infoSnippet = (TextView)infoWindow.findViewById(R.id.snippet);
        this.infoButton = (Button)infoWindow.findViewById(R.id.button);

        // Setting custom OnTouchListener which deals with the pressed state
        // so it shows up 
        this.infoButtonListener = new OnInfoWindowElemTouchListener(infoButton,
                getResources().getDrawable(R.drawable.btn_default_normal_holo_light),
                getResources().getDrawable(R.drawable.btn_default_pressed_holo_light)) 
        {
            @Override
            protected void onClickConfirmed(View v, Marker marker) {
                // Here we can perform some action triggered after clicking the button
                Toast.makeText(MainActivity.this, marker.getTitle() + "'s button clicked!", Toast.LENGTH_SHORT).show();
            }
        }; 
        this.infoButton.setOnTouchListener(infoButtonListener);


        map.setInfoWindowAdapter(new InfoWindowAdapter() {
            @Override
            public View getInfoWindow(Marker marker) {
                return null;
            }

            @Override
            public View getInfoContents(Marker marker) {
                // Setting up the infoWindow with current's marker info
                infoTitle.setText(marker.getTitle());
                infoSnippet.setText(marker.getSnippet());
                infoButtonListener.setMarker(marker);

                // We must call this to set the current marker and infoWindow references
                // to the MapWrapperLayout
                mapWrapperLayout.setMarkerWithInfoWindow(marker, infoWindow);
                return infoWindow;
            }
        });

        // Let's add a couple of markers
        map.addMarker(new MarkerOptions()
            .title("Prague")
            .snippet("Czech Republic")
            .position(new LatLng(50.08, 14.43)));

        map.addMarker(new MarkerOptions()
            .title("Paris")
            .snippet("France")
            .position(new LatLng(48.86,2.33)));

        map.addMarker(new MarkerOptions()
            .title("London")
            .snippet("United Kingdom")
            .position(new LatLng(51.51,-0.1)));
    }

    public static int getPixelsFromDp(Context context, float dp) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int)(dp * scale + 0.5f);
    }
}

这就是全部。目前我只在我的Galaxy Nexus(4.2.1)和Nexus 7(同样是4.2.1)上测试过,我将在有机会时尝试在一些Gingerbread手机上进行测试。到目前为止我发现的一个限制是,您无法从屏幕上的按钮所在位置拖动地图并移动地图。它可能可以通过某种方式克服,但现在,我可以接受这种情况。
我知道这是一个丑陋的hack,但我没有找到更好的方法,而且我非常需要这种设计模式,以至于这真的是回到地图v1框架的原因(顺便说一下,对于具有片段等内容的新应用程序,我真的很想避免这种情况)。我只是不明白为什么Google不向开发人员提供一种官方的方式来在InfoWindows上放置一个按钮。这是如此普遍的设计模式,而且甚至在官方的Google Maps应用程序中也使用了这种模式 :)。我理解他们不能让您的视图在InfoWindows中“活动”的原因 - 这可能会在移动和滚动地图时导致性能下降。但应该有一些方法可以在不使用视图的情况下实现此效果。

6
有趣的解决方案,我想尝试一下。表现如何? - Patrick Jackson
10
需要将view.setBackground(bgDrawablePressed);替换为以下代码才能在Android Gingerbread中运行:if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { view.setBackgroundDrawable(bgDrawablePressed); } else { view.setBackground(bgDrawablePressed); }请注意,代码中还有一个view.setBackground需要进行相同的更改。 - Balaji
4
你的InfoWindowAdapter实现可能有问题。需要覆盖两个方法:getInfoWindow()和getInfoContents()。框架首先调用getInfoWindow()方法,只有在它返回null时才会调用getInfoContents()方法。如果你从getInfoWindow中返回了一些View,框架将把它放在默认的信息窗口框架内 - 而这种情况下你不希望它这样做。所以只需从getInfoWindow返回null,并从getInfoContents返回你的View,那么问题就应该解决了。 - chose007
18
如果有人感兴趣,我在我的应用程序CG Transit中使用了这个解决方案(您可以在Google Play上找到它)。两个月后,已经有数万名用户使用它,而且还没有任何投诉。 - chose007
2
将OnInfoWindowElemTouchListener中的onTouch方法改为返回true即可解决该问题。 - Volodymyr Kulyk
显示剩余43条评论

14

我看到这个问题已经很久了,但仍然...

我们在公司开发了一个简单的库,以实现所需的功能 - 一个交互式信息窗口,带有各种视图和功能。您可以在GitHub上查看。

希望它能有所帮助 :)


1
它能否与集群一起使用? - gcolucci
这个库能在 React Native Expo 项目中使用吗? - Yogendra Patel

9
这是我对问题的看法。我创建了一个包含信息窗口(具有每个交互和绘图能力的常规视图)的AbsoluteLayout叠加层。然后,我启动处理程序,每16毫秒将信息窗口的位置与地图上点的位置同步。听起来很疯狂,但实际上很有效。
演示视频:https://www.youtube.com/watch?v=bT9RpH4p9mU(请注意,性能因模拟器和视频录制同时运行而降低)。
演示代码:https://github.com/deville/info-window-demo 提供详细信息的文章(俄文):http://habrahabr.ru/post/213415/

这会减慢/延迟地图的滚动。 - Shehan Ekanayake

2

对于那些无法运行choose007的答案的人

如果chose007的解决方案中clickListener不能始终正常工作,请尝试使用View.onTouchListener来代替clickListener。使用任何一个动作ACTION_UPACTION_DOWN处理触摸事件。由于某种原因,地图上的infoWindow在分派给clickListeners时会导致一些奇怪的行为。

infoWindow.findViewById(R.id.my_view).setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
          int action = MotionEventCompat.getActionMasked(event);
          switch (action){
                case MotionEvent.ACTION_UP:
                    Log.d(TAG,"a view in info window clicked" );
                    break;
                }
                return true;
          }

编辑:以下是我逐步完成的过程

首先在您的活动/片段中的某个位置扩充自己的信息窗口(全局变量)。我的在片段内。还要确保信息窗口布局中的根视图是LinearLayout(由于某种原因,RelativeLayout在信息窗口中占用了整个屏幕宽度)。

infoWindow = (ViewGroup) getActivity().getLayoutInflater().inflate(R.layout.info_window, null);
/* Other global variables used in below code*/
private HashMap<Marker,YourData> mMarkerYourDataHashMap = new HashMap<>();
private GoogleMap mMap;
private MapWrapperLayout mapWrapperLayout;

接下来,在 Google Maps Android API 的 onMapReady 回调函数中(如果您不知道 onMapReady 是什么,请参考此链接:Maps > Documentation - Getting Started)。

   @Override
    public void onMapReady(GoogleMap googleMap) {
       /*mMap is global GoogleMap variable in activity/fragment*/
        mMap = googleMap;
       /*Some function to set map UI settings*/ 
        setYourMapSettings();

MapWrapperLayout 初始化 https://dev59.com/DGYq5IYBdhLWcg3w4Edx interactive-infowindow-like-in-original-android-go/15040761#15040761 39 - 默认标记高度 20 - 默认信息窗口底部边缘与其内容底部边缘之间的偏移量 */

        mapWrapperLayout.init(mMap, Utils.getPixelsFromDp(mContext, 39 + 20));

        /*handle marker clicks separately - not necessary*/
       mMap.setOnMarkerClickListener(this);

       mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {
                @Override
                public View getInfoWindow(Marker marker) {
                    return null;
                }

            @Override
            public View getInfoContents(Marker marker) {
                YourData data = mMarkerYourDataHashMap.get(marker);
                setInfoWindow(marker,data);
                mapWrapperLayout.setMarkerWithInfoWindow(marker, infoWindow);
                return infoWindow;
            }
        });
    }

SetInfoWindow method

private void setInfoWindow (final Marker marker, YourData data)
            throws NullPointerException{
        if (data.getVehicleNumber()!=null) {
            ((TextView) infoWindow.findViewById(R.id.VehicelNo))
                    .setText(data.getDeviceId().toString());
        }
        if (data.getSpeed()!=null) {
            ((TextView) infoWindow.findViewById(R.id.txtSpeed))
                    .setText(data.getSpeed());
        }

        //handle dispatched touch event for view click
        infoWindow.findViewById(R.id.any_view).setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = MotionEventCompat.getActionMasked(event);
                switch (action) {
                    case MotionEvent.ACTION_UP:
                        Log.d(TAG,"any_view clicked" );
                        break;
                }
                return true;
            }
        });

单独处理标记点击事件

    @Override
    public boolean onMarkerClick(Marker marker) {
        Log.d(TAG,"on Marker Click called");
        marker.showInfoWindow();
        CameraPosition cameraPosition = new CameraPosition.Builder()
                .target(marker.getPosition())      // Sets the center of the map to Mountain View
                .zoom(10)
                .build();
        mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition),1000,null);
        return true;
    }

你能给我展示一下你的代码吗?我成功地记录了触摸事件,但是点击事件没有记录,所以我采用了这种实现方式。另外,你需要正确地实现choose007的解决方案。 - Manish Jangid
我分享了我的完整实现。如果您有任何疑问,请告诉我。 - Manish Jangid

-1

只是一种猜测,我没有足够的经验来尝试它... )-:

由于GoogleMap是片段,因此应该可以捕获标记onClick事件并显示自定义片段视图。地图片段仍将显示在背景上。有人尝试过吗?为什么不行?

缺点是地图片段将被冻结在背景上,直到自定义信息片段将控制权返回给它。


7
我认为将猜测作为答案发布并不有趣。或许应该作为原问题的评论。这只是我的想法。 - Johan Karlsson
1
这个库 https://github.com/Appolica/InteractiveInfoWindowAndroid 做了类似的事情。 - Deyan Genovski

-3

我已经为这个问题建立了一个示例Android Studio项目。

输出屏幕截图:

enter image description here

enter image description here

enter image description here

下载完整的项目源代码,请点击这里

请注意:您必须在Androidmanifest.xml文件中添加您的API密钥。


5
最好通过Github/gist分享您的代码,而不是使用zip文件。这样我们可以直接在那里检查。 - Noundla Sandeep
我发现了一个小程序bug,当你点击某个按钮时,所有按钮都被点击了。 - Morozov
3
@Noundla及其他人:不要费心去下载他的压缩代码。它是这个问题所接受答案的完全复制粘贴。 - Ganesh Mohan
1
@ganesh2shiv 是正确的,压缩的代码是无用的。你不应该附上复制粘贴的代码。 - Onkar Nene

-8

这非常简单。

googleMap.setInfoWindowAdapter(new InfoWindowAdapter() {

            // Use default InfoWindow frame
            @Override
            public View getInfoWindow(Marker marker) {              
                return null;
            }           

            // Defines the contents of the InfoWindow
            @Override
            public View getInfoContents(Marker marker) {

                // Getting view from the layout file info_window_layout
                View v = getLayoutInflater().inflate(R.layout.info_window_layout, null);

                // Getting reference to the TextView to set title
                TextView note = (TextView) v.findViewById(R.id.note);

                note.setText(marker.getTitle() );

                // Returning the view containing InfoWindow contents
                return v;

            }

        });

只需将上述代码添加到使用GoogleMap的类中。R.layout.info_window_layout是我们自定义布局,显示将出现在infowindow位置的视图。我只是在这里添加了textview。您可以在此处添加其他视图,以使其像示例快照一样。 我的info_window_layout如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" 
    android:orientation="vertical" 
    >

        <TextView 
        android:id="@+id/note"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

希望这能有所帮助。我们可以在http://wptrafficanalyzer.in/blog/customizing-infowindow-contents-in-google-map-android-api-v2-using-infowindowadapter/#comment-39731找到自定义信息窗口的工作示例。

编辑:此代码显示了如何在infoWindow上添加自定义视图。该代码未处理自定义视图项上的点击事件。因此,它接近答案但不完全是答案,这就是为什么它没有被接受为答案的原因。


您是否在创建标记时覆盖了getInfoContents和getInfoWindow方法?例如,如果我正在添加不同种类的标记并使用不同的方法,那么我只需在创建不同标记的每种方法体中进行覆盖吗? - Rarw
31
看起来你没有读懂我的问题。我知道如何在信息窗口内创建自定义视图,但我不知道的是如何在内部创建自定义按钮并监听其点击事件。 - user1943012
5
此解决方案并未回答原问题。 - biddulph.r
此解决方案并未回答问题。 - TlmaK0
这不是真正的解决方案,只是重申问题。该适配器通过将视图渲染到位图来获取视图的快照,而不是交互式的。 - HaMMeReD
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接