android - pin - Restaurar el estado de MapView en rotar y volver
marker android map (3)
Fondo
Tengo una aplicación más grande en la que tuve / tuve varios problemas con la nueva API de Google Maps. Traté de describirlo en una pregunta diferente, pero como parece demasiado complejo, decidí comenzar un nuevo proyecto, lo más simple posible y tratar de reproducir los problemas. Asi que aqui esta.
La situación
Estoy usando Fragments
y quiero poner MapView
adentro. No quiero usar MapFragment
. El proyecto de muestra que preparé puede no ser muy bonito, pero traté de hacerlo lo más simple posible y tenía que contener algunos elementos (nuevamente simplificados) de la aplicación original. Tengo una Activity
y mi Fragment
personalizado con MapView
en ella, agregado programáticamente. El Map
contiene algunos puntos / Markers
. Después de hacer clic en un Marker
se muestra la InfoWindow
y al hacer clic en ella se muestra el siguiente Fragment
(con la función de replace()
) en el contenido.
Los problemas
Hay dos problemas que tengo:
Cuando se muestra el
Map
conMarkers
, la rotación de la pantalla causa que laClass not found when unmarshalling
error con mi clase personalizada deMyMapPoint
; no tengo ni idea de por qué y qué significa.Hago clic en el
Marker
y luego en laInfoWindow
. Después de esto, presiono el botón de retroceso de hardware. Ahora puedo ver elMap
pero sinMarkers
y centrado en0,0
puntos.
El código
Actividad principal
public class MainActivity extends FragmentActivity {
private ArrayList<MyMapPoint> mPoints = new ArrayList<MyMapPoint>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
mPoints.add(new MyMapPoint(1, new LatLng(20, 10),
"test point", "description", null));
mPoints.add(new MyMapPoint(2, new LatLng(10, 20),
"test point 2", "second description", null));
Fragment fragment = MyMapFragment.newInstance(mPoints);
getSupportFragmentManager().beginTransaction()
.add(R.id.contentPane, fragment).commit();
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contentPane"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
map_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.google.android.gms.maps.MapView
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
MyMapFragment
public class MyMapFragment extends Fragment implements
OnInfoWindowClickListener {
public static final String KEY_POINTS = "points";
private MapView mMapView;
private GoogleMap mMap;
private HashMap<MyMapPoint, Marker> mPoints =
new HashMap<MyMapPoint, Marker>();
public static MyMapFragment newInstance(ArrayList<MyMapPoint> points) {
MyMapFragment fragment = new MyMapFragment();
Bundle args = new Bundle();
args.putParcelableArrayList(KEY_POINTS, points);
fragment.setArguments(args);
return fragment;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
MyMapPoint[] points = mPoints.keySet().toArray(
new MyMapPoint[mPoints.size()]);
outState.putParcelableArray(KEY_POINTS, points);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Bundle extras = getArguments();
if ((extras != null) && extras.containsKey(KEY_POINTS)) {
for (Parcelable pointP : extras.getParcelableArrayList(KEY_POINTS)) {
mPoints.put((MyMapPoint) pointP, null);
}
}
} else {
MyMapPoint[] points = (MyMapPoint[]) savedInstanceState
.getParcelableArray(KEY_POINTS);
for (MyMapPoint point : points) {
mPoints.put(point, null);
}
}
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.map_fragment, container, false);
mMapView = (MapView) layout.findViewById(R.id.map);
return layout;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mMapView.onCreate(savedInstanceState);
setUpMapIfNeeded();
addMapPoints();
}
@Override
public void onPause() {
mMapView.onPause();
super.onPause();
}
@Override
public void onResume() {
super.onResume();
setUpMapIfNeeded();
mMapView.onResume();
}
@Override
public void onDestroy() {
mMapView.onDestroy();
super.onDestroy();
}
public void onLowMemory() {
super.onLowMemory();
mMapView.onLowMemory();
};
private void setUpMapIfNeeded() {
if (mMap == null) {
mMap = ((MapView) getView().findViewById(R.id.map)).getMap();
if (mMap != null) {
setUpMap();
}
}
}
private void setUpMap() {
mMap.setOnInfoWindowClickListener(this);
addMapPoints();
}
private void addMapPoints() {
if (mMap != null) {
HashMap<MyMapPoint, Marker> toAdd =
new HashMap<MyMapPoint, Marker>();
for (Entry<MyMapPoint, Marker> entry : mPoints.entrySet()) {
Marker marker = entry.getValue();
if (marker == null) {
MyMapPoint point = entry.getKey();
marker = mMap.addMarker(point.getMarkerOptions());
toAdd.put(point, marker);
}
}
mPoints.putAll(toAdd);
}
}
@Override
public void onInfoWindowClick(Marker marker) {
Fragment fragment = DetailsFragment.newInstance();
getActivity().getSupportFragmentManager().beginTransaction()
.replace(R.id.contentPane, fragment)
.addToBackStack(null).commit();
}
public static class MyMapPoint implements Parcelable {
private static final int CONTENTS_DESCR = 1;
public int objectId;
public LatLng latLng;
public String title;
public String snippet;
public MyMapPoint(int oId, LatLng point,
String infoTitle, String infoSnippet, String infoImageUrl) {
objectId = oId;
latLng = point;
title = infoTitle;
snippet = infoSnippet;
}
public MyMapPoint(Parcel in) {
objectId = in.readInt();
latLng = in.readParcelable(LatLng.class.getClassLoader());
title = in.readString();
snippet = in.readString();
}
public MarkerOptions getMarkerOptions() {
return new MarkerOptions().position(latLng)
.title(title).snippet(snippet);
}
@Override
public int describeContents() {
return CONTENTS_DESCR;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(objectId);
dest.writeParcelable(latLng, 0);
dest.writeString(title);
dest.writeString(snippet);
}
public static final Parcelable.Creator<MyMapPoint> CREATOR =
new Parcelable.Creator<MyMapPoint>() {
public MyMapPoint createFromParcel(Parcel in) {
return new MyMapPoint(in);
}
public MyMapPoint[] newArray(int size) {
return new MyMapPoint[size];
}
};
}
}
Si necesita echar un vistazo a cualquier otro archivo, hágamelo saber. Here puede encontrar un proyecto completo, solo tiene que poner su propia clave de la API de Maps en el archivo AndroidManifest.xml
.
EDITAR
Logré hacer el ejemplo aún más simple y actualicé el código anterior.
Aquí está mi solución al primer problema:
Parece que Map
está intentando separar todo el paquete, no solo su propia información cuando llamo a mMap.onCreate(savedInstanceState)
y tiene un problema si uso mi clase Parcelable
personalizada. La solución que funcionó para mí fue eliminar mis extras de savedInstanceState
tan pronto como los usé, antes de llamar a Map onCreate()
. Lo hago con savedInstanceState.remove(MY_KEY)
. Otra cosa que tuve que hacer fue llamar a mMap.onSaveInstanceState()
antes de agregar mi propia información a outState
en Fragment''s
onSaveInstanceState(Bundle outState)
.
Y así es como manejé el segundo:
Simplifiqué el proyecto de ejemplo a fondo. Estaba agregando Markers
crudos al mapa y si reemplazo el Fragment
con mapa con otro, luego de hacer clic en "volver" todavía obtengo el mapa "anulado". Entonces hice dos cosas:
- guardando
CameraPosition
en la funciónonPause()
para restaurarlo enonResume()
- estableciendo
mMap
en null enonPause()
así que cuando elFragment
vuelve, losMarkers
son agregados nuevamente por la funciónaddMapPoints()
(tuve que cambiarlo un poco ya que estaba guardando y comprobando los identificadores deMarkers
).
Aquí hay ejemplos de código:
private CameraPosition cp;
...
public void onPause() {
mMapView.onPause();
super.onPause();
cp = mMap.getCameraPosition();
mMap = null;
}
...
public void onResume() {
super.onResume();
setUpMapIfNeeded();
mMapView.onResume();
if (cp != null) {
mMap.moveCamera(CameraUpdateFactory.newCameraPosition(cp));
cp = null;
}
}
Y para actualizar la posición de la cámara en onResume()
tuve que inicializar manualmente los mapas. Lo hice en setUpMap()
:
private void setUpMap() {
try {
MapsInitializer.initialize(getActivity());
} catch (GooglePlayServicesNotAvailableException e) {
}
mMap.setOnInfoWindowClickListener(this);
mMap.setOnMapLongClickListener(this);
addMapPoints();
}
Me doy cuenta de que esas no son soluciones reales, solo anulaciones, pero es lo mejor que puedo hacer por ahora y el proyecto debe continuar. Si alguien encuentra soluciones más limpias, le estaré agradecido por informarme sobre ellas.
Implementa su fragmento / actividad con GoogleMap.OnCameraChangeListener
y reemplaza el método onCameraChange
y en usted puede guardar la nueva posición de la cámara, y en el uso de onResume
googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPositionSaved));
Vine aquí en busca de sugerencias sobre onSaveInstance
y NullPointerException
. He intentado varias soluciones incluyendo la mencionada por @Izydorr pero no he tenido suerte. El problema era con FragmentPagerAdapter
, el adaptador de ViewPager
que hospedaba mis fragmentos que MapView
. Después de cambiarlo a FragmentStatePagerAdapter
los NPE se han ido. ¡Uf!