MapView in a Fragment (Honeycomb)
now that the final SDK is out with google apis - what is the best way to create a Fragment with a MapView? MapView needs a MapActivity to work right.
Having the Activity managing the Fragments inherit from MapActivity (bad solution because it goes against the idea that Fragments are self contained) and use a regular xml based layout does not work. I get a NullPointerException in MapActivity.setupMapView():
E/AndroidRuntime( 597): Caused by: java.lang.NullPointerException E/AndroidRuntime( 597): at com.google.android.maps.MapActivity.setupMapView(MapActivity.java:400) E/AndroidRuntime( 597): at com.google.android.maps.MapView.(MapView.java:289) E/AndroidRuntime( 597): at com.google.android.maps.MapView.(MapView.java:264) E/AndroidRuntime( 597): at com.google.android.maps.MapView.(MapView.java:247)
My second idea was to create the MapView programmatically and pass the associated activity (via getActivity()) as Context to the MapView constructor. Does not work:
E/AndroidRuntime( 834): Caused by: java.lang.IllegalArgumentException: MapViews can only be created inside instances of MapActivity. E/AndroidRuntime( 834): at com.google.android.maps.MapView.(MapView.java:291) E/AndroidRuntime( 834): at com.google.android.maps.MapView.(MapView.java:235) E/AndroidRuntime( 834): at de.foo.FinderMapFragment.onCreateView(FinderMapFragment.java:225) E/Androi开发者_StackOverflowdRuntime( 834): at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:708) E/AndroidRuntime( 834): at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:900) E/AndroidRuntime( 834): at android.app.FragmentManagerImpl.addFragment(FragmentManager.java:978) E/AndroidRuntime( 834): at android.app.Activity.onCreateView(Activity.java:4090) E/AndroidRuntime( 834): at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:664)
Really there should be something like MapFragment that takes care of the background threads MapView needs I guess... So what is the current best practice to do this?
Thanks and regards from Germany, Valentin
I've managed to resolve this by using TabHost in fragment.
Here is the idea (briefly):
MainFragmentActivity
extendsFragmentActivity
(from support library) and hasMapFragment
.MyMapActivity
extendsMapActivity
and containMapView
.LocalActivityManagerFragment
hostsLocalActivityManager
MapFragment
extendsLocalActivityManagerFragment
.And
LocalActivityManager
containsMyMapActivity
activity in it.
Example implementation: https://github.com/inazaruk/map-fragment.
As discussed at Google Groups, Peter Doyle built a custom compatibility library supporting Google Maps too. android-support-v4-googlemaps
However, there's a downside too:
Currently, one downside is that ALL classes extending FragmentActivity are MapActivitys. Its possible to make a separate class (i.e. FragmentMapActivity), but it requires some refactoring of the FragmentActivity code.
Just to clarify the answer. I tried the approach suggested by inazaruk and ChristophK. Actually you can run any activity in a fragment - not just google maps. Here is the code which implements google map activity as a fragment thanks to inazaruk and ChristophK.
import com.actionbarsherlock.app.SherlockFragment;
import android.view.Window;
import android.app.LocalActivityManager;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class MapFragment extends SherlockFragment {
private static final String KEY_STATE_BUNDLE = "localActivityManagerState";
private LocalActivityManager mLocalActivityManager;
protected LocalActivityManager getLocalActivityManager() {
return mLocalActivityManager;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle state = null;
if (savedInstanceState != null) {
state = savedInstanceState.getBundle(KEY_STATE_BUNDLE);
}
mLocalActivityManager = new LocalActivityManager(getActivity(), true);
mLocalActivityManager.dispatchCreate(state);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//This is where you specify you activity class
Intent i = new Intent(getActivity(), GMapActivity.class);
Window w = mLocalActivityManager.startActivity("tag", i);
View currentView=w.getDecorView();
currentView.setVisibility(View.VISIBLE);
currentView.setFocusableInTouchMode(true);
((ViewGroup) currentView).setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
return currentView;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBundle(KEY_STATE_BUNDLE,
mLocalActivityManager.saveInstanceState());
}
@Override
public void onResume() {
super.onResume();
mLocalActivityManager.dispatchResume();
}
@Override
public void onPause() {
super.onPause();
mLocalActivityManager.dispatchPause(getActivity().isFinishing());
}
@Override
public void onStop() {
super.onStop();
mLocalActivityManager.dispatchStop();
}
@Override
public void onDestroy() {
super.onDestroy();
mLocalActivityManager.dispatchDestroy(getActivity().isFinishing());
}
}
As of 03.12.2012 Google released Google Maps Android API v2. Now you can forget about these problems. https://developers.google.com/maps/documentation/android/
Example using new API - https://developers.google.com/maps/documentation/android/start#add_a_map
This API will work for at least Android API 8, so use it ;).
So now you can simply use "com.google.android.gms.maps.MapFragment" fragment class. It will display the map in your Activity. Layout example from the link above:
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.MapFragment"/>
Great news from Google on this. They are releasing today a new Google Maps API, with indoor maps and MapFragment.
With this new API, adding a map to your Activity is as simple as:
<fragment
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.MapFragment" />
The Google Maps API is not part of the AOSP. As long as no Googler responds it is barely possible to tell if there will be a MapFragment in the future.
A possible limited alternative is to use a WebViewFragment
and abuse it to load up a custom maps.google.com
URL.
Hm too bad that Google has not responded yet. FWIW if you really need to do this I found no other way than:
Have the Tab Managing Activity inherit from MapActivity, create the MapView in there programmatically, have the mapfragment.xml contain a ViewGroup and add the MapView to the ViewGroup using
((ViewGroup) getFragmentManager().findFragmentById(R.id.finder_map_fragment).getView()).addView(mapView);;
Clearly this goes strongly against the idea that fragments are ment to be self-contained but ...
Here's a MonoDroid (Mono for Android) version of a very-simplified MapFragment:
public class MapFragment : Fragment
{
// FOLLOW http://stackoverflow.com/questions/5109336/mapview-in-a-fragment-honeycomb
private static String KEY_STATE_BUNDLE = "localActivityManagerState";
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Bundle state = null;
if (savedInstanceState != null) {
state = savedInstanceState.GetBundle(KEY_STATE_BUNDLE);
}
mLocalActivityManager = new LocalActivityManager(Activity, true);
mLocalActivityManager.DispatchCreate(state);
}
public override Android.Views.View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
//This is where you specify you activity class
Intent i = new Intent(Activity, typeof(SteamLocationMapActivity));
Window w = mLocalActivityManager.StartActivity("tag", i);
View currentView=w.DecorView;
currentView.Visibility = ViewStates.Visible;
currentView.FocusableInTouchMode = true;
((ViewGroup) currentView).DescendantFocusability = DescendantFocusability.AfterDescendants;
return currentView;
}
private LocalActivityManager mLocalActivityManager;
protected LocalActivityManager GetLocalActivityManager() {
return mLocalActivityManager;
}
public override void OnSaveInstanceState(Bundle outState)
{
base.OnSaveInstanceState(outState);
outState.PutBundle(KEY_STATE_BUNDLE,mLocalActivityManager.SaveInstanceState());
}
public override void OnResume()
{
base.OnResume();
mLocalActivityManager.DispatchResume();
}
public override void OnPause()
{
base.OnPause();
mLocalActivityManager.DispatchPause(Activity.IsFinishing);
}
public override void OnStop()
{
base.OnStop();
mLocalActivityManager.DispatchStop();
}
}
This solves my issue in adding MapView in Fragments. https://github.com/petedoyle/android-support-v4-googlemaps
With the new version of ABS 4.0, there is no suppport for MapFragmentActivity, here is a good solution for having a mapview in a Fragment!
https://xrigau.wordpress.com/2012/03/22/howto-actionbarsherlock-mapfragment-listfragment/#comment-21
May I get the solution:
- create class TempFragmentActivity extends MapActivity
- there is a MapView object inside TempFragmentActivity(like normal define in xml)
- remove this MapView object form parent(LinearLayout)(void later exception)
- keep this MapView object in somewhere(ex: static member of TempFragmentActivity)
- in your Fragment , add this MapView object using code(do not define in xml) into some LinearLayout
I wrote a little library, mashing up the LocalActivityManager-based solutions to the MapFragment problem (also includes an example app showing various usage situations):
https://github.com/coreform/android-tandemactivities
精彩评论