Android - MapView包含在ListView中

11

目前我正在尝试在ListView中放置一个MapView。有人成功过吗?这是否可行?这是我的代码:

            ListView myList = (ListView) findViewById(android.R.id.list);
        List<Map<String, Object>> groupData = new ArrayList<Map<String, Object>>();

        Map<String, Object> curGroupMap = new HashMap<String, Object>();
        groupData.add(curGroupMap);
        curGroupMap.put("ICON", R.drawable.back_icon);
        curGroupMap.put("NAME","Go Back");
        curGroupMap.put("VALUE","By clicking here");

        Iterator it = data.entrySet().iterator();
        while (it.hasNext()) 
        {
            //Get the key name and value for it
            Map.Entry pair = (Map.Entry)it.next();
            String keyName = (String) pair.getKey();
            String value = pair.getValue().toString();

            if (value != null)
            {
                //Add the parents -- aka main categories
                curGroupMap = new HashMap<String, Object>();
                groupData.add(curGroupMap);

                //Push the correct Icon
                if (keyName.equalsIgnoreCase("Phone"))
                    curGroupMap.put("ICON", R.drawable.phone_icon);
                else if (keyName.equalsIgnoreCase("Housing"))
                    curGroupMap.put("ICON", R.drawable.house_icon);
                else if (keyName.equalsIgnoreCase("Website"))
                    curGroupMap.put("ICON", R.drawable.web_icon);
                else if (keyName.equalsIgnoreCase("Area Snapshot"))
                    curGroupMap.put("ICON", R.drawable.camera_icon);
                else if (keyName.equalsIgnoreCase("Overview"))
                    curGroupMap.put("ICON", R.drawable.overview_icon);  
                else if (keyName.equalsIgnoreCase("Location"))
                    curGroupMap.put("ICON", R.drawable.map_icon);
                else
                    curGroupMap.put("ICON", R.drawable.icon);

                //Pop on the Name and Value
                curGroupMap.put("NAME", keyName);
                curGroupMap.put("VALUE", value);
            }
        }

        curGroupMap = new HashMap<String, Object>();
        groupData.add(curGroupMap);
        curGroupMap.put("ICON", R.drawable.back_icon);
        curGroupMap.put("NAME","Go Back");
        curGroupMap.put("VALUE","By clicking here");

        //Set up adapter
        mAdapter = new SimpleAdapter(
                mContext,
                groupData,
                R.layout.exp_list_parent,
                new String[] { "ICON", "NAME", "VALUE" },
                new int[] { R.id.photoAlbumImg, R.id.rowText1, R.id.rowText2  }
        );

        myList.setAdapter(mAdapter); //Bind the adapter to the list 

提前感谢您的帮助!!


2
好的,我来回答。为什么你需要在ListView中使用MapView? - CaseyB
1
此外,您遇到了什么问题? - CommonsWare
假设--您为特定业务列出了所有内容..我想显示一个地图,标明它在列表中的位置。我遇到的问题是我不知道从哪里开始。我想将其包含在ListView中,而不是必须启动一个全新的Intent(这有点违背了ListView的目的)。 - Ryan
嘿,Ryan,你在listAdapter中展示mapview的功能实现了吗?我也在尝试实现这样的功能。 - Abdul Wahab
5个回答

56

为了向一个相当旧的答案(实际上已经超过2年)发布一种替代方案,但我认为这可能会帮助像我一样偶然发现此帖子的人。

注意:这对于只需要在“地图”中显示位置但不需要在ListView中与其交互的人可能有用。实际的地图可以显示在例如“详细信息”页面上,在ListView中单击项目后。

如@CaseyB所指出的那样,MapView是一种比较重的视图。为了应对这个方面(并使我的生活变得更加轻松;-)),我选择构建一个URL,就像您为静态Google地图构建URL一样,使用几个必要的参数适用于我的应用程序。您可以在此处获取更多选项:https://developers.google.com/maps/documentation/staticmaps/

首先,当我构建ListView的数据时,我将诸如纬度经度之类的数据传递给一个字符串,并使用来自上述链接的一些静态变量。我从Facebook API中获取我的坐标。

我用于构建链接的代码:

String getMapURL = "http://maps.googleapis.com/maps/api/staticmap?zoom=18&size=560x240&markers=size:mid|color:red|"  
+ JOLocation.getString("latitude") 
+ "," 
+ JOLocation.getString("longitude") 
+ "&sensor=false";

上述构建的URL在浏览器中使用时,返回一个 .PNG 文件。然后,在我的活动适配器中,我使用 @Fedor 的“延迟加载”来显示从先前构建的URL生成的图像以在自定义 ListView 中显示。当然,您可以选择自己的方法来显示此 Map(实际上是 Map 的图像)。
最终结果的示例。

enter image description here

目前,我在这个 ListView 中使用了约30个 Checkin Maps(我与 Facebook SDK 一起使用),但用户可能有数百个,而且没有任何减速的报告。

我怀疑,考虑到问题发布后经过的时间,这可能对 OP 没有帮助,但希望它能帮助未来访问此页面的其他用户。


1
哇,太棒了的回答! - tskulbru
2
想不出更好的方法了。干得好。 - Anirudh
我正在处理同样的问题。我想在列表视图中展示两个位置之间的实际路线路径(而不是直线)。有人知道如何实现吗? - Hitesh Kamani
这还是最好的方法吗?Android Google Maps SDK最近添加了“Lite Mode”,但这种方式似乎更轻量级。有没有办法能够将更长的标签添加到标记上?现在似乎只限于1个字符。 - John Oleynik
@JohnOleynik:说实话,到目前为止,我还没有感觉到需要改变使用静态地图的必要性。话虽如此,我确实阅读了 SDK 新版本中的 Lite Mode,它也是一个位置的位图。而且它是交互式的。这似乎有一个 ListView 中使用 Lite Mode 的工作示例。我还没有测试过它,甚至没有看过它的代码。但看起来很有前途。试试吧。 :-) - Siddharth Lele
这是一个很棒的答案。我从来没有想过。向你致敬。对于我来说,在列表中显示PNG并在选择地图后显示地图活动,这是最好的方式。方便而且最好!谢谢!!! - class Android

6

首先,我认为同时显示多个 MapView 是行不通的。MapActivity 文档中指出,每个进程只支持一个 MapActivity:

"每个进程只支持一个 MapActivity。同时运行多个 MapActivity 很可能会以意想不到和不希望的方式干扰彼此。"

(http://code.google.com/android/add-ons/google-apis/reference/index.html)

它并没有明确说明在 MapActivity 中不能有多个 MapView,但我认为无论这些 MapView 在什么样的父 ViewGroup 中,它们都会互相干扰。

其次,您可以考虑使用静态地图 API 获取一个简单的图像,以便包含在 ListView 中 -- 一个完整的 MapView 可能是不必要的重量级操作:

http://code.google.com/apis/maps/documentation/staticmaps/

您可能会面临的唯一问题是,静态地图 API 通过“用户”限制使用,这可能意味着通过 IP 进行限制(它不需要 API 密钥),而移动网络可能会在 IP 使用限制方面出现问题。我不确定这将如何发挥作用。


嗨,史蒂夫——我不是想显示多个地图视图...只有一个。此外,目标是让用户与地图交互,所以静态地图不起作用,不幸的是。不过还是谢谢你的建议 :) - Ryan
啊,我明白了。在这种情况下,你可以使用自定义列表适配器,就像CaseyB所说的那样,或者如果你的列表项大多是异构的,也可以只是在ScrollView中使用垂直LinearLayout。 - Steve

4
从GoogleMapSample代码本身,这是可能的:
/**
 * This shows to include a map in lite mode in a ListView.
 * Note the use of the view holder pattern with the
 * {@link com.google.android.gms.maps.OnMapReadyCallback}.
 */
public class LiteListDemoActivity extends AppCompatActivity {

    private ListFragment mList;

    private MapAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.lite_list_demo);

        // Set a custom list adapter for a list of locations
        mAdapter = new MapAdapter(this, LIST_LOCATIONS);
        mList = (ListFragment) getSupportFragmentManager().findFragmentById(R.id.list);
        mList.setListAdapter(mAdapter);

        // Set a RecyclerListener to clean up MapView from ListView
        AbsListView lv = mList.getListView();
        lv.setRecyclerListener(mRecycleListener);

    }

    /**
     * Adapter that displays a title and {@link com.google.android.gms.maps.MapView} for each item.
     * The layout is defined in <code>lite_list_demo_row.xml</code>. It contains a MapView
     * that is programatically initialised in
     * {@link #getView(int, android.view.View, android.view.ViewGroup)}
     */
    private class MapAdapter extends ArrayAdapter<NamedLocation> {

        private final HashSet<MapView> mMaps = new HashSet<MapView>();

        public MapAdapter(Context context, NamedLocation[] locations) {
            super(context, R.layout.lite_list_demo_row, R.id.lite_listrow_text, locations);
        }


        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View row = convertView;
            ViewHolder holder;

            // Check if a view can be reused, otherwise inflate a layout and set up the view holder
            if (row == null) {
                // Inflate view from layout file
                row = getLayoutInflater().inflate(R.layout.lite_list_demo_row, null);

                // Set up holder and assign it to the View
                holder = new ViewHolder();
                holder.mapView = (MapView) row.findViewById(R.id.lite_listrow_map);
                holder.title = (TextView) row.findViewById(R.id.lite_listrow_text);
                // Set holder as tag for row for more efficient access.
                row.setTag(holder);

                // Initialise the MapView
                holder.initializeMapView();

                // Keep track of MapView
                mMaps.add(holder.mapView);
            } else {
                // View has already been initialised, get its holder
                holder = (ViewHolder) row.getTag();
            }

            // Get the NamedLocation for this item and attach it to the MapView
            NamedLocation item = getItem(position);
            holder.mapView.setTag(item);

            // Ensure the map has been initialised by the on map ready callback in ViewHolder.
            // If it is not ready yet, it will be initialised with the NamedLocation set as its tag
            // when the callback is received.
            if (holder.map != null) {
                // The map is already ready to be used
                setMapLocation(holder.map, item);
            }

            // Set the text label for this item
            holder.title.setText(item.name);

            return row;
        }

        /**
         * Retuns the set of all initialised {@link MapView} objects.
         *
         * @return All MapViews that have been initialised programmatically by this adapter
         */
        public HashSet<MapView> getMaps() {
            return mMaps;
        }
    }

    /**
     * Displays a {@link LiteListDemoActivity.NamedLocation} on a
     * {@link com.google.android.gms.maps.GoogleMap}.
     * Adds a marker and centers the camera on the NamedLocation with the normal map type.
     */
    private static void setMapLocation(GoogleMap map, NamedLocation data) {
        // Add a marker for this item and set the camera
        map.moveCamera(CameraUpdateFactory.newLatLngZoom(data.location, 13f));
        map.addMarker(new MarkerOptions().position(data.location));

        // Set the map type back to normal.
        map.setMapType(GoogleMap.MAP_TYPE_NORMAL);
    }

    /**
     * Holder for Views used in the {@link LiteListDemoActivity.MapAdapter}.
     * Once the  the <code>map</code> field is set, otherwise it is null.
     * When the {@link #onMapReady(com.google.android.gms.maps.GoogleMap)} callback is received and
     * the {@link com.google.android.gms.maps.GoogleMap} is ready, it stored in the {@link #map}
     * field. The map is then initialised with the NamedLocation that is stored as the tag of the
     * MapView. This ensures that the map is initialised with the latest data that it should
     * display.
     */
    class ViewHolder implements OnMapReadyCallback {

        MapView mapView;

        TextView title;

        GoogleMap map;

        @Override
        public void onMapReady(GoogleMap googleMap) {
            MapsInitializer.initialize(getApplicationContext());
            map = googleMap;
            NamedLocation data = (NamedLocation) mapView.getTag();
            if (data != null) {
                setMapLocation(map, data);
            }
        }

        /**
         * Initialises the MapView by calling its lifecycle methods.
         */
        public void initializeMapView() {
            if (mapView != null) {
                // Initialise the MapView
                mapView.onCreate(null);
                // Set the map ready callback to receive the GoogleMap object
                mapView.getMapAsync(this);
            }
        }

    }

    /**
     * RecycleListener that completely clears the {@link com.google.android.gms.maps.GoogleMap}
     * attached to a row in the ListView.
     * Sets the map type to {@link com.google.android.gms.maps.GoogleMap#MAP_TYPE_NONE} and clears
     * the map.
     */
    private AbsListView.RecyclerListener mRecycleListener = new AbsListView.RecyclerListener() {

        @Override
        public void onMovedToScrapHeap(View view) {
            ViewHolder holder = (ViewHolder) view.getTag();
            if (holder != null && holder.map != null) {
                // Clear the map and free up resources by changing the map type to none
                holder.map.clear();
                holder.map.setMapType(GoogleMap.MAP_TYPE_NONE);
            }

        }
    };

    /**
     * Location represented by a position ({@link com.google.android.gms.maps.model.LatLng} and a
     * name ({@link java.lang.String}).
     */
    private static class NamedLocation {

        public final String name;

        public final LatLng location;

        NamedLocation(String name, LatLng location) {
            this.name = name;
            this.location = location;
        }
    }
}

