简体   繁体   English

在旋转和背面恢复MapView的状态

[英]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: 我有两个问题:

  1. 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. 当显示带MarkersMap ,屏幕旋转导致Class not found when unmarshalling使用我的自定义MyMapPointClass not found when unmarshalling错误时MyMapPoint类 - 我不知道为什么以及它意味着什么。

  2. 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: 所以我做了两件事:

  1. saving CameraPosition in onPause() function to restore it in onResume() onPause()函数中保存CameraPosition以在onResume()恢复它
  2. setting 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 . 来这里搜索有关onSaveInstanceNullPointerException的任何提示。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM