android - 防止 Google SupportMapFragment 重新加载

标签 android google-maps supportmapfragment

我在使用 SupportMapFragment 开发 Android 应用程序时遇到了一些问题。目前,当我切换 fragment 时,我只是隐藏 SupportMapFragment 并将 CameraPosition 保存在 SharedPreferences 中。然后,当我再次显示 map 时,它会从 SharedPreferences 加载 CameraPosition。这工作正常,但 map 本身需要再次加载。必须有一种方法来保存 map ,几乎不需要任何时间来弹出备份,比如将它移动到背景或其他什么?谁能帮我这个?谢谢!

最佳答案

一些截图证明我做到了:

Multipane mode of my app

我的应用的多面板模式

Moved to singlepane and selected the map('Kort' in danish) tab

移动到单 Pane 并选择 map (丹麦语中的“Kort”)选项卡。

发生的事情是:

  1. 保存 map fragment 的字段变量设置为单 Pane map fragment ,如果是第一次看到 map 则新创建,或者使用 findFragmentByTag 恢复。 在这种情况下,它使用来自多 Pane 模式的 fragment 恢复。
  2. 因为 map 是使用现有 fragment 恢复的,所以 map 的 GoogleMap 实例将与我们存储在 GoogleMapModule 中的实例相同(见下文),因此状态得以保留。

这会使 map 立即出现,并带有标记等,就像它们处于多 Pane 模式一样。


将对 map fragment 的引用存储为字段变量,并尽可能使用它。这样您只需担心一个 map 实例(使用 findFragmentByTag 重新实例化它)。

其次,为了存储 map 的状态,我将 GoogleMap 存储在独立于 Activity(与应用程序生命周期相关的单例)的 Activity 中,您可以通过在 map fragment 上调用 ​​getMap() 获得它。也就是说,我的 map fragment 将尽可能获取存储的 GoogleMap 对象并将其用于所有操作(缩放、标记等)。

这是我使用的 GoogleMapModule(如果您可以使用其中的一部分):

@Singleton
public class GoogleMapsModule extends StandardModule {

    private final GoogleMapsModulePreferences modulePreferences;

    public class GoogleMapsModulePreferences implements ModulePreferences {

        @Override public boolean isActivated() {
            return isActivated();
        }

        public int getAddressEntry() {
            return preferences
                    .getStringInt(R.string.key_googlemaps_address_entry);
        }

        public int getCityEntry() {
            return preferences.getStringInt(R.string.key_googlemaps_city_entry);
        }

    }

    public GoogleMapsModulePreferences getPreferences() {
        return modulePreferences;
    }

    public interface GoogleMapsCallback {
        public void mapReady(GoogleMap map);
    }

    public interface FetchAddressCallback {
        public void addressFound(AddressHolder addressHolder);

        public void addressLookupFailed();
    }

    private static final String TAG = "GoogleMapsModule";

    public static final int MAPTYPE_NORMAL = 0;
    public static final int MAPTYPE_SATELITE = 1;
    public static final int MAPTYPE_TERRAIN = 2;
    public static final int MAPTYPE_HYBRID = 3;

    private AddressHolder addressPin;

    private GoogleMap mGoogleMap;
    private Geocoder geocoder;

    private GoogleMapsCallback googleMapsCallback;

    private final Preferences preferences;

    @Inject public GoogleMapsModule(@ForApplication Context context,
            Preferences preferences, ExpirationCoreModule expiration,
            ParseCoreModule parse) {
        super(context, preferences, expiration, parse, MODULES.GOOGLEMAPS);

        this.modulePreferences = new GoogleMapsModulePreferences();
        this.preferences = preferences;

        Log.i(TAG, "CREATING MODULE " + TAG);

        geocoder = new Geocoder(context, new Locale("da_DK"));
    }

    @Override public void load() {

        MapsInitializer.initialize(context);
        loadEnded();

    }

    public void setGoogleMapsCallback(GoogleMapsCallback mapReadyCallback) {
        this.googleMapsCallback = mapReadyCallback;
    }

    public void destroyMap() {
        mGoogleMap = null;
    };

    public void clearMap() {
        clearMap(false);
    }

