3.在地图上标记位置

3.1 问题

除了将指定的位置显示在地图的中心,应用程序还需要在该位置上加上标记,以使其更加醒目。

3.2 解决方案

(API Level 9)
向地图添加Marker对象以及Circle和Polygon等形状元素。Marker对象是通过图标定义的交互式对象,该图标显示在给定位置。该位置可以是固定的,也可以设置Marker为可由用户拖动到他们希望的任意一点。每个Marker还可以响应触摸事件,如点击和长按。此外,可以为Marker提供包括标题的元数据和文本片段,当点击标记时会在弹出信息窗口中显示这些信息。这些窗口自身也可以定制显示。
Maps v2还支持绘制离散形状元素。这些元素在本质上是不可交互的,但我们会看到,可以轻松地添加与形状交互的功能。此功能也可以用于在地图上使用Polyline形状绘制路线,其不像其他选项一样会尝试绘制闭合的、填充的形状。

要点:
Google Maps v2是作为Google Play Services库的一部分进行分发的,它在任意平台级别都不是原生SDK的一部分。然而,目标平台为API Level 9或以后版本的应用程序以及Google Play体系内的设备都可以使用此绘图库。

3.3 实现机制

显示上一节的地图应用程序,其中使用标记添加了一些感兴趣的点。

以下两段代码清单显示了新的Activity示例,其中向地图添加了一些标记。XML布局与前一节中的相同,因此我们不会花费时间再次剖析其组成部分,只是为了完整性而在此添加此布局。

res/layout/main.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" >
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:text="Map Of Your Location" />
    <RadioGroup
        android:id="@+id/group_maptype"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <RadioButton
            android:id="@+id/type_normal"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Normal Map" />
        <RadioButton
            android:id="@+id/type_satellite"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Satellite Map" />
    </RadioGroup>
    
    <fragment
        class="com.google.android.gms.maps.SupportMapFragment" 
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

显示带有标记的地图的Activity

public class MarkerMapActivity extends FragmentActivity implements
        RadioGroup.OnCheckedChangeListener,
        GoogleMap.OnMarkerClickListener,
        GoogleMap.OnMarkerDragListener,
        GoogleMap.OnInfoWindowClickListener,
        GoogleMap.InfoWindowAdapter {
    private static final String TAG = "AndroidRecipes";

    private SupportMapFragment mMapFragment;
    private GoogleMap mMap;

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

        //检查play services是否激活且为最新版本
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
        switch (resultCode) {
            case ConnectionResult.SUCCESS:
                Log.d(TAG, "Google Play Services is ready to go!");
                break;
            default:
                showPlayServicesError(resultCode);
                return;
        }

        mMapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mMap = mMapFragment.getMap();

        // 监控与标记元素的交互
        mMap.setOnMarkerClickListener(this);
        mMap.setOnMarkerDragListener(this);
        // 设置应用程序以服务信息窗口的视图
        mMap.setInfoWindowAdapter(this);
        // 监控信息窗口上的点击事件
        mMap.setOnInfoWindowClickListener(this);

        // Google 总部 ( 37.427,-122.099)
        Marker marker = mMap.addMarker(new MarkerOptions()
                .position(new LatLng(37.4218, -122.0840))
                .title("Google HQ")
                // 将来自应用程序的图像资源显示为标记
                .icon(BitmapDescriptorFactory
                        .fromResource(R.drawable.logo))
                //降低透明度
                .alpha(0.6f));
        //使此标记在地图上可拖动
        marker.setDraggable(true);
        
        // 减去 0.01 度
        mMap.addMarker(new MarkerOptions()
                .position(new LatLng(37.4118, -122.0740))
                .title("Neighbor #1")
                .snippet("Best Restaurant in Town")
                // 以默认颜色显示默认标记
                .icon(BitmapDescriptorFactory.defaultMarker()));

        // 增加 0.01 度
        mMap.addMarker(new MarkerOptions()
                .position(new LatLng(37.4318, -122.0940))
                .title("Neighbor #2")
                .snippet("Worst Restaurant in Town")
                // 使用浅蓝色显示默认标记
                .icon(BitmapDescriptorFactory
                        .defaultMarker(BitmapDescriptorFactory.HUE_AZURE)));

        // 居中地图并同时缩放
        LatLng mapCenter = new LatLng(37.4218, -122.0840);
        CameraUpdate newCamera = CameraUpdateFactory
                .newLatLngZoom(mapCenter, 13);
        mMap.moveCamera(newCamera);

        // 连接地图类型选择器UI
        RadioGroup typeSelect = (RadioGroup) findViewById(R.id.group_maptype);
        typeSelect.setOnCheckedChangeListener(this);
        typeSelect.check(R.id.type_normal);
    }

    /** OnCheckedChangeListener方法 */

    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        switch (checkedId) {
            case R.id.type_satellite:
                mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
                break;
            case R.id.type_normal:
            default:
                mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
                break;
        }
    }

    /** OnMarkerClickListener方法 */

    @Override
    public boolean onMarkerClick(Marker marker) {
        // 返回 true 以禁用自动居中和信息弹出窗口
        return false;
    }

    /** OnMarkerDragListener 方法 */

    @Override
    public void onMarkerDrag(Marker marker) {
        // 在标记移动时执行某些操作
    }

    @Override
    public void onMarkerDragEnd(Marker marker) {
        Log.i("MarkerTest", "Drag " + marker.getTitle()
                + " to " + marker.getPosition());
    }

    @Override
    public void onMarkerDragStart(Marker marker) {
        Log.d("MarkerTest", "Drag " + marker.getTitle()
                + " from " + marker.getPosition());
    }
    
    /** OnInfoWindowClickListener 方法 */

    @Override
    public void onInfoWindowClick(Marker marker) {
        // 操作选择事件,在此仅是关闭窗口
        marker.hideInfoWindow();
    }

    /** InfoWindowAdapter 方法 */

    /*
     * 返回将放在标准信息窗口内的内容视图
     * 仅在getInfoWindow() 返回null时调用
     */
    @Override
    public View getInfoContents(Marker marker) {
        //在此改为尝试返回 createInfoView()  
        return null;
    }

    /*
     * 返回整个待显示的信息窗口
     */
    @Override
    public View getInfoWindow(Marker marker) {
        View content = createInfoView(marker);
        content.setBackgroundResource(R.drawable.background);
        return content;
    }

    /*
     * 用于构造内容视图的私有辅助方法
     */
    private View createInfoView(Marker marker) {
        // 我们没有父对象用于布局,因此传递null
        View content = getLayoutInflater().inflate(
                R.layout.info_window, null);
        ImageView image = (ImageView) content
                .findViewById(R.id.image);
        TextView text = (TextView) content
                .findViewById(R.id.text);

        image.setImageResource(R.drawable.ic_launcher);
        text.setText(marker.getTitle());

        return content;
    }

    /*
     *当 Play Services缺失或缺失不对时,
     * 客户端将以对话框的形式帮助用户进行更新。
     */
    private void showPlayServicesError(int errorCode) {
        // 从Google Play services 获得错误对话框
        Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
                errorCode,
                this,
                1000 /* RequestCode */);
        // 如果Google Play services 可以提供错误对话框
        if (errorDialog != null) {
            // 为错误对话框创建新的 DialogFragment  
            SupportErrorDialogFragment errorFragment = SupportErrorDialogFragment.newInstance(errorDialog);
            // 在 DialogFragment 中显示错误对话框
            errorFragment.show(
                    getSupportFragmentManager(),
                    "Google Maps");
        }
    }
}

免责声明:
我们没有实际拜访此地图上的这些位置以了解是否有餐馆,也没有了解这些餐馆的客户评级是否符合我们在此放置的副标题!

我们向Activity添加了一些新的侦听器接口,该Activity现在设置为监控每个Marker上的点击拖动事件,并且监控通过点击Marker显示的弹出信息窗口中的点击事件。此外,我们实现了InfoWindowAdapter,它用于最终定制弹出窗口,但目前先不讨论该适配器。
将MarkerOptions实例传入GoogleMap.addMarker(),这样就可以向地图添加标记。
MarkerOptions的工作方式类似于生成器,它只是在构造函数中将想要应用的所有信息链接在一起(我们以及完成该工作)。在MarkerOptions中设置一些基本信息,如标记位置、显示图标和标题。还有一些用于修该标记显示的额外选项,如alpha()、旋转和锚点。我们选择在位于山景城(Mountain View)的Google总部添加一个标记,并且在其附近添加另外两个标记。
有许多支持方法可用于创建Marker图标,使用BitmapDescriptor对象可应用这些方法,BitmapDescriptorFactory则提供了创建所有元素的方法。对于我们的两个元素,在此选择了defaultMarker()方法,该方法创建标准的Google大头针图标进行显示。我们还可以传入几个常量之一来控制大头针图标的显示颜色。
我们对位于Google总部的标记进行了控制,使用fromResource()将其定义为应用程序中已有的图标。还可以使用单独的工厂方法应用可能位于资源目录中的图像。此外,我们将此标记设置为可由用户拖动。这意味着如果用户长按大头针图标,则会从其当前位置拾起该图标,将其拖放到地图上的任意位置。我们实现的OnMarkerDragListener提供了关于二如何放置标记的回调。
如果用户点击某个标记,标准信息窗口将显示在图标上方。该窗口显示应用于标记的标题和代码片段。我们实现了OnInfoWindowClickListener,在点击窗口时将其关闭,这不是默认行为。
注意,我们不需要实现OnMarkerClickListener来实现在此描述的行为,但我们可以重写该行为。默认情况下,信息窗口将显示,并且地图将在所选标记出居中。如果onMarkerClick()返回true,则可以禁用此行为并提供我们自己的行为。

1.定制信息窗口
为了帮助你了解如何定制在点击标记时弹出的信息窗口,接下来为窗口添加一些自定义UI(参见以下两段代码),并且修改在Activity中实现的InfoWindowAdapter方法。

res/layout/info_window.xml

扫描二维码关注公众号,回复: 4396260 查看本文章
<?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" >
    <ImageView
        android:id="@+id/image"
        android:layout_width="35dp"
        android:layout_height="35dp"
        android:layout_gravity="center_horizontal"
        android:scaleType="fitCenter" />
    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

res/drawable/background.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" 
    android:shape="rectangle">
    <corners
        android:radius="10dp"/>
    <solid
        android:color="#CCC"/>
    <padding 
        android:left="10dp"
        android:right="10dp"
        android:top="10dp"
        android:bottom="10dp"/>
</shape>

通过从getInfoContents()返回有效的视图,该视图就会用作标准窗口背景显示中的内容。从getInfoWindow()返回相同的视图,该视图会显示为没有标准组件的完全自定义的窗口。我们已将弹出窗口的创建过程抽象化到一个辅助方法中,因此可以放松尝试上诉两种方式。

2.操作形状
接下来讨论像地图添加形状元素。在下面的示例中,我们创建了名为ShapeAdapter的自定义类,该类建立圆形或矩形形状并将它们添加到地图上,用于描述地图地区。

该例也使用Google Map 的onMapClickListener验证用户何时点击某个地区进行选择。以下清单代码显示了ShapeAdapter的代码。

创建地图形状的ShapeAdapter

public class ShapeAdapter implements OnMapClickListener {

    private static final float STROKE_SELECTED = 6.0f;
    private static final float STROKE_NORMAL = 2.0f;
    /* 所绘制区域的颜色 */
    private static final int COLOR_STROKE = Color.RED;
    private static final int COLOR_FILL = Color.argb(127, 0, 0, 255);
    
    /*
     * 外部接口,用于通知侦听器基于用户点击的所选区域进行变化
     */
    public interface OnRegionSelectedListener {
        //用户选择了跟踪地区
        public void onRegionSelected(Region selectedRegion);
        //用户选择了没有地区的区域
        public void onNoRegionSelected();
    }
    
    /*
     * 地图上交互式地区的基础定义
     * 定义方法以更改显示并检查用户点击
     */
    public static abstract class Region {
        private String mRegionName;
        public Region(String regionName) {
            mRegionName = regionName;
        }
        
        public String getName() {
            return mRegionName;
        }
        //检查位置是否在此地区内
        public abstract boolean hitTest(LatLng point);
        //根据用户的选择更改地区的显示
        public abstract void setSelected(boolean isSelected);
    }
    
    /*
     *将地区绘制为圆形
     */
    private static class CircleRegion extends Region {
        private Circle mCircle;
        
