SearchView 过滤器按预期工作,但在片段中检索 RecyclerView 上的旧项目位置

SearchView filter is working as intented, but retrieves old item position on RecyclerView in fragment

过去几天我一直在寻找我的问题的解决方案,但所有其他类似问题似乎都与 onClickListener 有关,或者他们的解决方案不适用于手头的问题.我在我的项目中使用的 onClickListeners 似乎按预期工作,只有 SearchView 不是。

我的起始列表包含一些项目及其 ingredients/materials,一切都按预期进行。当使用 SearchView 过滤时,新的过滤列表显示正确的项目名称但错误的成分。从字面上看是过滤前的旧位置,我似乎无法调试问题出在哪里。

MainAdapter.java

public class MainAdapter extends RecyclerView.Adapter<MainAdapter.ViewHolder> implements Filterable {
    private List<Item> mItemCard;
    private List<Item> mItemCardFull; //Needed for the getFilter() method

    static class ViewHolder extends RecyclerView.ViewHolder {


        // region Variables (Item Name, Icon, Expand Layout & Expand button)
        ImageView mItemIcon;
        TextView mItemName;
        ImageButton mExpandButton;
        // etc...

        // region Variables (Ingredients' Icons, Names & Amount)
        ImageView mIngredient_1_Icon;
        TextView mIngredient_1_Name;
        TextView mIngredient_1_amountTV;
        float mIngredient_1_amount;
        // etc...

        // region misc Variables
        boolean _isFirstTime = true; // Used in initialization & crafting method switch
        EditText editFactoryAmount;
        ImageView factoryAmountIcon;
        Button factoryAmountButton;
        // etc...


        ViewHolder(@NonNull View itemView) {
            super(itemView);
            mItemIcon = itemView.findViewById(R.id.itemIcon);
            mItemName = itemView.findViewById(R.id.itemName);
            mIngredient_1_Icon = itemView.findViewById(R.id.ingredient_1_icon);
            mIngredient_1_Name = itemView.findViewById(R.id.ingredient_1_name);
            mIngredient_1_amountTV = itemView.findViewById(R.id.ingredient_1_amount);
        // etc...
        }
    }

