如何使用Android地图API v2创建自定义形状的位图标记

191

我正在开发一款 Android 应用程序,其中使用了 Google Map API v2。我需要在地图上显示用户位置和定制标记。

每个标记将显示来自URL的用户图片。必须以异步模式从服务器下载图像。请参见附加屏幕截图作为示例。

如何向标记添加图像和自定义信息?

输入图像描述


这里有非常好的解决方案:http://www.nasc.fr/android/android-using-layout-as-custom-marker-on-google-map-api/ - Shirish Herwade
https://developers.google.com/maps/documentation/android-api/utility/marker-clustering#info-window - Google已经在maps-utils库中实现了此功能。 - Duna
你可以尝试这个 https://dev59.com/OJrga4cB1Zd3GeqPnHcg#48005956 - Ketan Ramani
返回翻译后的文本:https://dev59.com/RVsW5IYBdhLWcg3wyJt3#69883361 - Kishan Solanki
4个回答

219
Google Maps API v2演示中,有一个MarkerDemoActivity类,你可以看到如何将自定义图像设置为GoogleMap。
// Uses a custom icon.
mSydney = mMap.addMarker(new MarkerOptions()
    .position(SYDNEY)
    .title("Sydney")
    .snippet("Population: 4,627,300")
    .icon(BitmapDescriptorFactory.fromResource(R.drawable.arrow)));

作为只是用一张图片替换标记的方式,你可能想要使用Canvas来绘制更复杂和更花哨的东西:
Bitmap.Config conf = Bitmap.Config.ARGB_8888;
Bitmap bmp = Bitmap.createBitmap(80, 80, conf);
Canvas canvas1 = new Canvas(bmp);

// paint defines the text color, stroke width and size
Paint color = new Paint();
color.setTextSize(35);
color.setColor(Color.BLACK);

// modify canvas
canvas1.drawBitmap(BitmapFactory.decodeResource(getResources(),
    R.drawable.user_picture_image), 0,0, color);
canvas1.drawText("User Name!", 30, 40, color);

// add marker to Map
mMap.addMarker(new MarkerOptions()
    .position(USER_POSITION)
    .icon(BitmapDescriptorFactory.fromBitmap(bmp))
    // Specifies the anchor to be at a particular point in the marker image.
    .anchor(0.5f, 1));