        public CircleRegion(String name, Circle circle) {
            super(name);
            mCircle = circle;
        }
        
        @Override
        public boolean hitTest(LatLng point) {
            final LatLng center = mCircle.getCenter();
            float[] result = new float[1];
            Location.distanceBetween(center.latitude, center.longitude,
                    point.latitude, point.longitude,
                    result);
            
            return (result[0] < mCircle.getRadius());
        }

        @Override
        public void setSelected(boolean isSelected) {
            mCircle.setStrokeWidth(isSelected ? STROKE_SELECTED : STROKE_NORMAL);
        }
        
    }
    
    /*
     * 将地区绘制为矩形
     */
    private static class RectRegion extends Region {
        private Polygon mRect;
        private LatLngBounds mRectBounds;
        
        public RectRegion(String name, Polygon rect, LatLng southwest, LatLng northeast) {
            super(name);
            mRect = rect;
            mRectBounds = new LatLngBounds(southwest, northeast);
        }

        @Override
        public boolean hitTest(LatLng point) {
            return mRectBounds.contains(point);
        }

        @Override
        public void setSelected(boolean isSelected) {
            mRect.setStrokeWidth(isSelected ? STROKE_SELECTED : STROKE_NORMAL);
        }
    }
    
    private GoogleMap mMap;
    
    private OnRegionSelectedListener mRegionSelectedListener;
    private ArrayList<Region> mRegions;
    private Region mCurrentRegion;
    
    public ShapeAdapter(GoogleMap map) {
        //在内部跟踪地区以确认选择
        mRegions = new ArrayList<Region>();
        
        mMap = map;
        mMap.setOnMapClickListener(this);
    }

    public void setOnRegionSelectedListener(OnRegionSelectedListener listener) {
        mRegionSelectedListener = listener;
    }
    
    /*
     * 围绕给定点构造并添加新的圆形地区
     */
    public void addCircularRegion(String name, LatLng center, double radius) {
        CircleOptions options = new CircleOptions()
                .center(center)
                .radius(radius);
        //设置形状的显示属性        options.strokeWidth(STROKE_NORMAL).strokeColor(COLOR_STROKE).fillColor(COLOR_FILL);
        
        Circle c = mMap.addCircle(options);
        mRegions.add(new CircleRegion(name, c));
    }
    
    /*
     * 使用给定边界构造并添加新的矩形地区
     */
    public void addRectangularRegion(String name, LatLng southwest, LatLng northeast) {
        PolygonOptions options = new PolygonOptions().add(
                new LatLng(southwest.latitude, southwest.longitude),
                new LatLng(southwest.latitude, northeast.longitude),
                new LatLng(northeast.latitude, northeast.longitude),
                new LatLng(northeast.latitude, southwest.longitude));
//设置形状的显示属性        options.strokeWidth(STROKE_NORMAL).strokeColor(COLOR_STROKE).fillColor(COLOR_FILL);
        
        Polygon p = mMap.addPolygon(options);
        mRegions.add(new RectRegion(name, p, southwest, northeast));
    }
    
    /*
     * 处理从地图对象传入的触摸事件
     * 确定可能选择了哪个地区元素
     *如果多个地区在此地区重叠,则会选择添加的第一个地区
     */
    @Override
    public void onMapClick(LatLng point) {
        Region newSelection = null;
        //查找并选择触摸的地区
        for (Region region : mRegions) {
            if (region.hitTest(point) && newSelection == null) {
                region.setSelected(true);
                newSelection = region;
            } else {
                region.setSelected(false);
            }
        }

        if (mCurrentRegion != newSelection) {
            //通知并更新改动
            if (newSelection != null && mRegionSelectedListener != null) {
                mRegionSelectedListener.onRegionSelected(newSelection);
            } else if (mRegionSelectedListener != null) {
                mRegionSelectedListener.onNoRegionSelected();
            }
            
            mCurrentRegion = newSelection;
        }
    }

}

