[英]Restoring MapView's state on rotate and on back
Background 背景
I have a larger application in which I had/have several problems with new Google Maps API. 我有一个更大的应用程序,我在使用新的Google Maps API时遇到了一些问题。 I tried to describe it in a different question but since it seems too complex I decided to start a new project, as simple as possible and try to reproduce problems.
我尝试用不同的问题描述它,但由于它看起来太复杂,我决定开始一个新项目,尽可能简单并尝试重现问题。 So here it is.
所以这就是。
The situation 情况
I'm using Fragments
and want to put MapView
inside. 我正在使用
Fragments
并希望将MapView
放入其中。 I don't want to use MapFragment
. 我不想使用
MapFragment
。 The sample project I prepared may be not very beautiful but I tried to make it as simple as possible and it had to contain some elements (again simplified) from the original app. 我准备的示例项目可能不是很漂亮,但我试图让它尽可能简单,它必须包含原始应用程序中的一些元素(再次简化)。 I have one
Activity
and my custom Fragment
with MapView
in it, added programatically. 我有一个
Activity
和我的自定义Fragment
with MapView
以编程方式添加。 The Map
contains some points/ Markers
. Map
包含一些点/ Markers
。 After clicking on a Marker
the InfoWindow
is shown and clicking on it causes next Fragment
being shown (with replace()
function) in content. 单击
Marker
,显示InfoWindow
并单击它会导致在内容中显示下一个Fragment
(带有replace()
函数)。
The problems 问题
There are two issues I have: 我有两个问题:
When the Map
with Markers
is displayed screen rotation causes Class not found when unmarshalling
error with my custom MyMapPoint
class - I have no idea why and what it means. 当显示带
Markers
的Map
,屏幕旋转导致Class not found when unmarshalling
使用我的自定义MyMapPoint
类Class not found when unmarshalling
错误时MyMapPoint
类 - 我不知道为什么以及它意味着什么。
I click the Marker
and then InfoWindow
. 我单击
Marker
然后单击InfoWindow
。 After this I press hardware back button. 在此之后我按下硬件后退按钮。 Now I can see the
Map
but with no Markers
and centered in 0,0
point. 现在我可以看到
Map
但没有Markers
并且集中在0,0
点。
The code 编码
MainActivity 主要活动
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 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 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 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];
}
};
}
}
If you need to take a look at any other file - let me know. 如果您需要查看任何其他文件 - 请告诉我。 Here you can find a complete project, you just have to put your own Maps API KEY in
AndroidManifest.xml
file. 在这里,您可以找到一个完整的项目,您只需将自己的Maps API KEY放在
AndroidManifest.xml
文件中即可。
EDIT 编辑
I managed to make the example even more simple and updated the code above. 我设法使示例更简单并更新了上面的代码。
Here is my fix to the first problem: 这是我对第一个问题的解决方法:
It seems that Map
is trying to unparcel all the bundle, not just it's own information when I call mMap.onCreate(savedInstanceState)
and it has problem with it if I'm using my custom Parcelable
class. 看起来
Map
正在尝试解压缩所有的bundle,而不仅仅是当我调用mMap.onCreate(savedInstanceState)
时它自己的信息,如果我使用我的自定义Parcelable
类它有问题。 The solution that worked for me was removing my extras from savedInstanceState
as soon as I used them - before I call Map's onCreate()
. 对我
savedInstanceState
的解决方案是在我使用后立即从savedInstanceState
删除我的额外内容 - 在我调用Map的onCreate()
。 I do it with savedInstanceState.remove(MY_KEY)
. 我用
savedInstanceState.remove(MY_KEY)
。 Another thing I had to do was to call mMap.onSaveInstanceState()
before adding my own information to outState
in Fragment's
onSaveInstanceState(Bundle outState)
function. 我要做的另一件事是在将自己的信息添加到
Fragment's
onSaveInstanceState(Bundle outState)
函数中的outState
之前调用mMap.onSaveInstanceState()
。
And here's how I handled the second one: 以下是我处理第二个问题的方法:
I simplified the example project to the bare bones. 我将示例项目简化为裸骨。 I was adding raw
Markers
to the map and if I replace the Fragment
with map with another one then after clicking "back" I still got "nulled" map. 我正在将原始
Markers
添加到地图中,如果我用另一个地图替换Fragment
,那么在点击“返回”之后我仍然会得到“空白”地图。 So I did two things: 所以我做了两件事:
CameraPosition
in onPause()
function to restore it in onResume()
onPause()
函数中保存CameraPosition
以在onResume()
恢复它 mMap
to null in onPause()
so when the Fragment
comes back, the Markers
are added again by the addMapPoints()
function (I had to change it a little bit since I was saving and checking Markers
id's). onPause()
mMap
设置为null,因此当Fragment
返回时, addMapPoints()
函数会再次添加Markers
addMapPoints()
因为我保存并检查Markers
id,所以我必须稍微更改一下)。 Here are code samples: 以下是代码示例:
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;
}
}
And to update the camera position in onResume()
I had to manually initialize maps. 要更新
onResume()
的摄像头位置,我必须手动初始化地图。 I did it in setUpMap()
: 我在
setUpMap()
中setUpMap()
:
private void setUpMap() {
try {
MapsInitializer.initialize(getActivity());
} catch (GooglePlayServicesNotAvailableException e) {
}
mMap.setOnInfoWindowClickListener(this);
mMap.setOnMapLongClickListener(this);
addMapPoints();
}
I realize that those aren't real solutions - just overrides but it's the best I can do for now and the project must go on. 我意识到那些不是真正的解决方案 - 只是覆盖,但它是我现在能做的最好的,项目必须继续。 If anyone finds cleaner fixes I'll be grateful for letting me know about them.
如果有人发现更清洁的修复,我会很高兴让我知道他们。
Came here in search of any hints regarding onSaveInstance
and NullPointerException
. 来这里搜索有关
onSaveInstance
和NullPointerException
的任何提示。 I have tried various workarounds including the one mentioned by @Izydorr but have had no luck. 我尝试过各种各样的解决方法,包括@Izydorr提到的那个,但没有运气。 The issue was with
FragmentPagerAdapter
- the adapter of the ViewPager
that was hosting my fragments that were embedding the MapView
s. 问题在于
FragmentPagerAdapter
- ViewPager
的适配器,它托管了我嵌入MapView
的片段。 After having it changed to FragmentStatePagerAdapter
the NPEs have gone away. 在将其更改为
FragmentStatePagerAdapter
,NPE已经消失。 Whew! 呼!
使用GoogleMap.OnCameraChangeListener
实现您的片段/活动并覆盖方法onCameraChange
并进入您可以保存新摄像机位置,并使用onResume
使用
googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPositionSaved));
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.