完整代码可在以下链接中找到:https://github.com/googlemaps/android-samples/blob/master/ApiDemos/app/src/main/java/com/example/mapdemo/LiteListDemoActivity.java


链接失效,请使用更新后的链接 https://github.com/googlemaps/android-samples/blob/master/ApiDemos/java/app/src/main/java/com/example/mapdemo/LiteListDemoActivity.java - Shubham AgaRwal
更新的链接:https://github.com/googlemaps/android-samples/blob/master/ApiDemos/kotlin/app/src/gms/java/com/example/kotlindemos/LiteListDemoActivity.kt - Mith

4
在这种情况下,您可以像添加其他视图一样将MapView添加到列表中。 这里有一个快速教程,介绍如何创建自定义列表适配器。但是我必须提醒您,MapView是一个非常沉重的视图,如果您尝试在屏幕上获取大量MapView,则会注意到应用程序变得缓慢!你可以只添加一个按钮到列表项,让用户进入另一页查看更多信息,包括地图。

2
我认为,该链接不是用于在自定义适配器中显示地图视图... 请指导我如何在列表适配器中添加地图。 - Abdul Wahab
我有SupportMapFragment,可以在ListView行内使用吗?我有自定义适配器,但我不知道如何在BaseExpandableListAdapter的getChildView()中使用地图。我们可以像这样简单地做:((TextView) convertView.findViewById(R.id.parent_txt_list_title)).setText(parent.getTitle()); 但是我们如何为地图做同样的事情呢?你能帮我吗? - Chintan Khetiya
我在ListView中添加了一个地图,但由于父级滚动而遇到了缩放/捏合问题。 - Milon

-1
今天我遇到了同样的问题 - 原来你必须在你的MapActivity中创建MapView,否则你会得到一个错误,比如无法膨胀视图com.google.maps.MapView之类的错误... 然后将这个MapView传递给你的ListAdapter,并在需要时输出它。我不得不把MapView放在一个RelativeLayout中,以便根据需要调整高度和宽度(由于某种原因,MapView不像普通的视图那样表现)。
如果你想要更多细节,可以问我 :)

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