android tab with interacting map and list views

from:http://joshclemm.com/blog/?p=86

 

Last tutorial, we wrote a simple app that displays two interacting list views in a TabActivity. In this tutorial, we will up the ante and add a MapView as the content of one of the tabs. Why again are we using multiple views in an activity instead of using a separate activity for each tab content? Remember, we want our tabs to be able to easily interact with one another, and keeping them as views allows us to handle the logic and interaction within one activity.

So, our goal in this tutorial is to have a list of geo coordinates and when we click on an item in the list, our map view goes to that location.

First off, let's create an XML layout that contains a TabHostTabWidget, a ListView, and a MapView.

01 <?xml version="1.0" encoding="utf-8"?>
02 <TabHost xmlns:android="http://schemas.android.com/apk/res/android"
03     android:id="@android:id/tabhost" android:layout_width="fill_parent"
04     android:layout_height="fill_parent">
05     <LinearLayout android:orientation="vertical"
06         android:layout_width="fill_parent" android:layout_height="fill_parent">
07         <TabWidget android:id="@android:id/tabs"
08             android:layout_width="fill_parent" android:layout_height="wrap_content" />
09         <FrameLayout android:id="@android:id/tabcontent"
10             android:layout_width="fill_parent" android:layout_height="fill_parent">
11             <LinearLayout android:orientation="vertical"
12                 android:layout_width="fill_parent" android:layout_height="fill_parent"
13                 android:id="@+id/main">
14                 <ListView android:id="@+id/list" android:layout_width="fill_parent"
15                     android:layout_height="0dip" android:layout_weight="1" />
16                 <TextView android:id="@+id/empty" android:layout_width="wrap_content"
17                     android:layout_height="wrap_content" android:gravity="center"
18                     android:text="Loading" />
19             </LinearLayout>
20             <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
21                 android:id="@+id/mainlayout" android:orientation="vertical"
22                 android:layout_width="fill_parent" android:layout_height="fill_parent">
23                 <com.google.android.maps.MapView
24                     android:id="@+id/mapview" android:layout_width="fill_parent"
25                     android:layout_height="fill_parent" android:clickable="true"
26                     android:apiKey="[your MAPS API KEY here]" />
27             </RelativeLayout>
28         </FrameLayout>
29     </LinearLayout>
30 </TabHost>

Once we have a layout, we need to create our main Activity. While last tutorial our Activity was of type TabActivity, in this tutorial our Activity has to be of type MapActivity.

Why is this? Using a MapView in Android must be in a MapActivity class, otherwise your app with throw an Exception (take a look at the activities source code if you're interested in more details). As a result of this, we will have to perform the functions of TabActivity ourselves in the MapActivity class (you'll see that below).

Let's create a class called TabbedListMapActivity and have it extend MapActivity.

In the onCreate() method, we need to set the content to our XML layout, extract the TabHost object, and call setup() on the TabHost (we need to do this because our Activity is not a TabActivity).

1 setContentView(R.layout.main);
2  
3 tabHost = (TabHost) findViewById(android.R.id.tabhost);
4  
5 // setup must be called if you are not inflating the tabhost from XML
6 tabHost.setup();

Next, we want to extract our ListView from the XML, set it to a member variable, and add some initial coordinates to its list adapter.

1 // setup list view
2 listView = (ListView) findViewById(R.id.list);
3 listView.setEmptyView((TextView) findViewById(R.id.empty));
4  
5 // create some dummy coordinates to add to the list
6 List<GeoPoint> pointsList = new ArrayList<GeoPoint>();
7 pointsList.add(new GeoPoint((int)(32.864*1E6), (int)(-117.2353*1E6)));
8 pointsList.add(new GeoPoint((int)(37.441*1E6), (int)(-122.1419*1E6)));
9 listView.setAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, pointsList));

Then, we want to extract our MapView from the XML and set it to a member variable.

1 // setup map view
2 mapView = (MapView) findViewById(R.id.mapview);
3 mapView.setBuiltInZoomControls(true);
4 mapView.postInvalidate();

Once we have our MapView, we can add a listener to the ListView for selection events. Let's add a listener that when an item is clicked, we set the map to the coordinates of the selected item.

01 // add an onclicklistener to see point on the map
02 listView.setOnItemClickListener(new OnItemClickListener() {
03     public void onItemClick(AdapterView parent, View view, int position, long id) {
04         GeoPoint geoPoint = (GeoPoint) listView.getAdapter().getItem(position);
05         if(geoPoint != null) {
06             // have map view moved to this point
07             setMapZoomPoint(geoPoint, 12);
08             // programmatically switch tabs to the map view
09             tabHost.setCurrentTab(1);
10         }
11     }
12 });

The setMapZoomPoint method is implemented like this:

1 private void setMapZoomPoint(GeoPoint geoPoint, int zoomLevel) {
2     mapView.getController().setCenter(geoPoint);
3     mapView.getController().setZoom(zoomLevel);
4     mapView.postInvalidate();
5 }

The final step is to add our two views (MapView and ListView) as content to our tab host.

01 // add views to tab host
02 tabHost.addTab(tabHost.newTabSpec("List").setIndicator("List").setContent(newTabContentFactory() {
03     public View createTabContent(String arg0) {
04         return listView;
05     }
06 }));
07 tabHost.addTab(tabHost.newTabSpec("Map").setIndicator("Map").setContent(newTabContentFactory() {
08     public View createTabContent(String arg0) {
09         return mapView;
10     }
11 }));

Now if you were to run this app now, you may notice strange behavior (might just be on my phone). You should see our two tabs starting with the list view tab. Instead you may see the MapView "bleeding" through the ListView. I'm not exactly sure what causes this, but a workaround is to manually set the tab view to the map, then manually set the tab view back to the list. This causes the activity to redraw the tabs correctly. Here's my workaround in code:

1 //HACK to get the list view to show up first,
2 // otherwise the mapview would be bleeding through and visible
3 tabHost.setCurrentTab(1); //the mapview
4 tabHost.setCurrentTab(0); //the listview

Now everything should look right and you should be able to click a coordinate on the list view, and the map view tab goes to that spot on the earth.

If you are having issues with seeing the Map, remember you need to use the Google-Apis instead of the standard Android apis, you need to generate a Maps API key, you need to add interest permissions to the manifest, and you need to make sure the uses-library line is inside your application tag in the manifest.

Here's my manifest for reference:

01 <?xml version="1.0" encoding="utf-8"?>
02 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
03     package="com.joshclemm.android.tutorial" android:versionCode="1"
04     android:versionName="1.0">
05     <application android:icon="@drawable/icon" android:label="@string/app_name">
06         <activity android:name=".TabbedListMapActivity" android:label="@string/app_name">
07             <intent-filter>
08                 <action android:name="android.intent.action.MAIN" />
09                 <category android:name="android.intent.category.LAUNCHER" />
10             </intent-filter>
11         </activity>
12         <uses-library android:name="com.google.android.maps" />
13     </application>
14     <uses-sdk android:minSdkVersion="4"></uses-sdk>
15     <uses-permission android:name="android.permission.INTERNET" />
16 </manifest>

To see an activity with a map and list view in action, check out my Earthquake Alert! Android App

Here's the full Java source code:

001 package com.joshclemm.android.tutorial;
002  
003 import java.util.ArrayList;
004 import java.util.List;
005  
006 import android.os.Bundle;
007 import android.view.View;
008 import android.widget.AdapterView;
009 import android.widget.ArrayAdapter;
010 import android.widget.ListView;
011 import android.widget.TabHost;
012 import android.widget.TextView;
013 import android.widget.AdapterView.OnItemClickListener;
014 import android.widget.TabHost.OnTabChangeListener;
015 import android.widget.TabHost.TabContentFactory;
016  
017 import com.google.android.maps.GeoPoint;
018 import com.google.android.maps.MapActivity;
019 import com.google.android.maps.MapView;
020  
021 public class TabbedListMapActivity extends MapActivity implements OnTabChangeListener {
022  
023     private static final String LIST_TAB_TAG = "List";
024     private static final String MAP_TAB_TAG = "Map";
025  
026     private TabHost tabHost;
027  
028     private ListView listView;
029     private MapView mapView;
030  
031     @Override
032     public void onCreate(Bundle savedInstanceState) {
033         super.onCreate(savedInstanceState);
034         setContentView(R.layout.main);
035  
036         tabHost = (TabHost) findViewById(android.R.id.tabhost);
037  
038         // setup must be called if you are not inflating the tabhost from XML
039         tabHost.setup();
040         tabHost.setOnTabChangedListener(this);
041  
042         // setup list view
043         listView = (ListView) findViewById(R.id.list);
044         listView.setEmptyView((TextView) findViewById(R.id.empty));
045  
046         // create some dummy coordinates to add to the list
047         List<GeoPoint> pointsList = new ArrayList<GeoPoint>();
048         pointsList.add(new GeoPoint((int)(32.864*1E6), (int)(-117.2353*1E6)));
049         pointsList.add(new GeoPoint((int)(37.441*1E6), (int)(-122.1419*1E6)));
050         listView.setAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, pointsList));
051  
052         // add an onclicklistener to see point on the map
053         listView.setOnItemClickListener(new OnItemClickListener() {
054             public void onItemClick(AdapterView parent, View view, int position, long id) {
055                 GeoPoint geoPoint = (GeoPoint) listView.getAdapter().getItem(position);
056                 if(geoPoint != null) {
057                     // have map view moved to this point
058                     setMapZoomPoint(geoPoint, 12);
059                     // programmatically switch tabs to the map view
060                     tabHost.setCurrentTab(1);
061                 }
062             }
063         });
064  
065         // setup map view
066         mapView = (MapView) findViewById(R.id.mapview);
067         mapView.setBuiltInZoomControls(true);
068         mapView.postInvalidate();
069  
070         // add views to tab host
071         tabHost.addTab(tabHost.newTabSpec(LIST_TAB_TAG).setIndicator("List").setContent(newTabContentFactory() {
072             public View createTabContent(String arg0) {
073                 return listView;
074             }
075         }));
076         tabHost.addTab(tabHost.newTabSpec(MAP_TAB_TAG).setIndicator("Map").setContent(newTabContentFactory() {
077             public View createTabContent(String arg0) {
078                 return mapView;
079             }
080         }));
081  
082         //HACK to get the list view to show up first,
083         // otherwise the mapview would be bleeding through and visible
084         tabHost.setCurrentTab(1);
085         tabHost.setCurrentTab(0);
086     }
087  
088     /**
089      * Instructs the map view to navigate to the point and zoom level specified.
090      * @param geoPoint
091      * @param zoomLevel
092      */
093     private void setMapZoomPoint(GeoPoint geoPoint, int zoomLevel) {
094         mapView.getController().setCenter(geoPoint);
095         mapView.getController().setZoom(zoomLevel);
096         mapView.postInvalidate();
097     }
098  
099     /**
100      * From MapActivity, we ignore it for this demo
101      */
102     @Override
103     protected boolean isRouteDisplayed() {
104         return false;
105     }
106  
107     /**
108      * Implement logic here when a tab is selected
109      */
110     public void onTabChanged(String tabName) {
111         if(tabName.equals(MAP_TAB_TAG)) {
112             //do something on the map
113         }
114         else if(tabName.equals(LIST_TAB_TAG)) {
115             //do something on the list
116         }
117     }
118 }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章