    public void clearMap(boolean keepAddressPin) {
        this.googleMapsCallback = null;

        if (mGoogleMap != null) {
            mGoogleMap.clear();

            if (keepAddressPin && addressPin != null) {
                addAddressPin(addressPin);
            }
        }
    }

    public void setMap(GoogleMap map) {
        if (mGoogleMap == null) {
            mGoogleMap = map;
            mGoogleMap.setMyLocationEnabled(true);
            mGoogleMap.setTrafficEnabled(true);
            mGoogleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
        }

        if (googleMapsCallback != null) {
            googleMapsCallback.mapReady(mGoogleMap);
        }
    }

    public GoogleMap getMap() {
        return mGoogleMap;
    }

    public void setMapType(int maptype) {
        if (mGoogleMap != null) {
            mGoogleMap.setMapType(maptype);
        }
    }

    public String mapType(int maptype) {
        switch (maptype) {
        case MAPTYPE_NORMAL:

            return "Normal";
        case MAPTYPE_SATELITE:

            return "Satelite";
        case MAPTYPE_TERRAIN:

            return "Terrain";
        case MAPTYPE_HYBRID:

            return "Hybrid";

        default:
            return "Normal";
        }
    }

    public void zoomWithBounds(LatLngBounds bounds, int padding)
            throws IllegalStateException {
        CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, padding);