该类定义了名为Region的抽象类型,我们可以使用它定义形状类型之间的常见模式。首先,每个地区必须定义地图位置是否在给定地区内的逻辑,以及在选择地区时执行哪些操作。然后,为Circle和Polygon形状定义此逻辑的实现,或者用于绘制矩形。圆形地区由中心点和半径定义,而矩形地区则由其西南角和东北角的点定义。我们构造矩形的方法是使用组成该形状的4个角点坐标构造Polygon。
触摸事件将由侦听器接口的onMapClick()方法处理,而Maps库提供了作为LatLng位置的触摸位置。只需要检查中心点和触摸位置之间的距离是否大于半径,我们就可以验证这些事件在圆形地区内。Location有一个便利方法可计算两个地图点之间的直接距离。对于矩形地区,我们使用作为Maps库一部分的LayLngBounds方法,因为它直接验证给定点是在形状的内部还是外部。
对于每个触摸事件,我们遍历地区列表以查找第一个可能包含此位置的地区。如果未找到任何地区,则将所选地区设置为null。接下来,确定选择项是否已改变,并且调用自定义接口OnRegionSelectedListener的某个方法,较高级的对象可以使用该方法获得这些事件的通知。
以下清单代码显示了如何在Activity内部使用此适配器。

整合了ShapeAdapter的Activity

public class ShapeMapActivity extends FragmentActivity implements
        RadioGroup.OnCheckedChangeListener,
        ShapeAdapter.OnRegionSelectedListener {
    private static final String TAG = "AndroidRecipes";

    private SupportMapFragment mMapFragment;
    private GoogleMap mMap;

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

        //检查Google play services 是否已激活且为最新版本
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
        switch (resultCode) {
            case ConnectionResult.SUCCESS:
                Log.d(TAG, "Google Play Services is ready to go!");
                break;
            default:
                showPlayServicesError(resultCode);
                return;
        }

        mMapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mMap = mMapFragment.getMap();

        ShapeAdapter adapter = new ShapeAdapter(mMap);
        adapter.setOnRegionSelectedListener(this);

        adapter.addRectangularRegion("Google HQ",
                new LatLng(37.4168, -122.0890),
                new LatLng(37.4268, -122.0790));
        adapter.addCircularRegion("Neighbor #1",
                new LatLng(37.4118, -122.0740), 400);
        adapter.addCircularRegion("Neighbor #2",
                new LatLng(37.4318, -122.0940), 400);
        
        //居中地图并同时缩放
        LatLng mapCenter = new LatLng(37.4218,  -122.0840);
        CameraUpdate newCamera = CameraUpdateFactory.newLatLngZoom(mapCenter, 13);
        mMap.moveCamera(newCamera);
        
        //连接地图类型选择器 UI
        RadioGroup typeSelect = (RadioGroup) findViewById(R.id.group_maptype);
        typeSelect.setOnCheckedChangeListener(this);
        typeSelect.check(R.id.type_normal);
    }

    /** OnCheckedChangeListener 方法 */
    
    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        switch (checkedId) {
            case R.id.type_satellite:
                mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
                break;
            case R.id.type_normal:
            default:
                mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
                break;
        }
    }

    /** OnRegionSelectedListener 方法 */

    @Override
    public void onRegionSelected(Region selectedRegion) {
        Toast.makeText(this, selectedRegion.getName(),
                Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onNoRegionSelected() {
        Toast.makeText(this, "No Region",
                Toast.LENGTH_SHORT).show();
    }

    /*
     * 当 Play Services 缺失或版本不正确时,客户端库将显示了一个对话框,
     * 帮助用户进行更新
     */
    private void showPlayServicesError(int errorCode) {
        //获得来自 Google Play services的错误对话框
        Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
                errorCode,
                this,
                1000 /* RequestCode */);
        // 如果 Google Play services 可以提供错误对话框
        if (errorDialog != null) {
            // 为错误对话框创建新的 DialogFragment 可以提供错误对话框
            SupportErrorDialogFragment errorFragment = SupportErrorDialogFragment.newInstance(errorDialog);
            // 在 DialogFragment中显示错误对话框
            errorFragment.show(
                    getSupportFragmentManager(),
                    "Google Maps");
        }
    }
}

在此添加了与前一个示例相同的位置,但这一次使用新的ShapeAdapter将其添加为形状地区。将Google总部添加为矩形地区,而将其他两个标记添加圆形地区。当用户改变选择并影响到任何上诉地区时,则会调用onRegionSelected()或onNoRegionSelected()方法。

猜你喜欢

转载自blog.csdn.net/qq_41121204/article/details/84643361