OnBindViewHolder:为什么有时位置不从零开始?

OnBindViewHolder: why sometimes position don't start from zero?

我正在使用 RecyclerView,我注意到一个奇怪的行为:每次 onBindViewHolder() 被调用时我都会记录实际位置,有时位置不是从零开始的,你知道为什么吗?对我来说这是一个问题,因为在位置 0 我有不同的逻辑。

那是记录器:

2022-02-15 16:19:51.833 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:19:51.834 D/UpdateFragment: >>>ViewModel.GetAllTags()
2022-02-15 16:19:51.864 D/RecyclerViewAdapterUpdate: >>>Position:0
2022-02-15 16:19:52.110 D/RecyclerViewAdapterUpdate: >>>Position:1
2022-02-15 16:19:52.266 D/RecyclerViewAdapterUpdate: >>>Position:2
2022-02-15 16:19:52.331 D/RecyclerViewAdapterUpdate: >>>Position:3
2022-02-15 16:20:03.696 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:20:03.722 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:20:03.729 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:20:03.729 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:20:03.730 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:20:03.737 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:20:03.743 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:20:03.750 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:20:03.761 D/RecyclerViewAdapterUpdate: >>>Position:2
2022-02-15 16:20:03.830 D/RecyclerViewAdapterUpdate: >>>Position:3
2022-02-15 16:20:03.891 D/RecyclerViewAdapterUpdate: >>>Position:1
2022-02-15 16:20:04.042 D/RecyclerViewAdapterUpdate: >>>Position:0

好的,我知道顺序并不总是相同,但在我的应用程序中,当我更新数据时,我注意到 2 个 ViewHolder 的布局之间存在神秘的交换,但在我的代码中我从未将布局分配给视图持有者。正如您在日志中看到的,与位置相关联的布局的 id 发生了变化。

2022-02-15 19:03:10.878 D/RecyclerViewAdapterUpdate: >>>Position[0]android.widget.LinearLayout{83f4271 G.E...... ......I. 0,0-0,0 #7f0800f5 app:id/layout_cards}
2022-02-15 19:03:11.593 D/RecyclerViewAdapterUpdate: >>>Position[1]android.widget.LinearLayout{e951d5e G.E...... ......I. 0,0-0,0 #7f0800f5 app:id/layout_cards}
2022-02-15 19:03:11.954 D/RecyclerViewAdapterUpdate: >>>Position[2]android.widget.LinearLayout{61b8127 G.E...... ......I. 0,0-0,0 #7f0800f5 app:id/layout_cards}
2022-02-15 19:03:18.326 D/RecyclerViewAdapterUpdate: >>>View android.widget.LinearLayout{61b8127 V.E...... ......I. 0,0-0,0 #7f0800f5 app:id/layout_cards}true
2022-02-15 19:03:18.953 D/RecyclerViewAdapterUpdate: >>>View androidx.constraintlayout.widget.ConstraintLayout{8510217 V.E...... ......I. 0,0-0,0 #7f08021e app:id/layout_update_card}true
2022-02-15 19:03:53.394 D/RecyclerViewAdapterUpdate: >>>Saved
2022-02-15 19:03:53.582 D/RecyclerViewAdapterUpdate: >>>Position[2]android.widget.LinearLayout{83f4271 G.E...... ......I. 0,0-0,0 #7f0800f5 app:id/layout_cards}
2022-02-15 19:03:53.729 D/RecyclerViewAdapterUpdate: >>>Position[1]android.widget.LinearLayout{e951d5e G.E...... ......I. 0,0-0,0 #7f0800f5 app:id/layout_cards}
2022-02-15 19:03:54.005 D/RecyclerViewAdapterUpdate: >>>Position[0]android.widget.LinearLayout{61b8127 V.E...... .......D 0,98-720,761 #7f0800f5 app:id/layout_cards}
2022-02-15 19:03:54.437 D/RecyclerViewAdapterUpdate: >>>Position[0]android.widget.LinearLayout{61b8127 V.E...... .......D 0,98-720,378 #7f0800f5 app:id/layout_cards}
2022-02-15 19:03:54.695 D/RecyclerViewAdapterUpdate: >>>Position[1]android.widget.LinearLayout{e951d5e G.E...... ......I. 0,0-0,0 #7f0800f5 app:id/layout_cards}
2022-02-15 19:03:54.879 D/RecyclerViewAdapterUpdate: >>>Position[2]android.widget.LinearLayout{83f4271 G.E...... ......I. 0,0-0,0 #7f0800f5 app:id/layout_cards}

public void onBindViewHolder(@NonNull ViewHolder holder, int position) {

        Log.d(TAG, ">>>Position["+position+"]" + holder.linearLayout);
        if (position == 0) {

            // All the cards
            holder.numberOfItems.setText(String.valueOf(cardList.size()));
            holder.tagName.setText(R.string.all_cards_tag);
            holder.clearTag.setVisibility(View.GONE); // Can't delete all cards
            holder.checkBox.setChecked(allCardIsChecked);
            holder.checkBox.setOnClickListener(view -> {

                SharedPreferences.Editor editor = sharedPreferences.edit();
                editor.putInt(Utilities.RECYCLER_CARD_POSITION, 0);
                editor.putBoolean(Utilities.SHOULD_SHUFFLE, true);

                if (holder.checkBox.isChecked()) {

                    MainActivity.recyclerTagList.clear();
                    editor.putStringSet(Utilities.SELECTED_TAGS, new HashSet<>());
                    Log.d(TAG, ">>Tags selected: " + MainActivity.recyclerTagList);

                    numberOfSelected = 1;
                    if (!allCardIsChecked) {
                        allCardIsChecked = true;
                        notifyDataSetChanged();
                    }

                } else {
                    // Can't deselect all cards, at least one group chosen
                    holder.checkBox.setChecked(true);
                    Toast.makeText(context, R.string.min_one_tag, Toast.LENGTH_SHORT).show();
                }

                editor.commit();

            });

            initializeLayoutCards(cardList,null,  holder, true);

        } else {

            // Single TAG after all cards
            Tag tag = tagList.get(position - 1);

            List<CardWithTags> listOfSingleTag = new ArrayList<>();
            for (CardWithTags cwt: cardList) {
                for (Tag t: cwt.getTagList()) {
                    if (t.getTag().equals(tag.getTag()))
                        listOfSingleTag.add(cwt);
                }
            }

            initializeLayoutCards(listOfSingleTag, tag, holder, false);

            holder.numberOfItems.setText(String.valueOf(listOfSingleTag.size()));
            holder.tagName.setText(tag.getTag());
            holder.clearTag.setVisibility(View.VISIBLE);
            if (allCardIsChecked)
                holder.checkBox.setChecked(false);
            else {
                holder.checkBox.setChecked(false);
                for (String s: selectedTags) {
                    if (s.equalsIgnoreCase(tag.getTag()))
                        holder.checkBox.setChecked(true);
                }
            }
            holder.checkBox.setOnClickListener(view -> {

                SharedPreferences.Editor editor = sharedPreferences.edit();
                editor.putInt(Utilities.RECYCLER_CARD_POSITION, 0);
                editor.putBoolean(Utilities.SHOULD_SHUFFLE, true);

                if (holder.checkBox.isChecked()) {
                    MainActivity.recyclerTagList.add(tag);
                    selectedTags = sharedPreferences.getStringSet(Utilities.SELECTED_TAGS, new HashSet<>());
                    selectedTags.add(tag.getTag());
                    editor.putStringSet(Utilities.SELECTED_TAGS, selectedTags);
                    Log.d(TAG, ">>Tags selected: " + MainActivity.recyclerTagList);

                    if (allCardIsChecked) {
                        allCardIsChecked = false;
                        notifyDataSetChanged();
                    } else {
                        ++numberOfSelected;
                    }
                } else {
                    if (numberOfSelected == 1) {
                        Toast.makeText(context, R.string.min_one_tag, Toast.LENGTH_SHORT).show();
                        holder.checkBox.setChecked(true);
                    }
                    else {
                        --numberOfSelected;
                        MainActivity.recyclerTagList.removeIf(t -> t.getTag().equalsIgnoreCase(tag.getTag()));
                        selectedTags = sharedPreferences.getStringSet(Utilities.SELECTED_TAGS, new HashSet<>());
                        selectedTags.remove(tag.getTag());
                        editor.putStringSet(Utilities.SELECTED_TAGS, selectedTags);
                        Log.d(TAG, ">>Tags selected: " + MainActivity.recyclerTagList);
                    }
                }

                editor.commit();

            });

            View viewDialogTag = LayoutInflater.from(context).inflate(R.layout.dialog_modify_tag, (ViewGroup) null);
            EditText tagNameEditText = viewDialogTag.findViewById(R.id.tag_name_dialog);
            tagNameEditText.setText(tag.getTag());

            // In this way the dialog is created only one time
            AlertDialog dialogTag = getDialogUpdateTag(tag, viewDialogTag, tagNameEditText);
            AlertDialog dialogDeleteTag = getDialogDeleteTag(tag);

            holder.tagName.setOnLongClickListener( view -> {
                // Change tag name only if closed
                if (holder.linearLayout.getVisibility() == View.GONE)
                    dialogTag.show();
                return true;
            });

            holder.clearTag.setOnClickListener( view -> dialogDeleteTag.show());

        }

    }

private void initializeLayoutCards(List<CardWithTags> cardList, Tag tag, ViewHolder holder, boolean isAllCards) {

        // Or it will add already added cards
        holder.linearLayout.removeAllViews();

        for (CardWithTags cwt : cardList) {

            Card card = cwt.getCard();

            View cardView = LayoutInflater.from(context).inflate(R.layout.card_update, holder.linearLayout, false);
            TextView textViewCardName = cardView.findViewById(R.id.card_name);
            Button clearButton = cardView.findViewById(R.id.clear_card);
            ConstraintLayout layoutCardUpdate = cardView.findViewById(R.id.layout_update_card);
            EditText titleEditText = cardView.findViewById(R.id.update_title_edit_text);
            EditText taboo1EditText = cardView.findViewById(R.id.update_taboo_1_edit_text);
            EditText taboo2EditText = cardView.findViewById(R.id.update_taboo_2_edit_text);
            EditText taboo3EditText = cardView.findViewById(R.id.update_taboo_3_edit_text);
            EditText taboo4EditText = cardView.findViewById(R.id.update_taboo_4_edit_text);
            EditText taboo5EditText = cardView.findViewById(R.id.update_taboo_5_edit_text);
            Button saveButton = cardView.findViewById(R.id.save_button);
            Button tagButton = cardView.findViewById(R.id.tag_button);

            textViewCardName.setText(card.getTitle());
            titleEditText.setText(card.getTitle());
            taboo1EditText.setText(card.getTabooWord1());
            taboo2EditText.setText(card.getTabooWord2());
            taboo3EditText.setText(card.getTabooWord3());
            taboo4EditText.setText(card.getTabooWord4());
            taboo5EditText.setText(card.getTabooWord5());

            saveButton.setOnClickListener(view -> {

                Animations.doReduceIncreaseAnimation(view);

                String title = titleEditText.getText().toString();
                String taboo1 = taboo1EditText.getText().toString();
                String taboo2 = taboo2EditText.getText().toString();
                String taboo3 = taboo3EditText.getText().toString();
                String taboo4 = taboo4EditText.getText().toString();
                String taboo5 = taboo5EditText.getText().toString();

                if (title.equalsIgnoreCase(card.getTitle()) &&
                    taboo1.equalsIgnoreCase(card.getTabooWord1()) &&
                    taboo2.equalsIgnoreCase(card.getTabooWord2()) &&
                    taboo3.equalsIgnoreCase(card.getTabooWord3()) &&
                    taboo4.equalsIgnoreCase(card.getTabooWord4()) &&
                    taboo5.equalsIgnoreCase(card.getTabooWord5())) {
                    Log.d(TAG, ">>Change something before saving card");
                    return;
                }

                Card newCard = new Card(title, taboo1, taboo2, taboo3, taboo4, taboo5);
                newCard.setIdCard(card.getIdCard());

                // Check if new title already exists
                for (CardWithTags c: this.cardList) {
                    if (!c.getCard().getTitle().equalsIgnoreCase(card.getTitle()) && c.getCard().getTitle().equalsIgnoreCase(title)) {
                        Toast.makeText(context, R.string.title_already_exists, Toast.LENGTH_SHORT).show();
                        return;
                    }
                }

                cwt.setCard(newCard);
                Log.d(TAG, ">>New cwt: " + cwt);
                Log.d(TAG, ">>>Saved");
                viewModelFragment.updateCWT(cwt);
                Toast.makeText(context, R.string.card_updated, Toast.LENGTH_SHORT).show();
            });

            tagButton.setOnClickListener(view -> {

                Animations.doReduceIncreaseAnimation(view);
            });

            textViewCardName.setOnClickListener(view -> {
                openCloseView(layoutCardUpdate);
            });

            AlertDialog dialogDeleteCard;
            if (!isAllCards)
                dialogDeleteCard = getDialogDeleteCardOrTag(card, tag);
            else
                dialogDeleteCard = getDialogDeleteCard(card);

            clearButton.setOnClickListener(view -> dialogDeleteCard.show());

            // Set the click on both number of items and tag name
            holder.tagName.setOnClickListener(view -> {
                openCloseView(holder.linearLayout);
            });
            holder.numberOfItems.setOnClickListener(view -> {
                openCloseView(holder.linearLayout);
            });

            holder.linearLayout.addView(cardView);

        }

    }

    private void openCloseView(View view) {

        if (view.getVisibility() == View.GONE) {

            view.setVisibility(View.VISIBLE);
            boolean isVisible = view.getVisibility() == View.VISIBLE? true : false;
            Log.d(TAG, ">>>View " + view +isVisible);

            AnimationSet animation = new AnimationSet(true);
            Animation animationAlpha = new AlphaAnimation(0, 1);
            animation.addAnimation(animationAlpha);
            Animation animationTranslate = new TranslateAnimation(0, 0, -view.getHeight(), 0);
            animation.addAnimation(animationTranslate);
            Animation animationY = new ScaleAnimation(1, 1, 0, 1);
            animationY.setInterpolator(new LinearInterpolator());
            animation.addAnimation(animationY);
            animation.setDuration(200);

            view.startAnimation(animation);

        } else {

            AnimationSet animation = new AnimationSet(true);
            Animation animationAlpha = new AlphaAnimation(1, 0);
            animation.addAnimation(animationAlpha);
            Animation animationTranslate = new TranslateAnimation(0, 0, 0, -view.getHeight());
            animation.addAnimation(animationTranslate);
            Animation animationY = new ScaleAnimation(1, 1, 1, 0);
            animationY.setInterpolator(new LinearInterpolator());
            animation.addAnimation(animationY);
            animation.setDuration(200);

            animation.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    view.setVisibility(View.GONE);
                    boolean isVisible = view.getVisibility() == View.VISIBLE;
                    Log.d(TAG, ">>>View " + view +isVisible);
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });

            view.startAnimation(animation);
        }
    }

只绑定屏幕上需要更新的项目。事实上,该组件会尽量减少 onbind 调用的次数,以防止额外的工作。没有承诺的调用顺序。因此,唯一一次 0 被确保被调用是在第一次加载时——即使在这种情况下它也不会被调用(例如,如果你正在进行反向填充并从底部开始)。如果您在编写代码时假设它总是尝试绑定 0,那么您就误解了 RecyclerView 的工作原理,需要重新评估您的设计。

为了解决我的问题,我使用 SharedPreferences 在视图是否可见时输入一个布尔值,然后在 onBindViewHolder() 中检索该数据并设置可见性。