        getMap().animateCamera(cu);

    }

    public void addLocationPin(Location location) {
        mGoogleMap.addMarker(new MarkerOptions().position(
                new LatLng(location.getLatitude(), location.getLongitude()))
                .icon(BitmapDescriptorFactory
                        .defaultMarker(BitmapDescriptorFactory.HUE_RED)));
    }

    public void addAddressPin(AddressHolder addressHolder) {
        if (addressHolder.position != null) {

            String city = addressHolder.city;
            String address = addressHolder.address;

            // address or city or empty
            String title = (address != null && !address.isEmpty()) ? address
                    : ((city != null) ? city : "");

            MarkerOptions markerOptions = new MarkerOptions()
                    .position(addressHolder.position)
                    .title(title)
                    .icon(BitmapDescriptorFactory
                            .defaultMarker(BitmapDescriptorFactory.HUE_ORANGE));

            Marker destMarker = mGoogleMap.addMarker(markerOptions);

            destMarker.showInfoWindow();

            addressPin = addressHolder;

        }
    }

    public void moveTo(AddressHolder addressHolder) {

        GoogleMap map = getMap();

        CameraUpdate center = CameraUpdateFactory
                .newLatLng(addressHolder.position);

        CameraUpdate zoom = CameraUpdateFactory.zoomTo(16);

        map.moveCamera(center);
        map.animateCamera(zoom);
    }

    public void zoomTo(Location location) {
        if (location == null) {
            return;
        }

        zoomTo(new LatLng(location.getLatitude(), location.getLongitude()));
    }

    public void zoomTo(AddressHolder addressHolder) {
        if (getMap() == null || addressHolder.position == null) {
            Log.e(TAG, "zoomTo map or address position was null: map "
                    + (getMap() == null) + " address position "
                    + (addressHolder.position == null));
            return;
        }

        addAddressPin(addressHolder);

        // Zoom in, animating the camera.
        zoomTo(addressHolder.position);

    }

    private void zoomTo(LatLng latlng) {

        GoogleMap map = getMap();

        if (getMap() == null || latlng == null) {
            return;
        }

        // Zoom in, animating the camera.
        map.animateCamera(CameraUpdateFactory.newLatLngZoom(latlng, 16));
    }

    public void lookupAddress(final FetchAddressCallback fetchAddressCallback,
            String address, String city) {

        new GetAddressPositionTask(fetchAddressCallback, address, city)
                .execute();
    }

    public void lookupAddress(final FetchAddressCallback fetchAddressCallback,
            String searchString) {

        new GetAddressPositionTask(fetchAddressCallback, searchString)
                .execute();
    }

    private class GetAddressPositionTask extends
            AsyncTask<String, Integer, AddressHolder> {

        private FetchAddressCallback fetchAddressPositionCallback;

        private String searchString;

        private String city = "";
        private String address = "";

        public GetAddressPositionTask(
                FetchAddressCallback fetchAddressPositionCallback,
                String address, String city) {
            this.fetchAddressPositionCallback = fetchAddressPositionCallback;

            this.city = city;
            this.address = address;
            this.searchString = address + ", " + city;
        }

        public GetAddressPositionTask(
                FetchAddressCallback fetchAddressPositionCallback,
                String searchString) {
            this.fetchAddressPositionCallback = fetchAddressPositionCallback;

            this.searchString = searchString;
        }

        @Override protected void onPreExecute() {
            super.onPreExecute();
        }

        @Override protected AddressHolder doInBackground(String... params) {

            final String lookupStringUriencoded = Uri.encode(searchString);

            LatLng position = null;

            try {
                if (geocoder != null) {
                    List<Address> addresses = geocoder.getFromLocationName(
                            searchString, 1);
                    if (addresses != null && !addresses.isEmpty()) {
                        Address first_address = addresses.get(0);

                        String foundCity = first_address.getLocality();

                        if (foundCity != null) {
                            city = (city.isEmpty()) ? foundCity : city;
                        }

                        String addressName = first_address.getThoroughfare();
                        String streetNumber = first_address
                                .getSubThoroughfare();

                        // if (addressName != null && address.isEmpty()) {
                        address = (streetNumber != null) ? addressName + " "
                                + streetNumber : addressName;
                        // }
                        position = new LatLng(first_address.getLatitude(),
                                first_address.getLongitude());
                        Log.d(TAG, "geocoder was found " + position);
                    }
                } else {
                    Log.e(TAG, "geocoder was null, is the module loaded?");
                }

            } catch (IOException e) {
                // Log.e(TAG, "geocoder failed, moving on to HTTP");
            }
            // try HTTP lookup to the maps API
            if (position == null) {
                HttpGet httpGet = new HttpGet(
                        "http://maps.google.com/maps/api/geocode/json?address="
                                + lookupStringUriencoded + "&sensor=true");
                HttpClient client = new DefaultHttpClient();
                HttpResponse response;
                StringBuilder stringBuilder = new StringBuilder();

                try {
                    response = client.execute(httpGet);
                    HttpEntity entity = response.getEntity();
                    InputStream stream = entity.getContent();
                    int b;
                    while ((b = stream.read()) != -1) {
                        stringBuilder.append((char) b);
                    }
                } catch (ClientProtocolException e) {
                    Log.e(TAG, e.getMessage(), e);
                } catch (IOException e) {
                    Log.e(TAG, e.getMessage(), e);
                }

                JSONObject jsonObject = new JSONObject();
                try {
                    // Log.d("MAPSAPI", stringBuilder.toString());

                    jsonObject = new JSONObject(stringBuilder.toString());
                    if (jsonObject.getString("status").equals("OK")) {
                        jsonObject = jsonObject.getJSONArray("results")
                                .getJSONObject(0);

                        JSONArray address_components = jsonObject
                                .getJSONArray("address_components");

                        String jsonCity = "";
                        String jsonAddress = "";
                        String jsonStreetNumber = "";

                        // extract looked up address information
                        for (int i = 0; i < address_components.length(); i++) {
                            JSONObject address_component = address_components
                                    .getJSONObject(i);
                            String type = address_component.getJSONArray(
                                    "types").getString(0);

                            String value = address_component
                                    .getString("long_name");

                            if (type.equals("locality")) {
                                jsonCity = value;
                            }

                            if (type.equals("route")) {
                                jsonAddress = value;
                            }

                            if (type.equals("street_number")) {
                                jsonStreetNumber = value;
                            }

                        }

                        Log.d("MAPSAPI", jsonCity + "," + jsonAddress + " "
                                + jsonStreetNumber);

                        city = (city.isEmpty()) ? jsonCity : city;

                        address = (address.isEmpty()) ? (jsonAddress + " " + jsonStreetNumber)
                                .trim() : address;

                        // extract position
                        jsonObject = jsonObject.getJSONObject("geometry");
                        jsonObject = jsonObject.getJSONObject("location");
                        String lat = jsonObject.getString("lat");
                        String lng = jsonObject.getString("lng");

                        Log.d("MAPSAPI", "latlng " + lat + ", " + lng);

                        position = new LatLng(Double.valueOf(lat),
                                Double.valueOf(lng));
                    }

                } catch (JSONException e) {
                    Log.e(TAG, e.getMessage(), e);
                }

            }
            return new AddressHolder(address, city, position);
        }

        @Override protected void onPostExecute(final AddressHolder result) {
            Log.d(TAG, "GetAddressPositionTask " + result);
            if (result.position != null) {
                fetchAddressPositionCallback.addressFound(result);
            } else {
                fetchAddressPositionCallback.addressLookupFailed();
            }
            // ensure no more callbacks to this
            fetchAddressPositionCallback = null;
            super.onPostExecute(result);
        }

    }

}