    public MainAdapter(List<Item> itemCard) {
        this.mItemCard = itemCard;
        this.mItemCardFull = new ArrayList<>(itemCard);
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
        final ViewHolder holder = new ViewHolder(v);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {
        final Item currentItem = mItemCard.get(position);

        final float craftingTime = currentItem.getCraftingTime();
        final float outputAmount = currentItem.getOutputAmount();

        holder.mItemIcon.setImageResource(currentItem.getItemIcon());
        holder.mItemName.setText(currentItem.getItemName());
        holder.mExpandButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                boolean show = toggleLayout(!currentItem.isExpanded(), view, holder.mExpandedLayout);
                currentItem.setExpanded(show);
            }
        });

        // region Textwatchers
        // region Editor Listeners
        /* Editor Listener: When actionDone is clicked on the soft keyboard, clear focus from editText */

        // other random methods included in the project which arent relevant
        // ......
    }

    /* Expands or collapses depending on item state */
    private boolean toggleLayout(boolean isExpanded, View v, ConstraintLayout expandedLayout) {
        Animations.toggleArrow(v, isExpanded);
        if (isExpanded) {
            Animations.expand(expandedLayout);
        } else {
            Animations.collapse(expandedLayout);
        }
        return isExpanded;
    }

    @Override
    public int getItemViewType(int position) {
        return position;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public int getItemCount() {
        return mItemCard.size();
    }

    /*~~~~~~~~~~~~~~~~~~~~~~~~~~ Search Filter Setup ~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    @Override
    public Filter getFilter() {
        return itemFilter;
    }

    private Filter itemFilter = new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            List<Item> filteredList = new ArrayList<>();

            if (constraint == null || constraint.length() == 0) {
                filteredList.addAll(mItemCardFull);
            } else {
                String filterPattern = constraint.toString().toLowerCase().trim();

                for (Item item : mItemCardFull) {
                    if (item.getItemName().toLowerCase().contains(filterPattern)) {
                        filteredList.add(item);
                    }
                }
            }

            FilterResults results = new FilterResults();
            results.values = filteredList;
            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            mItemCard.clear();
            mItemCard.addAll((List) results.values);
            notifyDataSetChanged();
        }
    };
}

IntermediatesFragment.java

public class IntermediatesFragment extends Fragment {

    private RecyclerView mRecyclerView;
    private MainAdapter mAdapter;
    private RecyclerView.LayoutManager mLayoutManager;


    public IntermediatesFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View thisFragment = inflater.inflate(R.layout.fragment_layout, container, false);
        setHasOptionsMenu(true);

        mRecyclerView = thisFragment.findViewById(R.id.recyclerView);
        mRecyclerView.setHasFixedSize(true);
        mLayoutManager = new LinearLayoutManager(getContext());


        createItemList();
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setAdapter(mAdapter);
        return thisFragment;
    }

    @Override
    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);

        MenuItem searchItem = menu.findItem(R.id.action_search);
        SearchView searchView = (SearchView) searchItem.getActionView();

        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                mAdapter.getFilter().filter(newText);
                return false;
            }
        });
    }

    private void createItemList() {
        ArrayList<Item> itemList = new ArrayList<>();
        // region Intermediates Items
        // etc items ...

        mAdapter = new MainAdapter(itemList);
    }
}

如果我在 Viewholder 中包含“setIsRecyclable(false)”,问题似乎就消失了,但是在低端手机上性能很糟糕,而且卡是 destroyed/resetted他们退出视图,这不是一种理想的行为。

完整编辑 onBindViewHolder:

@Override
    public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {
        final Item currentItem = mItemCard.get(position);

        final float craftingTime = currentItem.getCraftingTime();
        final float outputAmount = currentItem.getOutputAmount();

        holder.mItemIcon.setImageResource(currentItem.getItemIcon());
        holder.mItemName.setText(currentItem.getItemName());
        holder.mExpandButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                boolean show = toggleLayout(!currentItem.isExpanded(), view, holder.mExpandedLayout);
                currentItem.setExpanded(show);
            }
        });

        // region Ingredients' Icons, Names & Amounts
        holder.mIngredient_1_Icon.setImageResource(currentItem.getIngredientIcon_1());
        holder.mIngredient_2_Icon.setImageResource(currentItem.getIngredientIcon_2());
        holder.mIngredient_3_Icon.setImageResource(currentItem.getIngredientIcon_3());
        holder.mIngredient_4_Icon.setImageResource(currentItem.getIngredientIcon_4());
        holder.mIngredient_5_Icon.setImageResource(currentItem.getIngredientIcon_5());
        holder.mIngredient_6_Icon.setImageResource(currentItem.getIngredientIcon_6());

        holder.mIngredient_1_Name.setText(currentItem.getIngredientName_1());
        holder.mIngredient_2_Name.setText(currentItem.getIngredientName_2());
        holder.mIngredient_3_Name.setText(currentItem.getIngredientName_3());
        holder.mIngredient_4_Name.setText(currentItem.getIngredientName_4());
        holder.mIngredient_5_Name.setText(currentItem.getIngredientName_5());
        holder.mIngredient_6_Name.setText(currentItem.getIngredientName_6());

        holder.mIngredient_1_amountTV.setText(String.valueOf(currentItem.getIngredientAmount_1()));
        holder.mIngredient_2_amountTV.setText(String.valueOf(currentItem.getIngredientAmount_2()));
        holder.mIngredient_3_amountTV.setText(String.valueOf(currentItem.getIngredientAmount_3()));
        holder.mIngredient_4_amountTV.setText(String.valueOf(currentItem.getIngredientAmount_4()));
        holder.mIngredient_5_amountTV.setText(String.valueOf(currentItem.getIngredientAmount_5()));
        holder.mIngredient_6_amountTV.setText(String.valueOf(currentItem.getIngredientAmount_6()));


        if (currentItem.getIngredientName_1() == R.string.item_null) {
            holder.mIngredient_1_Icon.setVisibility(View.GONE);
            holder.mIngredient_1_Name.setVisibility(View.GONE);
            holder.mIngredient_1_amountTV.setVisibility(View.GONE);\

        // if statements continue for the rest of the 5 ingredients
        // endregion Ingredients' Icons, Names & Amounts

        holder.factoryAmountButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                holder.button_f++;
                float factoryAmount = Float.parseFloat(holder.editFactoryAmount.getText().toString());
                float outputSpeed = Float.parseFloat((holder.editItemOutput.getText().toString()));
                if (currentItem.getCraftingMethod().equals("Assembler")) {
                    switch (holder.button_f) {
                        case 0:
                            holder.factoryAmountIcon.setImageResource(R.drawable.pf_assembling_machine_1);
                            holder.craftingSpeed = 0.50f;
                            if (holder.editItemOutput.isFocused()) {
                                holder.factoryAmount = (outputSpeed * craftingTime) / ((outputAmount * holder.craftingSpeed) * (1 + (holder.speedModuleAmount * holder.speed)) * (1 + (holder.prodModuleAmount * holder.productivity)));
                                holder.editFactoryAmount.setText(String.valueOf(holder.factoryAmount));
                                // region Edit Ingredients' Amount
                                holder.factoryAmount = Float.parseFloat(holder.editFactoryAmount.getText().toString());
                                holder.mIngredient_1_amount = (holder.factoryAmount * holder.craftingSpeed * currentItem.getIngredientAmount_1()) / craftingTime;
                                holder.mIngredient_1_amountTV.setText(String.valueOf(holder.mIngredient_1_amount));

                                holder.mIngredient_2_amount = (holder.factoryAmount * holder.craftingSpeed * currentItem.getIngredientAmount_2()) / craftingTime;
                                holder.mIngredient_2_amountTV.setText(String.valueOf(holder.mIngredient_2_amount));

                                holder.mIngredient_3_amount = (holder.factoryAmount * holder.craftingSpeed * currentItem.getIngredientAmount_3()) / craftingTime;
                                holder.mIngredient_3_amountTV.setText(String.valueOf(holder.mIngredient_3_amount));

                                holder.mIngredient_4_amount = (holder.factoryAmount * holder.craftingSpeed * currentItem.getIngredientAmount_4()) / craftingTime;
                                holder.mIngredient_4_amountTV.setText(String.valueOf(holder.mIngredient_4_amount));

                                holder.mIngredient_5_amount = (holder.factoryAmount * holder.craftingSpeed * currentItem.getIngredientAmount_5()) / craftingTime;
                                holder.mIngredient_5_amountTV.setText(String.valueOf(holder.mIngredient_5_amount));

                                holder.mIngredient_6_amount = (holder.factoryAmount * holder.craftingSpeed * currentItem.getIngredientAmount_6()) / craftingTime;
                                holder.mIngredient_6_amountTV.setText(String.valueOf(holder.mIngredient_6_amount));


            // it goes on in the same pattern depending on other circumstances


        /*~~~~~~~~~~~~ Initialization & Crafting Method Switch ~~~~~~~~~~~~~~~~~~*/
        if (holder.editFactoryAmount.getText().toString().isEmpty() && !holder.editFactoryAmount.isFocused()) {
            holder.editFactoryAmount.setText(String.valueOf(1.0f));
            holder.outputSpeed = ((Float.parseFloat(holder.editFactoryAmount.getText().toString()) * holder.craftingSpeed) * (1 + (holder.speedModuleAmount * holder.speed))) / craftingTime * (1 + (holder.prodModuleAmount * holder.productivity)) * outputAmount;
            holder.editItemOutput.setText(String.valueOf(holder.outputSpeed));
        }

        float factoryAmount = Float.parseFloat(holder.editFactoryAmount.getText().toString());
        switch (currentItem.getCraftingMethod()) {
            case "Assembler":
                // If it's the first time the app starts, set default crafting speed, else keep as it is.
                if (holder._isFirstTime) {
                    holder.craftingSpeed = 0.50f;
                    holder._isFirstTime = false;
                }
                if (holder.craftingSpeed == 0.50f) {
                    holder.factoryAmountIcon.setImageResource(R.drawable.pf_assembling_machine_1);
                    holder.button_f = 0;
                } else if (holder.craftingSpeed == 0.75f) {
                    holder.factoryAmountIcon.setImageResource(R.drawable.pf_assembling_machine_2);
                    holder.button_f = 1;
                } else {
                    holder.factoryAmountIcon.setImageResource(R.drawable.pf_assembling_machine_3);
                }
                holder.outputSpeed = ((factoryAmount * holder.craftingSpeed) * (1 + (holder.speedModuleAmount * holder.speed))) / craftingTime * (1 + (holder.prodModuleAmount * holder.productivity)) * outputAmount;
                holder.editItemOutput.setText(String.valueOf(holder.outputSpeed));
                // region Edit Ingredients' Amount
                holder.factoryAmount = Float.parseFloat(holder.editFactoryAmount.getText().toString());
                holder.mIngredient_1_amount = (holder.factoryAmount * holder.craftingSpeed * currentItem.getIngredientAmount_1()) / craftingTime;
                holder.mIngredient_1_amountTV.setText(String.valueOf(holder.mIngredient_1_amount));

                holder.mIngredient_2_amount = (holder.factoryAmount * holder.craftingSpeed * currentItem.getIngredientAmount_2()) / craftingTime;
                holder.mIngredient_2_amountTV.setText(String.valueOf(holder.mIngredient_2_amount));

                holder.mIngredient_3_amount = (holder.factoryAmount * holder.craftingSpeed * currentItem.getIngredientAmount_3()) / craftingTime;
                holder.mIngredient_3_amountTV.setText(String.valueOf(holder.mIngredient_3_amount));

                holder.mIngredient_4_amount = (holder.factoryAmount * holder.craftingSpeed * currentItem.getIngredientAmount_4()) / craftingTime;
                holder.mIngredient_4_amountTV.setText(String.valueOf(holder.mIngredient_4_amount));

                holder.mIngredient_5_amount = (holder.factoryAmount * holder.craftingSpeed * currentItem.getIngredientAmount_5()) / craftingTime;
                holder.mIngredient_5_amountTV.setText(String.valueOf(holder.mIngredient_5_amount));

                holder.mIngredient_6_amount = (holder.factoryAmount * holder.craftingSpeed * currentItem.getIngredientAmount_6()) / craftingTime;
                holder.mIngredient_6_amountTV.setText(String.valueOf(holder.mIngredient_6_amount));
                // endregion Edit Ingredients' Amount
                break;
            case "Smelter":
            // thing happening here and etc for all the other cases
        }
    }

你能试试这个吗

class SectionRecyclerViewAdapter(
    var sectionModelArrayList: ArrayList<SectionModel>,
    val context: Activity
) : RecyclerView.Adapter<SectionRecyclerViewAdapter.MyViewHolder>(), Filterable {
    private val mSectionList: List<SectionModel>? = sectionModelArrayList

    override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): MyViewHolder {
        val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.section_custom_row_layout, viewGroup, false)
        return MyViewHolder(view)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val sectionModel = sectionModelArrayList[position]
        holder.tvStations.text = sectionModel.stations
        holder.ivLogo.setImageDrawable(sectionModel.logo)
        holder.ivArrow.setImageResource(R.mipmap.ic_arrow_dark)
        holder.view.setOnClickListener {
            when (position) {
                0 -> {
                    val intent = Intent(context, AllUserActivity::class.java)
                    context.startActivity(intent)
                    context.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
                }
                1 -> {
                    Toast.makeText(context, "Nothing to Show", Toast.LENGTH_SHORT).show()
                }


            }
        }
    }

    override fun getItemCount(): Int {
        return sectionModelArrayList.size
    }

    inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val ivLogo: ImageView
        val tvStations: TextView
        val ivArrow: ImageView
        val view: View


        init {
            ivLogo = itemView.findViewById(R.id.ivLogo)
            tvStations = itemView.findViewById(R.id.tvStations)
            ivArrow = itemView.findViewById(R.id.ivArrow)
            view = itemView
        }
    }

    override fun getFilter(): Filter {
        return object : Filter() {
            protected override fun performFiltering(charSequence: CharSequence): FilterResults {
                val charString = charSequence.toString()
                if (charString.isEmpty()) {
                    sectionModelArrayList = mSectionList as ArrayList<SectionModel>
                } else {
                    var filteredList = ArrayList<SectionModel>()
                    if (mSectionList != null) {
                        for (row in mSectionList) {

                            // name match condition. this might differ depending on your requirement
                            // here we are looking for name or phone number match
                            if (row.stations.toLowerCase().contains(charString.toLowerCase())) {
                                filteredList.add(row)
                            }
                        }
                    }
                    sectionModelArrayList = filteredList
                }

                val filterResults = FilterResults()
                filterResults.values = sectionModelArrayList
                return filterResults
            }

            override fun publishResults(charSequence: CharSequence, filterResults: FilterResults) {
                sectionModelArrayList = filterResults.values as ArrayList<SectionModel>
                notifyDataSetChanged()
            }
        }
    }
}

将筛选列表传递给适配器将解决问题。

如果没有全貌,很难确切地说出这里出了什么问题,但是...

你在你的 ViewHolder 中持有很多状态,比如是否是第一次,成分数量等。你真的想持有View 在那里,因为它们可以在项目之间不可预测地交换。这就是当你告诉它它们不可回收时它起作用的原因——它不再这样做了。

您还将设置为 GONE,但如果应该显示则不可见。这是回收的问题:

holder.mIngredient_1_Icon.setVisibility(View.GONE);

您的按钮 onClick 侦听器正在修改支架中的状态 - 这很糟糕,因为支架会在项目之间交换,并且可能会反弹。你真的想要在你的 Item class 中所有这些状态。一旦你这样做了,你的 RecyclerView 就变成了从 Items 到设置所有内容的 onBindViewHolder 的单向转换,推理起来会容易得多。