MapView in a Fragment (Honeycomb)
-
04-07-2021 - |
Вопрос
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/AndroidRuntime( 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
Решение
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"/>
Другие советы
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());
}
}
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):