我已经实现了 Dagger http://square.github.io/dagger/进入我的应用程序,它解释了@Singleton,这使我能够这样做:

@Inject GoogleMapsModule googleMapsModule;

每当我想在我的代码中的任何地方使用这个对象时。不过,我认为您可以通过扩展 Application 来简单地存储您的 map 数据。例如,您可以阅读此博客:http://www.devahead.com/blog/2011/06/extending-the-android-application-class-and-dealing-with-singleton/

为了在 GoogleMapsModule 中保持正确的引用,我执行以下操作:

@Override public void onMapReady(GoogleMap map) {
    if (map != null) {
        googleMapsModule.setMap(map);
    }
}

现在,最后一个松散的结局是,如果 Activity 从头开始​​,而 GoogleMapModule 持有一个引用,那么在 map 上完成的任何操作都将无效,因为引用不再与 map 绑定(bind)。为了处理这个问题,如果 savedInstanceState 为 null,我会销毁 map :

@Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
         ...
    if (savedInstanceState != null) {
         // all is well
        mapsFragment = (GoogleMapFragment) fm
                .findFragmentByTag(FRAGMENT_MAP_TAG);
    } else {
        // no saved instance - destroy the map to enable onMapReady setting a new reference
        googleMapsModule.destroyMap();
    }

    if (device.isMultiPane()) {
        addMapFragment(ft).commit();
    }
}

// called when the map tab is selected or from onCreate if multipane
private FragmentTransaction addMapFragment(FragmentTransaction ft) {
    if (mapsFragment == null) {
        AddressHolder address = (device.isMultiPane()) ? null
                : getSelectedAddressHolder();
        mapsFragment = GoogleMapFragment.newInstance(address);
        ft.add(R.id.details, mapsFragment, Turios.FRAGMENT_MAP_TAG);

        mapsOptionsFragment = new GoogleMapOptionsFragment();
        ft.add(R.id.details, mapsOptionsFragment, FRAGMENT_MAP_OPTIONS_TAG);
    } else {
        ft.attach(mapsFragment);
        ft.attach(mapsOptionsFragment);
    }
    return ft;
}

最后,Android 的默认行为是启动一个新的 Activity,执行 onCreate,每次启动应用程序,或者从另一个 Activity 返回等。因此我在 list 中将 launchMode 设置为 singleTask。

    <activity
        android:name=".activities.Turios"
        android:alwaysRetainTaskState="true"
        android:clearTaskOnLaunch="true"
        android:launchMode="singleTask"
         >

        <intent-filter>
            <category android:name="android.intent.category.DEFAULT" />
            <action android:name="android.intent.action.SEARCH" />
        </intent-filter>

        <meta-data
            android:name="android.app.searchable"
            android:resource="@xml/searchable_address" />
    </activity>

此处解释了启动模式:http://developer.android.com/guide/topics/manifest/activity-element.html#lmode

关于android - 防止 Google SupportMapFragment 重新加载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22224326/

相关文章:

android - 在 Android Studio 中始终显示错误已拾取 _JAVA_OPTION -Xmx512M

android - 即使LiveData仅更新一次,也可以从协同程序发布到LiveData到更新UI

android - 如何在 Android 中制作这样的列表?

java - ActionBarActivity 返回 api10f 的 ListFragment 时出现问题

google-maps - 如何显示android mapfragment View 的圆形

android - 如何创建和使用自托管对象在 Android 中共享自定义故事?

ios - 无法将 'NSTaggedPointerString' 类型的值转换为 Google Place Api 中的“NSArray”

android - 在 Android 谷歌地图上缩放不正确的行为

google-maps - 如何在 flutter 中在谷歌地图上绘制多段线

android - 如何从 url 图像到 InfoWindowAdapter android?