【问题标题】:Android Adapter "java.lang.IndexOutOfBoundsException: Invalid index 4, size is 4"Android 适配器“java.lang.IndexOutOfBoundsException:索引 4 无效,大小为 4”
【发布时间】:2015-12-24 20:57:49
【问题描述】:

我在删除 ArrayList 中的项目和同步 Adapter 时遇到问题。 我有我的RecyclerView 适配器,里面有一些ArrayList,称为items。我从服务器下载了一些列表并在其中显示。每当我单击某些列表项时,我想从服务器、本地 ArrayList 中删除它并通知适配器。问题是,当我从列表中删除从 downup 的所有内容时,一切正常,但是当 f.e.我从列表中删除第一个元素,然后在我单击的那个元素之后随机删除一些元素。在某些情况下,应用程序崩溃(例如,我删除了第一个元素,然后删除了最后一个元素)。我得到的错误是 f.e.:

java.lang.IndexOutOfBoundsException: Invalid index 4, size is 4

看起来好像是列表大小的问题,但我不知道出了什么问题?

这是我从 (setPopUpListener(popupMenu, position)) 获得位置的函数:

// Binding New View
    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        RecipeItem item = items.get(position);

        // Binding Recipe Image
        Picasso.with(context).load(item.getImgThumbnailLink()).into(holder.recipeItemImage);
        // Binding Recipe Title
        holder.recipeItemTitle.setText(item.getTitle());
        // Binding Recipe Subtitle
        String subtitle = "Kuchnia " + item.getKitchenType() + ", " + item.getMealType();
        holder.recipeItemSubtitle.setText(subtitle);
        // Binding Recipe Likes Count
        holder.recipeItemLikesCount.setText(Integer.toString(item.getLikeCount()));
        // Binding Recipe Add Date
        holder.recipeItemAddDate.setText(item.getAddDate());
        // Binding Recipe Options Icon
        holder.recipeItemOptionsIcon.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                PopupMenu popupMenu = new PopupMenu(context, v);
                setPopUpListener(popupMenu, position);   // Setting Popup Listener
                inflatePopupMenu(popupMenu);             // Inflating Correct Menu
                popupMenu.show();
            }

        });
        // Item Click Listener
        holder.setClickListener(new RecipeItemClickListener() {

            @Override
            public void onClick(View view, int position) {
                // taking to recipe activity
            }

        });
    }

这里是setPopUpListener() - 看看removeFromFavourites(position)

// Setting Popup Listener
private void setPopUpListener(PopupMenu popupMenu, final int position) {
    popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {

        @Override
        public boolean onMenuItemClick(MenuItem item) {
            switch (popupType) {
                // Add To Favourites Menu
                case 0: {
                    switch (item.getItemId()) {
                        case R.id.item_add: {
                            addToFavourites(position);
                            return true;
                        }
                    }
                }

                // Remove From Favourites Menu
                case 1: {
                    switch (item.getItemId()) {
                        case R.id.item_remove: {
                            removeFromFavourites(position);
                            return true;
                        }
                    }
                }
            }
            return false;
        }

    });
}

这里是出现错误的地方 (removeFromFavourites(position)):

  // Removing User's Favourite
private void removeFromFavourites(int position) {
    // Checking Connection Status
    if (!FormValidation.isOnline(context)) {
        showSnackbarInfo(context.getString(R.string.err_msg_connection_problem),
                R.color.snackbar_error_msg);
    } else {
        SQLiteHandler db = new SQLiteHandler(context);
        // Getting User Unique ID
        String userUniqueId = db.getUserUniqueId();
        db.close();

        RecipeItem listItem = items.get(position);
        // Getting Recipe Unique ID
        String recipeUniqueId = listItem.getUniqueId();

        // Removing From User's Favourites
        removeFromUserFavouritesOnServer(recipeUniqueId, userUniqueId);

        // Removing Item From Local Array List
        items.remove(position);

        // Notifying Adapter That Item Has Been Removed
        notifyItemRemoved(position);
    }
}

【问题讨论】:

    标签: android indexing android-recyclerview notifydatasetchanged


    【解决方案1】:

    解决方案 - 希望这能帮助某人

    我已经找到了解决办法。如果有人试图从他的数组列表中动态删除元素并且notifyItemRemoved(position) 不要将点击位置作为参数发送到onBindViewHolder(ViewHolder holder, int position) 中。你会遇到和我一模一样的情况。

    如果列表中有 4 个显示元素 f.e. [0, 1, 2, 3] 并尝试从列表末尾删除一切都会好起来的,因为 clicked positions 将匹配完全相同的 positions in ArrayList。例如,如果您单击第 4 个元素:

    position = 3 - 单击列表元素时您将获得的位置; myArray.remove(position) - 将删除带有 index = 3notifyItemRemoved(position) 的元素 - 将为列表设置动画并从显示的列表中删除已删除的元素。您将获得以下列表:[0, 1, 2]。这很好。

    当您想删除随机元素时情况会发生变化。假设我想删除第三个显示的列表元素。我点击它删除所以我得到:

    position = 2 -> myArray.remove(position) -> notifyItemRemoved(position)

    在这种情况下,我将获得的 ArrayList 将是这样的:[0, 1, 3]。我现在点击最后一个显示的元素并想删除它,这就是我将得到的:

    position = 3->myArray.remove(position)->notifyItemRemoved(position)

    但是会发生什么?应用突然崩溃异常:java.lang.IndexOutOfBoundsException: Invalid index 3, size is 3。这意味着我们试图在不存在的位置获取元素。但为什么?我从元素中获得了我的点击位置......这就是发生的事情:

    At the beggining we had:

    数组列表索引 -> [0, 1, 2, 3]

    点击位置 -> [0, 1, 2, 3]

    After Deleting 3rd element:

    数组列表索引 -> [0, 1, 2]

    点击位置 -> [0, 1, 3]

    现在,当我尝试删除 position = 3 的元素时,我们不能这样做。我们没有那个位置。我们可以获得的最大位置是2。这就是我们得到异常的原因。如何解决这个问题?

    onBindViewHolder(ViewHolder holder, int position) 中,我们使用了 positionremoveFromFavourites(position)。但我们也有我们返回的holder。如果我们使用名为:getAdapterPosition() from class RecyclerView.ViewHolder 的方法,我们就在家。

    getAdapterPosition

    来自开发者网站:http://developer.android.com/reference/android/support/v7/widget/RecyclerView.ViewHolder.html#getAdapterPosition()

    这将始终返回与ArrayList 中的索引相同的索引。所以总结一下我们所要做的就是用holder.getAdapterPosition()改变position参数:

    // Binding New View
        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            RecipeItem item = items.get(position);
    
            // Binding Recipe Image
            Picasso.with(context).load(item.getImgThumbnailLink()).into(holder.recipeItemImage);
            // Binding Recipe Title
            holder.recipeItemTitle.setText(item.getTitle());
            // Binding Recipe Subtitle
            String subtitle = "Kuchnia " + item.getKitchenType() + ", " + item.getMealType();
            holder.recipeItemSubtitle.setText(subtitle);
            // Binding Recipe Likes Count
            holder.recipeItemLikesCount.setText(Integer.toString(item.getLikeCount()));
            // Binding Recipe Add Date
            holder.recipeItemAddDate.setText(item.getAddDate());
            // Binding Recipe Options Icon
            holder.recipeItemOptionsIcon.setOnClickListener(new View.OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    PopupMenu popupMenu = new PopupMenu(context, v);
                    setPopUpListener(popupMenu, holder.getAdapterPosition());   // Setting Popup Listener
                    inflatePopupMenu(popupMenu);             // Inflating Correct Menu
                    popupMenu.show();
                }
    
            });
            // Item Click Listener
            holder.setClickListener(new RecipeItemClickListener() {
    
                @Override
                public void onClick(View view, int position) {
                    // taking to recipe activity
                }
    
            });
        }
    

    【讨论】:

    • 为什么holder.getAdapterPosition()返回正确的数据而position没有?
    【解决方案2】:

    使用

    notifyItemRangeChanged(position, getItemCount()); 
    

    之后

    notifyItemRemoved(position);
    

    你不需要使用索引,只需使用位置。

    【讨论】:

    • 我按照你说的做了,但是当我从列表顶部删除第一个元素时,我得到了底部的最后一个元素:java.lang.IndexOutOfBoundsException: Invalid index 7, size is 7
    • 是的,应用程序在任何元素上都会崩溃。
    【解决方案3】:

    您正在更改内部数据结构,这不是一个好方法

    建议:

    1. 在添加/删除操作的情况下,对传递给适配器的主数组(例如 ArrayList)执行操作
    2. 切勿将主数组(传递给适配器)初始化为 null,这将丢失适配器引用
    3. 添加/删除后,只需调用 adapter.notifyDataSetChanged(); 获取适配器

    【讨论】:

    • 但是有没有办法处理从 ArrayList 中删除单个项目的位置?例如,我有位置列表:0、1、2、3、4。一开始我删除指定位置时可以:数组中项目的位置=项目点击的位置。当我删除 4, 3, 2, 1, 0 时没关系,但是当我删除 f.e. 2 首先我有列表:0、1、3、4。如何从 ArrayList 中删除与单击项目完全相同的项目?
    • 您还需要更改外部引用数组。您只是在更改内部数组,这就是面临这个问题的原因。尝试遵循我的建议。您需要将点击位置传递给控制活动/片段,从那里您需要从参考数组中删除特定项目,然后调用adapter.notifyDataSetChanged();
    【解决方案4】:

    只需使用

            product_list.remove(product_list.get(position));
            notifyItemRemoved(position);
            notifyItemRangeChanged(position, itemCount);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-09
      • 2016-05-11
      • 2013-05-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多