将来自 URL 的图像添加到自定义 InfoWindow Google 地图 V2 中

18
我正在开发一个安卓应用,用户在谷歌地图上搜索餐厅。在谷歌地图上显示了他附近所有餐厅的标记。如果他点击标记,会显示一个自定义的信息窗口。我的问题是无法加载从谷歌地点返回的图片。我成功获取了图片的URL,但无法显示在信息窗口中。
信息窗口
<?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"
android:background="@color/bg_color" >

<ImageView
        android:id="@+id/place_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:focusable="false"" />

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

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

<LinearLayout 
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:background="@color/bg_color" >

    <RatingBar
         android:id="@+id/place_rating"
         style="?android:attr/ratingBarStyleSmall"
         android:numStars="5"
         android:rating="0"
         android:isIndicator="false"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dip" />

    <ImageView
        android:id="@+id/navigate_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:focusable="false"
        android:src="@drawable/navigate" />

</LinearLayout>

在创建时我有这个

mGoogleMap.setInfoWindowAdapter(new InfoWindowAdapter() {

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

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

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

                // Getting the snippet from the marker
                String snippet = arg0.getSnippet();

                // Getting the snippet from the marker
                String titlestr = arg0.getTitle();

                String cutchar1= "%#";
                String cutchar2= "%##";
                String ratingstr = snippet.substring(0,snippet.indexOf( cutchar1 ));
                String vicinitystr = snippet.substring(snippet.indexOf( cutchar1 )+2, snippet.indexOf( cutchar2 ) );
                String iconurl= snippet.substring(snippet.indexOf( cutchar2 )+3);

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

                TextView vicinity = (TextView) v.findViewById(R.id.place_vicinity);

                ImageView image = (ImageView) v.findViewById(R.id.navigate_icon);

                // Setting the latitude
                title.setText(titlestr);

                // declare RatingBar object
                RatingBar rating=(RatingBar) v.findViewById(R.id.place_rating);// create RatingBar object
                if( !(ratingstr.equals("null")) ){
                    rating.setRating(Float.parseFloat(ratingstr));
                }
                vicinity.setText(vicinitystr);                  

                final DownloadImageTask download = new DownloadImageTask((ImageView) v.findViewById(R.id.place_icon) ,arg0);
                download.execute(iconurl);
                // Returning the view containing InfoWindow contents
                return v;

            }

});

DownloadImage的代码如下:

private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
      ImageView bmImage;
      Marker marker;
      boolean refresh;

      public DownloadImageTask(final ImageView bmImage, final Marker marker) {
          this.bmImage = bmImage;
          this.marker=marker;
          this.refresh=false;
      }

     public void SetRefresh(boolean refresh ){
         this.refresh=true;

     }

    /*  @Override
      protected void onPreExecute() 
      {
          super.onPreExecute();
          bmImage.setImageBitmap(null);
      }*/

      @Override
      protected Bitmap doInBackground(String... urls) {
          String urldisplay = urls[0];
          Bitmap mIcon11 = null;
          try {
            InputStream in = new java.net.URL(urldisplay).openStream();
            mIcon11 = BitmapFactory.decodeStream(in);
          } catch (Exception e) {
              Log.e("Error", e.getMessage());
              e.printStackTrace();
          }
          return mIcon11;
      }
      @Override
      protected void onPostExecute(Bitmap result) {
          if(!refresh){
              SetRefresh(refresh);
              bmImage.setImageBitmap(result);
              marker.showInfoWindow();
          }
      }
    }

最后,当我执行代码并点击标记时,getInfoContents函数不停地执行,图标也没有出现。

为什么会发生这种情况呢?


1
可能是Maps V2 InfoWindow中的动态内容的重复问题。 - MaciejGórski
@Mixalis,你有解决方案吗?请与我分享。 - Fedy Venom
4个回答

38

我正在建立一个类似的应用程序。

首先,你的 InfoWindow 无法显示下载的图像是因为 MapFragment 将视图渲染到一个 Canvas 中,然后绘制它。在信息窗口中看到的不是你创建的视图,而是它们的“图片”或“截图”。你需要再次调用 Marker 对象上的 showInfoWindow(),这将重新渲染 Canvas,从而使你的图像可见。

但是,根据我的经验,从 URL 加载 Bitmap 然后设置它并不是最好的解决方案。Android 并不很好地处理 Bitmap。在加载多个位图后,OutOfMemoryError 异常只是时间问题,这取决于您拥有的系统内存量。

