高德地图SDK Android版开发 10 InfoWindow

前言

前文介绍高德地图添加Marker覆盖物的使用方法,Marker结合InfoWindow可展示更详尽的信息。本文将介绍以下内容:

  1. 使用SDK默认样式显示InfoWindow的方法;
  2. 自定义InfoWindow样式的方法。

相关类和方法

默认样式

  • 在高德地图SDK中,InfoWindow 是点标记的一部分,默认的 Infowindow 只显示 Marker 对象的两个属性,一个是 title 和另一个 snippet。
  • SDK 为用户提供了默认的 InfoWindow 样式,调用 Marker 类的 showInfoWindow()hideInfoWindow() 方法可以控制显示和隐藏。
  • 当改变 Markertitlesnippet 属性时,再次调用 showInfoWindow(),可以更新 InfoWindow 显示内容。

Marker类

  • 标题、文字片段和附加信息的方法
类型 方法 说明
String getSnippet() 获取Marker 覆盖物的文字片段。
String getTitle() 获取Marker 覆盖物的标题。
Object getObject() 获取Marker覆盖物的附加信息对象,即自定义的Marker的属性。
void setSnippet(String snippet) 设置Marker 覆盖物的文字片段。
void setTitle(String title) 设置Marker 覆盖物的标题。
void setObject(Object object) 设置Marker覆盖物的附加信息对象。
  • InfoWindow的方法
类型 方法 说明
boolean isInfoWindowEnable() 获取Marker覆盖物是否允许InfoWindow显示,
可以通过 Marker.setInfoWindowEnable(boolean) 进行设置
void setInfoWindowEnable(boolean enabled) 设置Marker覆盖物的InfoWindow是否允许显示,默认为true。
设置为false之后, 调用Marker.showInfoWindow() 将不会生效
boolean isInfoWindowShown() 返回Marker覆盖物的信息窗口是否显示,true: 显示,false: 不显示。
void showInfoWindow() 显示 Marker 覆盖物的信息窗口。
void hideInfoWindow() 隐藏Marker覆盖物的信息窗口。

AMap类

类型 方法 说明
void setOnInfoWindowClickListener(AMap.OnInfoWindowClickListener listener) 设置marker的信息窗口点击事件监听接口。

AMap.OnInfoWindowClickListener 接口

public interface OnInfoWindowClickListener {
    void onInfoWindowClick(Marker marker);
}

自定义样式(视图)

AMap 类

类型 方法 说明
void setInfoWindowAdapter(AMap.InfoWindowAdapter adapter) 设置marker的信息窗口定制接口。

AMap.ImageInfoWindowAdapter 接口

用来定制Marker的信息窗口。

类型 方法 说明
android.view.View getInfoWindow(Marker marker) 定制展示marker信息的View。(注:可自定义背景
android.view.View getInfoContents(Marker marker) 定制展示marker信息的View。(注:使用默认背景
public interface InfoWindowAdapter {
    // 如果返回的View不为空且View的background不为null,则直接使用它来展示marker的信息。
    // 如果backgound为null,SDK内部会给这个View设置一个默认的background。
    // 如果这个方法返回null,内容将会从getInfoContents(Marker)方法获取。
    View getInfoWindow(Marker marker);

    // 如果返回的View不为空且View的background不为null,则直接使用它来展示marker的信息。
    // 如果backgound为null,SDK内部会给这个View设置一个默认的background。
    // 如果这个方法返回null,将使用内置的一个默认的View来展示marker的信息。
    View getInfoContents(Marker marker);
}

触发机制

  • 默认情况下,当单击某个marker时,如果该marker的Title和Snippet不为空,则会触发getInfoWindow和getInfoContents回调。
  • 另外,通过调用Marker.showInfoWindow()同样可以触发上面两个回调。

返回null的处理逻辑

  • 自5.2.1开始,如果getInfoWindow(Marker) 和 getInfoContents(Marker) 均返回null,将不展示InfoWindow的信息

自定义样式(Image)

说明:此方法官方指南未介绍,来自参考手册。(未做验证)

AMap.ImageInfoWindowAdapter 接口

image.png

用途:

用来实现marker与对应InfoWindow同步移动。

默认情况下,InfoWindow是一个View, 拖动地图的时候由于View 布局较慢,会有延迟的效果

为了解决此问题,新增AMap.ImageInfoWindowAdapter, InfoWindow会被转为图片,拖动地图时会跟随Marker

注意

使用ImageInfoWindowAdapter后InfoWindow作为View本身的功能被减弱,比如动态更新图片,播放Gif图片等等均无法使用。

如果想要动态的去更新infowindow内容,请务必仔细看看此接口的更新机制

更新机制

设置此接口返回值之后,会定期(默认周期无穷大)调用一个 getInfoWindow(Marker)将View转换为图片

由于将View转成图片会比较耗时,不能一直调用,而设置时间间隔可以减少一定的耗时。

调用Marker.showInfoWindow() 也可以触发调用 AMap.InfoWindowAdapter.getInfoWindow(Marker) 并将View转换为图片。

类型 方法 说明
long getInfoWindowUpdateTime() 自定义整个信息窗口属性间隔时间。单位为 ms

方法说明

  • 如果返回值 小于或等于 0,则认为是无穷大。
  • 如果返回值 (0,100] , 则认为是100(如果频繁将View转成图片,内存抖动会很严重,建议这个值不要太低)。
  • 如果这个想实现更小的时间间隔或者不想受这个接口约束,可以保持返回默认值,并自行设置计时器。

示例

界面布局

1-布局.png
  • 布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="MapInfoWindowActivity">

    <com.amap.api.maps.MapView
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/bottomView"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.appcompat.widget.LinearLayoutCompat
        android:id="@+id/bottomView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/map">

        <RadioGroup
            android:id="@+id/RadioGroup"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/background_dark"
            android:gravity="center_horizontal"
            android:orientation="horizontal"
            android:paddingHorizontal="10dp">

            <RadioButton
                android:id="@+id/simpleMode"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:checked="true"
                android:onClick="setMarkerFlag"
                android:text="简单"
                android:textColor="@color/white"
                android:textStyle="bold" />

            <RadioButton
                android:id="@+id/adapter_window_mode"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:onClick="setMarkerFlag"
                android:text="适配器(窗口)"
                android:textColor="@color/white"
                android:textStyle="bold" />

            <RadioButton
                android:id="@+id/adapter_content_mode"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:onClick="setMarkerFlag"
                android:text="适配器(内容)"
                android:textColor="@color/white"
                android:textStyle="bold" />

        </RadioGroup>

    </androidx.appcompat.widget.LinearLayoutCompat>
</androidx.constraintlayout.widget.ConstraintLayout>

MapInfoWindow类

  • 以下是MapInfoWIndows部分代码

常量

public static final String SIMPlE_MODE = "SimpleMode";
public static final String ADAPTER_WINDOW_MODE = "AdapterWindowMode";
public static final String ADAPTER_CONTENT_MODE = "AdapterContentMode";

成员变量

// 覆盖物列表
List<BaseOverlay> overlays = new ArrayList<>();
// 选中的状态
String selectedFlag = SIMPlE_MODE;
// 气泡图标
ArrayList<BitmapDescriptor> bitmaps = new ArrayList<>();

初始化

int[] drawableIds = BubbleIcons.Number;
for (int drawableId : drawableIds) {
    BitmapDescriptor bitmap = BitmapDescriptorFactory.fromResource(drawableId);
    bitmaps.add(bitmap);
}
initEvent();
private void initEvent() {
    // 设置marker的信息窗口点击事件监听接口。
    map.setOnInfoWindowClickListener(new AMap.OnInfoWindowClickListener() {
        @Override
        public void onInfoWindowClick(Marker marker) {
            // 隐藏Marker覆盖物的信息窗口。
            marker.hideInfoWindow();
        }
    });
}

创建与移除覆盖物

public void addMarkers() {
    // 构造大量坐标数据
    List<LatLng> points = new ArrayList<>();
    points.add(new LatLng(39.97923, 116.357428));
    points.add(new LatLng(39.94923, 116.397428));
    points.add(new LatLng(39.97923, 116.437428));
    points.add(new LatLng(39.92353, 116.490705));
    points.add(new LatLng(40.023537, 116.289429));
    points.add(new LatLng(40.022211, 116.406137));

    // 创建OverlayOptions的集合
    ArrayList<MarkerOptions> optionsList = new ArrayList<>();
    for (int i = 0; i < points.size(); ++i) {
        // 创建OverlayOptions属性
        MarkerOptions option = new MarkerOptions()
                .position(points.get(i))
                .icon(bitmaps.get(i))
                .title("标题" + (i + 1))
                .snippet("详细信息" + (i + 1));
        // 将OverlayOptions添加到list
        optionsList.add(option);
    }

    boolean moveToCenter = true;
    // 在地图上添一组图片标记(marker)对象,
    // 并设置是否改变地图状态以至于所有的marker对象都在当前地图可视区域范围内显示。
    ArrayList<Marker> newOverlays = map.addMarkers(optionsList, moveToCenter);
    overlays.addAll(newOverlays);
}

public void removeOverlay() {
    // 从地图上删除所有的覆盖物(marker,circle,polyline 等对象),
    // 但myLocationOverlay(内置定位覆盖物)除外。
//        boolean isKeepMyLocationOverlay = true;
//        map.clear(isKeepMyLocationOverlay);

    for (BaseOverlay overlay : overlays) {
        if (overlay instanceof Marker) {
            Marker marker = (Marker) overlay;
            marker.hideInfoWindow();
        }
    }
    overlays.clear();
}

设置属性

public void setFlag(String flag) {
    selectedFlag = flag;

    switch (selectedFlag) {
    case SIMPlE_MODE:
        map.setInfoWindowAdapter(null);
        break;
    case ADAPTER_WINDOW_MODE:
        map.setInfoWindowAdapter(new WindowModeAdapter());
        break;
    case ADAPTER_CONTENT_MODE:
        map.setInfoWindowAdapter(new ContentModeAdapter());
        break;
    }
}

说明:自定义样式参考官方Demo,WindowModeAdapterContentModeAdapter为自定义两个适配器。代码和布局见附录。

自定义样式

  • WindowModeAdapter

布局custom_info_window.xml (background+image+tItle+snippet)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@drawable/custom_info_bubble"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/badge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="5dp" />

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

        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:ellipsize="end"
            android:singleLine="true"
            android:textColor="#ff000000"
            android:textSize="14dp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/snippet"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:singleLine="true"
            android:textColor="#ff7f7f7f"
            android:textSize="14dp" />
    </LinearLayout>

</LinearLayout>

WindowModeAdapter

private class WindowModeAdapter implements AMap.InfoWindowAdapter {
    @Override
    public View getInfoWindow(Marker marker) {
        // 加载自定义布局文件作为InfoWindow的样式
        View view = LayoutInflater.from(context).inflate(R.layout.custom_info_window, null);
        render(marker, view);
        return view;
    }

    @Override
    public View getInfoContents(Marker marker) {
        return null;
    }
}
  • ContentModeAdapter

布局custom_info_contents.xml (image+tItle+snippet)

<?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="horizontal">

    <ImageView
        android:id="@+id/badge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="5dp"
        android:adjustViewBounds="true" />

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

        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:ellipsize="end"
            android:singleLine="true"
            android:textColor="#ff000000"
            android:textSize="14dp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/snippet"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:singleLine="true"
            android:textColor="#ff7f7f7f"
            android:textSize="14dp" />
    </LinearLayout>

</LinearLayout>

ContentModeAdapter

private class ContentModeAdapter implements AMap.InfoWindowAdapter {
    @Override
    public View getInfoWindow(Marker marker) {
        return null;
    }

    @Override
    public View getInfoContents(Marker marker) {
        // 加载自定义布局文件作为InfoWindow的样式
        View view = LayoutInflater.from(context).inflate(R.layout.custom_info_contents, null);
        render(marker, view);
        return view;
    }
}
  • render方法(设置image+title+snippet)
private void render(Marker marker, View view) {
    ImageView imageView = view.findViewById(R.id.badge);
    imageView.setImageResource(android.R.drawable.ic_menu_gallery);

    String title = marker.getTitle();
    TextView titleUi = view.findViewById(R.id.title);
    if (title != null) {
        SpannableString titleText = new SpannableString(title);
        titleText.setSpan(new ForegroundColorSpan(Color.RED), 0, titleText.length(), 0);
        titleUi.setTextSize(15);
        titleUi.setText(titleText);
    } else {
        titleUi.setText("");
    }

    String snippet = marker.getSnippet();
    TextView snippetUi = view.findViewById(R.id.snippet);
    if (snippet != null) {
        SpannableString snippetText = new SpannableString(snippet);
        snippetText.setSpan(new ForegroundColorSpan(Color.GREEN), 0, snippetText.length(), 0);
        snippetUi.setTextSize(20);
        snippetUi.setText(snippetText);
    } else {
        snippetUi.setText("");
    }
}

加载与移除地图

public void onMapLoaded() {
    addMarkers();
    setFlag(SIMPlE_MODE);
}

public void onMapDestroy() {
    removeOverlay();

    for (BitmapDescriptor bitmap : bitmaps) {
        bitmap.recycle();
    }
    bitmaps = null;
}

MapInfoWindowActivity 类

  • 以下是MapInfoWindowActivity类部分代码

控件响应事件

public void setMarkerFlag(View view) {
    boolean checked = ((RadioButton) view).isChecked();
    if (!checked)
        return;

    int id = view.getId();
    String flag;
    if (id == R.id.simpleMode)
        flag = MapInfoWindow.SIMPlE_MODE;
    else if (id == R.id.adapter_window_mode)
        flag = MapInfoWindow.ADAPTER_WINDOW_MODE;
    else if (id == R.id.adapter_content_mode)
        flag = MapInfoWindow.ADAPTER_CONTENT_MODE;
    else
        return;

    mapInfoWindow.setFlag(flag);
}

运行效果图

简单 适配器(窗口) 适配器(内容)
2-效果图1.png
2-效果图2.png
2-效果图3.png
默认样式 自定义样式1 自定义样式2
  • 自定义样式1:background+image+title+snippet
  • 自定义样式2:image+title+snippet
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,271评论 6 524
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,533评论 3 405
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,580评论 0 370
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,203评论 1 303
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,204评论 6 401
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,664评论 1 316
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,014评论 3 431
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,991评论 0 280
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,536评论 1 326
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,558评论 3 347
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,678评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,267评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,997评论 3 341
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,429评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,580评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,259评论 3 382
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,744评论 2 366

推荐阅读更多精彩内容