Intereting Posts

Восстановление состояния MapView при вращении и назад

Задний план

У меня есть более широкое приложение, в котором у меня возникли проблемы с новым API Карт Google. Я попытался описать это в другом вопросе, но, поскольку он кажется слишком сложным, я решил начать новый проект, как можно проще, и попытаться воспроизвести проблемы. Так вот оно.

Ситуация

Я использую Fragments и хочу поместить MapView внутрь. Я не хочу использовать MapFragment . Примерный проект, который я подготовил, может быть не очень красивым, но я старался сделать его максимально простым, и он должен был содержать некоторые элементы (снова упрощенные) из исходного приложения. У меня есть одно действие и мой пользовательский Fragment с MapView в нем, добавленный программно. Map содержит некоторые пункты / Markers . После нажатия на Marker InfoWindow и нажатие на нее вызывает отображение следующего Fragment (с функцией replace() ) в содержимом.

Проблемы

Есть два вопроса, которые у меня есть:

  1. Когда отображается Map с Markers , поворот экрана приводит к тому, что Class not found when unmarshalling ошибке MyMapPoint с моим пользовательским классом MyMapPoint – я понятия не имею, почему и что это значит.

  2. Я нажимаю Marker а затем InfoWindow . После этого я нажимаю кнопку возврата оборудования. Теперь я вижу Map но без Markers и в центре 0,0 .

Код

Основная деятельность

 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]; } }; } } 

Если вам нужно взглянуть на любой другой файл – сообщите мне. Здесь вы можете найти полный проект, вам просто нужно поместить свой собственный Maps API KEY в файл AndroidManifest.xml .

РЕДАКТИРОВАТЬ

Мне удалось сделать пример еще более простым и обновленным приведенным выше кодом.

Вот мое решение первой проблемы:

Кажется, что Map пытается распараллелить весь пакет, а не только свою собственную информацию, когда я вызываю mMap.onCreate(savedInstanceState) и у нее есть проблемы с этим, если я использую свой пользовательский класс Parcelable . Решение, которое сработало для меня, заключалось в удалении моих дополнительных savedInstanceState из savedInstanceState как только я их использовал, – прежде чем я назову Map onCreate() . Я делаю это с savedInstanceState.remove(MY_KEY) . Еще одна вещь, которую я должен был сделать, – вызвать mMap.onSaveInstanceState() прежде чем добавлять свою собственную информацию в outState в функции onSaveInstanceState(Bundle outState) в Fragment's .

И вот как я справился со вторым:

Я упростил пример проекта к голой кости. Я добавлял на карту необработанные Markers и если я заменил Fragment на карту другим, а после нажатия «назад» я все равно получил «нулевую» карту. Поэтому я сделал две вещи:

  1. Сохранение функции CameraPosition в функции onPause() для ее восстановления в onResume()
  2. Установка mMap в null в onPause() поэтому, когда возвращается Fragment , Markers снова addMapPoints() функцией addMapPoints() (мне пришлось немного изменить его с момента сохранения и проверки идентификаторов Markers ).

Вот примеры кода:

 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; } } 

И чтобы обновить положение камеры в onResume() мне пришлось вручную инициализировать карты. Я сделал это в setUpMap() :

 private void setUpMap() { try { MapsInitializer.initialize(getActivity()); } catch (GooglePlayServicesNotAvailableException e) { } mMap.setOnInfoWindowClickListener(this); mMap.setOnMapLongClickListener(this); addMapPoints(); } 

Я понимаю, что это не реальные решения – просто переопределяет, но это лучшее, что я могу сделать сейчас, и проект должен продолжаться. Если кто-нибудь найдет чистые исправления, я буду благодарен за то, что сообщите мне о них.

Пришел сюда в поисках каких-либо намеков относительно onSaveInstance и NullPointerException . Я пробовал различные обходные пути, в том числе тот, о котором говорил @Izydorr, но не повезло. Проблема была в FragmentPagerAdapter – адаптере ViewPager , в котором размещались мои фрагменты, которые ViewPager . После того, как они были изменены на FragmentStatePagerAdapter NPE ушли. Уф!

Реализует ваш фрагмент / активность с помощью метода GoogleMap.OnCameraChangeListener и переопределяет метод onCameraChange и вы можете сохранить новую позицию камеры и использовать onResume

 googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPositionSaved));