[英]IndexOutOfBoundsException for MapView in RecyclerView
I'm getting IndexOutOfBoundsException
exception when I try to update the adapter containing the MapView
.当我尝试更新包含
MapView
的适配器时,出现IndexOutOfBoundsException
异常。
I'm using example LiteListDemoActivity .我正在使用示例LiteListDemoActivity 。 Once it's static everything is fine, but when I want to submit an update to the adapter to show a different type of data not map related, then in some rare cases the app crashes.
一旦它是静态的,一切都很好,但是当我想向适配器提交更新以显示与地图无关的不同类型的数据时,在极少数情况下应用程序会崩溃。
The beginning of the stack trace is:堆栈跟踪的开头是:
java.lang.IndexOutOfBoundsException:
at java.util.ArrayList.get (ArrayList.java:437)
at com.google.maps.api.android.lib6.lite.u.a (u.java:4)
at com.google.maps.api.android.lib6.lite.x.a (x.java:15)
at com.google.maps.api.android.lib6.lite.j.onDraw (j.java:48)
at android.view.View.draw (View.java:21860)
at android.view.View.buildDrawingCacheImpl (View.java:21129)
at android.view.View.buildDrawingCache (View.java:20989)
at android.view.View.updateDisplayListIfDirty (View.java:20705)
at android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:4535)
at android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:4507)
at android.view.View.updateDisplayListIfDirty (View.java:20688)
at android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:4535)
at android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:4507)
at android.view.View.updateDisplayListIfDirty (View.java:20688)
at android.view.View.draw (View.java:21586)
at android.view.ViewGroup.drawChild (ViewGroup.java:4551)
at android.view.ViewGroup.dispatchDraw (ViewGroup.java:4326)
at androidx.constraintlayout.widget.ConstraintLayout.dispatchDraw (ConstraintLayout.java:2023)
at android.view.View.updateDisplayListIfDirty (View.java:20719)
at android.view.View.draw (View.java:21586)
at android.view.ViewGroup.drawChild (ViewGroup.java:4551)
at androidx.recyclerview.widget.RecyclerView.drawChild (RecyclerView.java:4820)
at android.view.ViewGroup.dispatchDraw (ViewGroup.java:4326)
at android.view.View.draw (View.java:21863)
at androidx.recyclerview.widget.RecyclerView.draw (RecyclerView.java:4219)
Is it something related to the RecyclerView implementation?它与 RecyclerView 实现有关吗? I am sharing the adapter from my code.
我正在从我的代码中共享适配器。 You can find the full implementation in the link above that I shared.
您可以在我分享的上面链接中找到完整的实现。
/**
* Adapter that displays a title and {@link com.google.android.gms.maps.MapView} for each item.
* The layout is defined in <code>lite_list_demo_row.xml</code>. It contains a MapView
* that is programatically initialised in
* {@link #(int, android.view.View, android.view.ViewGroup)}
*/
private class MapAdapter extends RecyclerView.Adapter<MapAdapter.ViewHolder> {
private NamedLocation[] namedLocations;
private MapAdapter(NamedLocation[] locations) {
super();
namedLocations = locations;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.lite_list_demo_row, parent, false));
}
/**
* This function is called when the user scrolls through the screen and a new item needs
* to be shown. So we will need to bind the holder with the details of the next item.
*/
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (holder == null) {
return;
}
holder.bindView(position);
}
@Override
public int getItemCount() {
return namedLocations.length;
}
/**
* Holder for Views used in the {@link LiteListDemoActivity.MapAdapter}.
* Once the the <code>map</code> field is set, otherwise it is null.
* When the {@link #onMapReady(com.google.android.gms.maps.GoogleMap)} callback is received and
* the {@link com.google.android.gms.maps.GoogleMap} is ready, it stored in the {@link #map}
* field. The map is then initialised with the NamedLocation that is stored as the tag of the
* MapView. This ensures that the map is initialised with the latest data that it should
* display.
*/
class ViewHolder extends RecyclerView.ViewHolder implements OnMapReadyCallback {
MapView mapView;
TextView title;
GoogleMap map;
View layout;
private ViewHolder(View itemView) {
super(itemView);
layout = itemView;
mapView = layout.findViewById(R.id.lite_listrow_map);
title = layout.findViewById(R.id.lite_listrow_text);
if (mapView != null) {
// Initialise the MapView
mapView.onCreate(null);
// Set the map ready callback to receive the GoogleMap object
mapView.getMapAsync(this);
}
}
@Override
public void onMapReady(GoogleMap googleMap) {
MapsInitializer.initialize(getApplicationContext());
map = googleMap;
setMapLocation();
}
/**
* Displays a {@link LiteListDemoActivity.NamedLocation} on a
* {@link com.google.android.gms.maps.GoogleMap}.
* Adds a marker and centers the camera on the NamedLocation with the normal map type.
*/
private void setMapLocation() {
if (map == null) return;
NamedLocation data = (NamedLocation) mapView.getTag();
if (data == null) return;
// Add a marker for this item and set the camera
map.moveCamera(CameraUpdateFactory.newLatLngZoom(data.location, 13f));
map.addMarker(new MarkerOptions().position(data.location));
// Set the map type back to normal.
map.setMapType(GoogleMap.MAP_TYPE_NORMAL);
}
private void bindView(int pos) {
NamedLocation item = namedLocations[pos];
// Store a reference of the ViewHolder object in the layout.
layout.setTag(this);
// Store a reference to the item in the mapView's tag. We use it to get the
// coordinate of a location, when setting the map location.
mapView.setTag(item);
setMapLocation();
title.setText(item.name);
}
}
}
I don't see any update strategy in your adapter that you have shared on Github.我在您的适配器中没有看到您在 Github 上共享的任何更新策略。 The adapter takes a fixed-length array in the constructor and thus gets the size of the fixed array from the
getItemCount
function.适配器在构造函数中使用一个固定长度的数组,从而从
getItemCount
函数中获取固定数组的大小。
I would like to suggest modifying the adapter like the following.我想建议修改适配器如下。
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapView;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
/**
* This shows to include a map in lite mode in a ListView.
* Note the use of the view holder pattern with the
* {@link com.google.android.gms.maps.OnMapReadyCallback}.
*/
public class LiteListDemoActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private LinearLayoutManager mLinearLayoutManager;
private GridLayoutManager mGridLayoutManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.lite_list_demo);
mGridLayoutManager = new GridLayoutManager(this, 2);
mLinearLayoutManager = new LinearLayoutManager(this);
// Set up the RecyclerView
mRecyclerView = findViewById(R.id.recycler_view);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setLayoutManager(mLinearLayoutManager);
// Pass the ArrayList instead of the array
mRecyclerView.setAdapter(new MapAdapter(getInitialLocations(LIST_LOCATIONS)));
mRecyclerView.setRecyclerListener(mRecycleListener);
}
/**
* Create a menu to switch between Linear and Grid LayoutManager.
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.lite_list_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.layout_linear:
mRecyclerView.setLayoutManager(mLinearLayoutManager);
break;
case R.id.layout_grid:
mRecyclerView.setLayoutManager(mGridLayoutManager);
break;
}
return true;
}
/**
* Adapter that displays a title and {@link com.google.android.gms.maps.MapView} for each item.
* The layout is defined in <code>lite_list_demo_row.xml</code>. It contains a MapView
* that is programatically initialised in
* {@link #(int, android.view.View, android.view.ViewGroup)}
*/
private class MapAdapter extends RecyclerView.Adapter<MapAdapter.ViewHolder> {
// Take an ArrayList instead of array. Its easier to implement the add and clear functionality here
private ArrayList<NamedLocation> namedLocations;
private MapAdapter(ArrayList<NamedLocation> locations) {
super();
namedLocations = locations;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.lite_list_demo_row, parent, false));
}
/**
* This function is called when the user scrolls through the screen and a new item needs
* to be shown. So we will need to bind the holder with the details of the next item.
*/
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (holder == null) {
return;
}
holder.bindView(position);
}
@Override
public int getItemCount() {
return namedLocations.size();
}
public void updateLocationList(ArrayList<NamedLocation> newLocations) {
// Clear the old locations
this.namedLocations.clear();
// Add the new locations passed by calling this function to the list associated with this adapter.
for (NamedLocation location : newLocations) this.namedLocations.add(location);
// Call notifyDataSetChanged so that the RecyclerView takes the new ArrayList to be populated in the RecyclerView
notifyDataSetChanged();
}
/**
* Holder for Views used in the {@link LiteListDemoActivity.MapAdapter}.
* Once the the <code>map</code> field is set, otherwise it is null.
* When the {@link #onMapReady(com.google.android.gms.maps.GoogleMap)} callback is received and
* the {@link com.google.android.gms.maps.GoogleMap} is ready, it stored in the {@link #map}
* field. The map is then initialised with the NamedLocation that is stored as the tag of the
* MapView. This ensures that the map is initialised with the latest data that it should
* display.
*/
class ViewHolder extends RecyclerView.ViewHolder implements OnMapReadyCallback {
MapView mapView;
TextView title;
GoogleMap map;
View layout;
private ViewHolder(View itemView) {
super(itemView);
layout = itemView;
mapView = layout.findViewById(R.id.lite_listrow_map);
title = layout.findViewById(R.id.lite_listrow_text);
if (mapView != null) {
// Initialise the MapView
mapView.onCreate(null);
// Set the map ready callback to receive the GoogleMap object
mapView.getMapAsync(this);
}
}
@Override
public void onMapReady(GoogleMap googleMap) {
MapsInitializer.initialize(getApplicationContext());
map = googleMap;
setMapLocation();
}
/**
* Displays a {@link LiteListDemoActivity.NamedLocation} on a
* {@link com.google.android.gms.maps.GoogleMap}.
* Adds a marker and centers the camera on the NamedLocation with the normal map type.
*/
private void setMapLocation() {
if (map == null) return;
NamedLocation data = (NamedLocation) mapView.getTag();
if (data == null) return;
// Add a marker for this item and set the camera
map.moveCamera(CameraUpdateFactory.newLatLngZoom(data.location, 13f));
map.addMarker(new MarkerOptions().position(data.location));
// Set the map type back to normal.
map.setMapType(GoogleMap.MAP_TYPE_NORMAL);
}
private void bindView(int pos) {
NamedLocation item = namedLocations.get(pos);
// Store a reference of the ViewHolder object in the layout.
layout.setTag(this);
// Store a reference to the item in the mapView's tag. We use it to get the
// coordinate of a location, when setting the map location.
mapView.setTag(item);
setMapLocation();
title.setText(item.name);
}
}
}
/**
* RecycleListener that completely clears the {@link com.google.android.gms.maps.GoogleMap}
* attached to a row in the RecyclerView.
* Sets the map type to {@link com.google.android.gms.maps.GoogleMap#MAP_TYPE_NONE} and clears
* the map.
*/
private RecyclerView.RecyclerListener mRecycleListener = new RecyclerView.RecyclerListener() {
@Override
public void onViewRecycled(RecyclerView.ViewHolder holder) {
MapAdapter.ViewHolder mapHolder = (MapAdapter.ViewHolder) holder;
if (mapHolder != null && mapHolder.map != null) {
// Clear the map and free up resources by changing the map type to none.
// Also reset the map when it gets reattached to layout, so the previous map would
// not be displayed.
mapHolder.map.clear();
mapHolder.map.setMapType(GoogleMap.MAP_TYPE_NONE);
}
}
};
/**
* Location represented by a position ({@link com.google.android.gms.maps.model.LatLng} and a
* name ({@link java.lang.String}).
*/
private static class NamedLocation {
public final String name;
public final LatLng location;
NamedLocation(String name, LatLng location) {
this.name = name;
this.location = location;
}
}
private ArrayList<NamedLocation> getInitialLocations(NamedLocation[] locationsArray) {
ArrayList<NamedLocation> locationArrayList = new ArrayList<>();
for (NamedLocation location : locationsArray)
locationArrayList.add(location);
return locationArrayList;
}
/**
* A list of locations to show in this ListView.
*/
private static final NamedLocation[] LIST_LOCATIONS = new NamedLocation[]{
new NamedLocation("Cape Town", new LatLng(-33.920455, 18.466941)),
new NamedLocation("Beijing", new LatLng(39.937795, 116.387224)),
new NamedLocation("Bern", new LatLng(46.948020, 7.448206)),
new NamedLocation("Breda", new LatLng(51.589256, 4.774396)),
new NamedLocation("Brussels", new LatLng(50.854509, 4.376678)),
new NamedLocation("Copenhagen", new LatLng(55.679423, 12.577114)),
new NamedLocation("Hannover", new LatLng(52.372026, 9.735672)),
new NamedLocation("Helsinki", new LatLng(60.169653, 24.939480)),
new NamedLocation("Hong Kong", new LatLng(22.325862, 114.165532)),
new NamedLocation("Istanbul", new LatLng(41.034435, 28.977556)),
new NamedLocation("Johannesburg", new LatLng(-26.202886, 28.039753)),
new NamedLocation("Lisbon", new LatLng(38.707163, -9.135517)),
new NamedLocation("London", new LatLng(51.500208, -0.126729)),
new NamedLocation("Madrid", new LatLng(40.420006, -3.709924)),
new NamedLocation("Mexico City", new LatLng(19.427050, -99.127571)),
new NamedLocation("Moscow", new LatLng(55.750449, 37.621136)),
new NamedLocation("New York", new LatLng(40.750580, -73.993584)),
new NamedLocation("Oslo", new LatLng(59.910761, 10.749092)),
new NamedLocation("Paris", new LatLng(48.859972, 2.340260)),
new NamedLocation("Prague", new LatLng(50.087811, 14.420460)),
new NamedLocation("Rio de Janeiro", new LatLng(-22.90187, -43.232437)),
new NamedLocation("Rome", new LatLng(41.889998, 12.500162)),
new NamedLocation("Sao Paolo", new LatLng(-22.863878, -43.244097)),
new NamedLocation("Seoul", new LatLng(37.560908, 126.987705)),
new NamedLocation("Stockholm", new LatLng(59.330650, 18.067360)),
new NamedLocation("Sydney", new LatLng(-33.873651, 151.2068896)),
new NamedLocation("Taipei", new LatLng(25.022112, 121.478019)),
new NamedLocation("Tokyo", new LatLng(35.670267, 139.769955)),
new NamedLocation("Tulsa Oklahoma", new LatLng(36.149777, -95.993398)),
new NamedLocation("Vaduz", new LatLng(47.141076, 9.521482)),
new NamedLocation("Vienna", new LatLng(48.209206, 16.372778)),
new NamedLocation("Warsaw", new LatLng(52.235474, 21.004057)),
new NamedLocation("Wellington", new LatLng(-41.286480, 174.776217)),
new NamedLocation("Winnipeg", new LatLng(49.875832, -97.150726))
};
}
Now you have the updateLocationList
function in your adapter and when you need to change the location list, just use this function to pass your updated location list in your adapter.现在您的适配器中有
updateLocationList
函数,当您需要更改位置列表时,只需使用此函数将更新的位置列表传递到您的适配器中。
Please note that I have not tested this code.请注意,我尚未测试此代码。 So, it might have some compilation error as I have changed the array implementation to a list.
因此,当我将数组实现更改为列表时,它可能会出现一些编译错误。 Please modify as per your need.
请根据您的需要进行修改。
Hope that helps!希望有帮助!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.