简体   繁体   English

RecyclerView 中 MapView 的 IndexOutOfBoundsException

[英]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.

相关问题 RecyclerView中的MapView内存泄漏 - Memory leaks with MapView in a RecyclerView 单击RecyclerView项时出现IndexOutOfBoundsException - IndexOutOfBoundsException when clicking RecyclerView item 替换RecyclerView项时获取IndexOutOfBoundsException - Getting IndexOutOfBoundsException when replacing RecyclerView items 具有多种视图类型的RecyclerView适配器上的IndexOutOfBoundsException - IndexOutOfBoundsException on RecyclerView adapter with multiple view types RecyclerView 在向下滚动时崩溃( IndexOutOfBoundsException: Index: 2, Size: 2 ) - RecyclerView Crashes on Scrolldown ( IndexOutOfBoundsException: Index: 2, Size: 2 ) 使用 AsyncTask 和 notifyItemInserted() 重新填充 RecyclerView 后的 IndexOutOfBoundsException - IndexOutOfBoundsException after refilling RecyclerView with AsyncTask and notifyItemInserted() 每当我想删除 RecyclerView 项目时出现 IndexOutOfBoundsException - IndexOutOfBoundsException whenever I want to delete an RecyclerView item 滚动时RecyclerView获取IndexOutOfBoundsException(Retrofit2 +库部分RecyclerView) - RecyclerView gets IndexOutOfBoundsException when Scrolling (Retrofit2 + Library Section RecyclerView) 精简模式下的 MapView 导致 RecyclerView 无法正确滚动 - MapView in lite mode causes RecyclerView to not scroll correctly IndexOutOfBoundsException异常 - IndexOutOfBoundsException
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM