我在使用 SupportMapFragment 开发 Android 应用程序时遇到了一些问题。目前,当我切换 fragment 时,我只是隐藏 SupportMapFragment 并将 CameraPosition 保存在 SharedPreferences 中。然后,当我再次显示 map 时,它会从 SharedPreferences 加载 CameraPosition。这工作正常,但 map 本身需要再次加载。必须有一种方法来保存 map ,几乎不需要任何时间来弹出备份,比如将它移动到背景或其他什么?谁能帮我这个?谢谢!
最佳答案
一些截图证明我做到了:
我的应用的多面板模式
移动到单 Pane 并选择 map (丹麦语中的“Kort”)选项卡。
发生的事情是:
- 保存 map fragment 的字段变量设置为单 Pane map fragment ,如果是第一次看到 map 则新创建,或者使用 findFragmentByTag 恢复。 在这种情况下,它使用来自多 Pane 模式的 fragment 恢复。
- 因为 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/