我建议使用 Picasso 库,它可以处理异步下载和缓存(在内存和磁盘中),并使实际的图像加载变成只有一行 (Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);)。 (更多信息请参见 http://square.github.io/picasso/)

前面的答案很好,但是,正如他所说的那样,那个“延迟”对我来说有点太神奇了。Picasso 有使用回调的选项,我建议使用它(我在我的应用程序中使用它)。

首先创建一个实现 Picasso 的 Callback 接口的类(它可以是您活动的内部类),并在构造函数中接收一个 Marker(这样您就可以再次调用 showInfoWindow() 来显示该标记)。

private class InfoWindowRefresher implements Callback {
   private Marker markerToRefresh;

   private InfoWindowRefresher(Marker markerToRefresh) {
        this.markerToRefresh = markerToRefresh;
    }

    @Override
    public void onSuccess() {
        markerToRefresh.showInfoWindow();
    }

    @Override
    public void onError() {}
}

信息窗口长这样:

mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {
    @Override
    public View getInfoWindow(Marker marker) {
        // inflate view window

        // set other views content

        // set image view like this:
        if (not_first_time_showing_info_window) {
            Picasso.with(ActivityClass.this).load(restaurantPictureURL).into(imgInfoWindowPicture);
        } else { // if it's the first time, load the image with the callback set
            not_first_time_showing_info_window=true;
            Picasso.with(ActivityClass.this).load(restaurantPictureURL).into(imgInfoWindowPicture,new InfoWindowRefresher(marker));
        }

        return v;
    }

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

正如您所见,回调函数非常简单。 但是,在使用此方法时,您必须小心,只能在第一次调用中使用回调函数,而不是在后续调用中使用(我只是加入了not_first_time_showing_info_window以反映这个想法...您将不得不看一下如何在程序逻辑中包含它。如果您不这样做,Picasso回调将调用showInfoWindow(),这将重新调用回调,然后回调将再次调用showInfoWindow()......嗯,您可以想象递归会发生什么。:)

主要的问题是让带有回调的Picasso仅运行一次,并在后续调用中不使用回调。


4
同样的方法,这里实现更加简洁:https://dev59.com/m4Hba4cB1Zd3GeqPNSfo#28885430。 - Chris Chen

11

我使用黑魔法(也就是设置延迟)解决了这个问题。我利用了Picasso的缓存,只是在初始加载开始几毫秒后调用showInfoWindow。

这是我的CustomWindowAdapter。

class CustomWindowAdapter implements InfoWindowAdapter{
   LayoutInflater mInflater;
   Map<Marker, String> imageStringMapMarker;
   Context context;

   public CustomWindowAdapter(LayoutInflater i,  Map<Marker, String> imageStringMapMarker2, Context context ){
      mInflater = i;
      imageStringMapMarker = imageStringMapMarker2;
   }

   @Override
   public View getInfoContents(final Marker marker) {

      View v = mInflater.inflate(R.layout.custom_info_window, null);

      ImageView ivThumbnail = (ImageView) v.findViewById(R.id.ivThumbnail);
      String urlImage = imageStringMapMarker.get(marker).toString();
      Picasso.with(context).load(Uri.parse(urlImage)).resize(250,250).into(ivThumbnail);

      return v;

   }

   @Override
   public View getInfoWindow(Marker marker) {
    // TODO Auto-generated method stub
     return null;
   }
}

这是在我的主活动中实现延迟的调用信息窗口的方法。

myMap.setInfoWindowAdapter(new CustomWindowAdapter(this.getLayoutInflater(),
imageStringMapMarker, getApplicationContext()));
myMap.setOnMarkerClickListener(new OnMarkerClickListener() {

    @Override
    public boolean onMarkerClick(final Marker mark) {


    mark.showInfoWindow();

        final Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mark.showInfoWindow();

            }
        }, 200);

        return true;
    }
});

你的帖子已经发布很久了,但是你还记得为什么在“onMarkerClick”函数中两次调用了“mark.showInfoWindow();”吗? - Ignacio Rubio
嗨,伊格纳西奥, 我正在重写应用程序,其中使用了这段代码。我的情况是,在向用户显示图片之前,我需要下载它。我希望用户有一种感觉,即第一个窗口警报是为了呈现占位符窗口,以便用户期望在几秒钟内出现某些内容。处理程序是在平均加载特定实例的照片所需的时间后显示照片。 我承认这实际上是一种非常糟糕的做法。但是,我需要快速完成项目。我现在正在支付我的技术债务。 - Michael Alan Huff
太棒了!非常有效!你救了我的一天。 - Gaurav Arora

7
无论您从 getInfoContents() 返回什么,在那一时刻,它都会被转换成一个 Bitmap 并用于显示结果。只有在下载完成之后,Bitmap 已经被创建和使用时,才会显示图像。
在调用 getInfoContents() 之前,您需要先下载图片。

实际上,我想在下载完成后刷新Infowindow。像这样https://code.google.com/p/gmaps-api-issues/issues/detail?id=4645 为此我使用var refrese。我如何等待下载完成? 我认为通过AsyncTask下载是并行进行的。 - Mixalis
1
@Mixalis:虽然您的代码将尝试刷新信息窗口,但随后您立即又开始了另一个下载任务。您在太晚的时候填充了ImageViewBitmap已经被创建),并且当您刷新信息窗口时,您将在填充另一个布局实例时创建另一个不同的ImageView。您需要缓存Bitmap,在再次出现信息窗口时使用它,而不是启动另一个任务。处理这个问题的一种方法是使用缓存图像加载库--Picasso可能适用。 - CommonsWare
@Mixalis:但是感谢您指出这个问题,因为那里描述的技术非常有帮助。我会尝试在明天编写一个示例应用程序,假设我能想到一个好的相关图像来源... :-) - CommonsWare
谢谢。我在等着...如果示例被发布,能够提供链接将会很好。 - Mixalis
@Mixalis:如果到周二还没有给你发链接,请在这里回复提醒我。 - CommonsWare
6
@Mixalis: 给你链接:https://github.com/commonsguy/cw-omnibus/tree/master/MapsV2/ImagePopups - CommonsWare

1

我这样做,同时参考@Daniel Gray的回答

if (userImg.getDrawable() == null) {
  Picasso.with(ctx).load(UtilitiesApp.urlServer + user.getImgUrl())
      .error(R.drawable.logo)
      .into(userImg, new InfoWindowRefresher(marker));
} else {
  Picasso.with(ctx).load(UtilitiesApp.urlServer + user.getImgUrl())
      .error(R.drawable.logo)
      .into(userImg);
}


public class InfoWindowRefresher implements Callback {
  private Marker markerToRefresh;

  public InfoWindowRefresher(Marker markerToRefresh) {
    this.markerToRefresh = markerToRefresh;
  }

  @Override
  public void onSuccess() {
    markerToRefresh.showInfoWindow();
  }

  @Override
  public void onError() {}
}

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