这将把Canvas canvas1绘制到GoogleMap mMap上。代码本身应该(大部分)可以解释,有很多教程可以教你如何绘制Canvas。你可以从Android开发者页面的Canvas和Drawables开始学习。
现在你还想从URL下载一张图片。
URL url = new URL(user_image_url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();   
conn.setDoInput(true);   
conn.connect();     
InputStream is = conn.getInputStream();
bmImg = BitmapFactory.decodeStream(is); 

你必须从后台线程下载图像(可以使用AsyncTaskVolleyRxJava)。
之后,你可以用下载的图像bmImg替换BitmapFactory.decodeResource(getResources(), R.drawable.user_picture_image)

22
回答非常出色,除了URL部分。您将在主应用程序线程上下载该图像,这是一个极为糟糕的想法。实际上,由于此原因,您的代码将在Android 4.0+上直接崩溃。这里使用“可能”这个词太过弱化了——您必须在后台线程中下载图像,以避免UI卡顿,更不用说NetworkOnMainThreadException了。 - CommonsWare
1
@lambda k 我们使用后台线程下载图像,但有一件事我不太明白,就是当我们从 Web 服务获取纬度、经度和 URL 时,如何快速将 URL 转换为位图。 - Rishi Gautam
3
这个方案可行但存在性能问题,而且没有缓存、处理内存问题等功能。建议使用谷歌的Volley或Square的Picasso框架进行更高效的下载。 - Patrick Jackson
1
它在Lolipop上工作吗?我在我的应用程序中实现了类似的功能,从URL加载标记图像,在所有操作系统版本上都可以正常工作,但在Lolipop上无法呈现下载的图标。有人有想法吗? - Vinay
这里唯一的“问题”是它必须先下载图像来构建标记位图,这意味着它不能一次性显示所有标记,而是在下载下一个图像时按顺序显示。 - Teo Inke
显示剩余7条评论

104

另一种更简单的解决方案是创建自定义标记布局并将其转换为位图,这也是我使用的方法。

view_custom_marker.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/custom_marker_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/marker_mask">

    <ImageView
        android:id="@+id/profile_image"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_gravity="center_horizontal"
        android:contentDescription="@null"
        android:src="@drawable/avatar" />
</FrameLayout>

使用下面的代码将此视图转换为位图。
 private Bitmap getMarkerBitmapFromView(@DrawableRes int resId) {

        View customMarkerView = ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.view_custom_marker, null);
        ImageView markerImageView = (ImageView) customMarkerView.findViewById(R.id.profile_image);
        markerImageView.setImageResource(resId);
        customMarkerView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
        customMarkerView.layout(0, 0, customMarkerView.getMeasuredWidth(), customMarkerView.getMeasuredHeight());
        customMarkerView.buildDrawingCache();
        Bitmap returnedBitmap = Bitmap.createBitmap(customMarkerView.getMeasuredWidth(), customMarkerView.getMeasuredHeight(),
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(returnedBitmap);
        canvas.drawColor(Color.WHITE, PorterDuff.Mode.SRC_IN);
        Drawable drawable = customMarkerView.getBackground();
        if (drawable != null)
            drawable.draw(canvas);
        customMarkerView.draw(canvas);
        return returnedBitmap;
    }

在地图准备就绪的回调函数中添加自定义标记。
@Override
public void onMapReady(GoogleMap googleMap) {
    Log.d(TAG, "onMapReady() called with");
    mGoogleMap = googleMap;
    MapsInitializer.initialize(this);
    addCustomMarker();
}
private void addCustomMarker() {
    Log.d(TAG, "addCustomMarker()");
    if (mGoogleMap == null) {
        return;
    }

    // adding a marker on map with image from  drawable
   mGoogleMap.addMarker(new MarkerOptions()
            .position(mDummyLatLng)
            .icon(BitmapDescriptorFactory.fromBitmap(getMarkerBitmapFromView(R.drawable.avatar))));
}

@dit 谢谢你指出来,我已经更新了我的答案。实际上它是customMarkerView。 - waleedsarwar86
1
我们能否使用Glide从URL加载图像? - Prashant Gosai
1
@Prashant Gosai,你需要添加一个回调函数,当图像下载完成后,你可以从充气布局中获取位图。 - Hampel Előd
1
这个答案真的很棒,非常有创意,让我解决了一个烦人的问题。 - Beeeeeeer
@waleedsarwar86 或许你可以帮忙解决这个相关问题:https://stackoverflow.com/questions/69108723/google-maps-custom-markers-overlap-issue? - Oleh Liskovych
显示剩余2条评论

16

希望我分享的解决方案还不算太晚。在此之前,你可以按照Android Developer documentation中所述的教程进行操作。为了实现这一点,你需要使用带有defaultRenderer的群集管理器。

  1. 创建一个实现ClusterItem的对象

public class SampleJob implements ClusterItem {

private double latitude;
private double longitude;

//Create constructor, getter and setter here

@Override
public LatLng getPosition() {
    return new LatLng(latitude, longitude);
}
创建一个默认的渲染器类。这个类会完成所有的工作(用自己的样式填充自定义标记/聚合)。我正在使用Universal Image Loader来下载和缓存图像。
public class JobRenderer extends DefaultClusterRenderer< SampleJob > {

private final IconGenerator iconGenerator;
private final IconGenerator clusterIconGenerator;
private final ImageView imageView;
private final ImageView clusterImageView;
private final int markerWidth;
private final int markerHeight;
private final String TAG = "ClusterRenderer";
private DisplayImageOptions options;


public JobRenderer(Context context, GoogleMap map, ClusterManager<SampleJob> clusterManager) {
    super(context, map, clusterManager);

    // initialize cluster icon generator
    clusterIconGenerator = new IconGenerator(context.getApplicationContext());
    View clusterView = LayoutInflater.from(context).inflate(R.layout.multi_profile, null);
    clusterIconGenerator.setContentView(clusterView);
    clusterImageView = (ImageView) clusterView.findViewById(R.id.image);

    // initialize cluster item icon generator
    iconGenerator = new IconGenerator(context.getApplicationContext());
    imageView = new ImageView(context.getApplicationContext());
    markerWidth = (int) context.getResources().getDimension(R.dimen.custom_profile_image);
    markerHeight = (int) context.getResources().getDimension(R.dimen.custom_profile_image);
    imageView.setLayoutParams(new ViewGroup.LayoutParams(markerWidth, markerHeight));
    int padding = (int) context.getResources().getDimension(R.dimen.custom_profile_padding);
    imageView.setPadding(padding, padding, padding, padding);
    iconGenerator.setContentView(imageView);

    options = new DisplayImageOptions.Builder()
            .showImageOnLoading(R.drawable.circle_icon_logo)
            .showImageForEmptyUri(R.drawable.circle_icon_logo)
            .showImageOnFail(R.drawable.circle_icon_logo)
            .cacheInMemory(false)
            .cacheOnDisk(true)
            .considerExifParams(true)
            .bitmapConfig(Bitmap.Config.RGB_565)
            .build();
}

@Override
protected void onBeforeClusterItemRendered(SampleJob job, MarkerOptions markerOptions) {


    ImageLoader.getInstance().displayImage(job.getJobImageURL(), imageView, options);
    Bitmap icon = iconGenerator.makeIcon(job.getName());
    markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon)).title(job.getName());


}

@Override
protected void onBeforeClusterRendered(Cluster<SampleJob> cluster, MarkerOptions markerOptions) {

    Iterator<Job> iterator = cluster.getItems().iterator();
    ImageLoader.getInstance().displayImage(iterator.next().getJobImageURL(), clusterImageView, options);
    Bitmap icon = clusterIconGenerator.makeIcon(iterator.next().getName());
    markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
}

@Override
protected boolean shouldRenderAsCluster(Cluster cluster) {
    return cluster.getSize() > 1;
}
在您的Activity/Fragment类中应用集群管理器。
public class SampleActivity extends AppCompatActivity implements OnMapReadyCallback {

private ClusterManager<SampleJob> mClusterManager;
private GoogleMap mMap;
private ArrayList<SampleJob> jobs = new ArrayList<SampleJob>();

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

    SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
            .findFragmentById(R.id.map);
    mapFragment.getMapAsync(this);
}


@Override
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;
    mMap.getUiSettings().setMapToolbarEnabled(true);
    mClusterManager = new ClusterManager<SampleJob>(this, mMap);
    mClusterManager.setRenderer(new JobRenderer(this, mMap, mClusterManager));
    mMap.setOnCameraChangeListener(mClusterManager);
    mMap.setOnMarkerClickListener(mClusterManager);

    //Assume that we already have arraylist of jobs


    for(final SampleJob job: jobs){
        mClusterManager.addItem(job);
    }
    mClusterManager.cluster();
}
  • 结果

  • 结果


    你能列出这种方法的依赖关系吗?很多代码库都没有被考虑进去。 - Jonathan Dunn
    你所引用的'Job'类是否来自于'com.evernote:android-job:1.2.1'库? - Jonathan Dunn
    @JonDunn 在这个示例中,“Job”是自定义类。我使用的依赖关系是:compile 'com.google.maps.android:android-maps-utils:0.5+。更多解释可以在这里找到:https://developers.google.com/maps/documentation/android-api/utility/marker-clustering#introduction - Amad Yus

    2

    根据Lambda的回答,我已经将某些内容更接近要求。

    boolean imageCreated = false;
    
    Bitmap bmp = null;
    Marker currentLocationMarker;
    private void doSomeCustomizationForMarker(LatLng currentLocation) {
        if (!imageCreated) {
            imageCreated = true;
            Bitmap.Config conf = Bitmap.Config.ARGB_8888;
            bmp = Bitmap.createBitmap(400, 400, conf);
            Canvas canvas1 = new Canvas(bmp);
    
            Paint color = new Paint();
            color.setTextSize(30);
            color.setColor(Color.WHITE);
    
            BitmapFactory.Options opt = new BitmapFactory.Options();
            opt.inMutable = true;
    
            Bitmap imageBitmap=BitmapFactory.decodeResource(getResources(),
                    R.drawable.messi,opt);
            Bitmap resized = Bitmap.createScaledBitmap(imageBitmap, 320, 320, true);
            canvas1.drawBitmap(resized, 40, 40, color);
    
            canvas1.drawText("Le Messi", 30, 40, color);
    
            currentLocationMarker = mMap.addMarker(new MarkerOptions().position(currentLocation)
                    .icon(BitmapDescriptorFactory.fromBitmap(bmp))
                    // Specifies the anchor to be at a particular point in the marker image.
                    .anchor(0.5f, 1));
        } else {
            currentLocationMarker.setPosition(currentLocation);
        }
    
    }
    

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