简体   繁体   中英

Recyclerview row format changes after search filter

I have a recyclerview that displays a list of contacts. To differentiate between contacts that are also users of my app (let's refer to these as app-contacts) and all other contacts (non-app-contacts), i have made the typeface of all app-contacts bold (Typeface.BOLD), and non-app-contacts normal (Typeface.NORMAL). However, when the recyclerview gets filtered while searching for a contact, and app-contacts get displayed in certain rows (let's say rows 1 and 2) with a bold typeface, then those rows remain in a bold typeface. Even when i change the search, and non-app-contacts (which should be in a normal typeface) now occupy those rows (1 and 2), it's in a bold typeface. Essentially rows 1 and 2 now remain in a bold typeface regardless of the type of contact being displayed in them.

Here is my recyclerview adapter. the onBindViewHolder is where i change the typeface. "is Suma Contact" boolean means the contact is an app contact.


public class SearchRecipientHintsAdapter extends RecyclerView.Adapter<SearchRecipientHintsAdapter.ViewHolder>  {

    private Context context;
    private List<RecipientsContactItem> contactItems;
    private final int SELECT_DROPOFF_REQUEST_CODE = 77;

    public SearchRecipientHintsAdapter (Context context, List<RecipientsContactItem> contactItems) {
        this.context = context;
        this.contactItems = contactItems;
    }

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

    @Override
    public void onBindViewHolder(@NonNull SearchRecipientHintsAdapter.ViewHolder holder, int position) {
        RecipientsContactItem contactItem = contactItems.get(position);
        holder.name.setText(contactItem.getName());
        holder.phoneNumber.setText(contactItem.getPhoneNumber());

        if (contactItem.getImage() != null && !contactItem.getImage().isEmpty()) {
            try {
                Picasso.get().load(contactItem.getImage()).into(holder.image);
            } catch (Throwable ignored) { }
        } else {
            holder.image.setImageDrawable(context.getResources().getDrawable(R.drawable.user_default_img));
        }

        if (contactItem.isVerified()) {
            holder.verificationIcon.setVisibility(View.VISIBLE);
        } else {
            holder.verificationIcon.setVisibility(View.GONE);
        }

        if (contactItem.isSumaContact()) {

            holder.name.setTypeface(holder.name.getTypeface(), Typeface.BOLD);

            switch (contactItem.getPrivacy()) {
                case "Public":
                    holder.publicIcon.setVisibility(View.VISIBLE);
                    holder.privateIcon.setVisibility(View.GONE);
                    holder.allowedIcon.setVisibility(View.GONE);
                    holder.inviteButton.setVisibility(View.GONE);
                    break;
                case "Private":
                    holder.publicIcon.setVisibility(View.GONE);
                    holder.privateIcon.setVisibility(View.VISIBLE);
                    holder.allowedIcon.setVisibility(View.GONE);
                    holder.inviteButton.setVisibility(View.GONE);
                    break;
                case "Allowed":
                    holder.publicIcon.setVisibility(View.GONE);
                    holder.privateIcon.setVisibility(View.GONE);
                    holder.allowedIcon.setVisibility(View.VISIBLE);
                    holder.inviteButton.setVisibility(View.GONE);
                    break;
            }
        } else {
            holder.name.setTypeface(holder.name.getTypeface(), Typeface.NORMAL);
            holder.inviteButton.setVisibility(View.VISIBLE);
            holder.publicIcon.setVisibility(View.GONE);
            holder.privateIcon.setVisibility(View.GONE);
            holder.allowedIcon.setVisibility(View.GONE);
        }
    }

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

    public class ViewHolder extends RecyclerView.ViewHolder {

        private TextView name;
        private TextView phoneNumber;
        private ImageView image;
        private ImageView verificationIcon;
        private Button inviteButton;
        private ImageView publicIcon;
        private ImageView privateIcon;
        private ImageView allowedIcon;

        public ViewHolder(@NonNull View itemView, Context ctx) {
            super(itemView);
            context = ctx;
            name = itemView.findViewById(R.id.recipientsCRowNameID);
            phoneNumber = itemView.findViewById(R.id.recipientsCRowPhoneID);
            image = itemView.findViewById(R.id.recipientsCRowImageID);
            verificationIcon = itemView.findViewById(R.id.recipientsCRowVerifiedID);
            inviteButton = itemView.findViewById(R.id.recipientsCRowInviteID);
            publicIcon = itemView.findViewById(R.id.recipientsCRowPublicID);
            privateIcon = itemView.findViewById(R.id.recipientsCRowPrivateID);
            allowedIcon = itemView.findViewById(R.id.recipientsCRowAllowedID);

            itemView.setOnClickListener(v -> {
                //Get position of row
                int position = getAdapterPosition();

                RecipientsContactItem contactItem = contactItems.get(position);
                String uID = contactItem.getUID();
                String name = contactItem.getName();
                String phoneNumber = contactItem.getPhoneNumber();
                String lat = contactItem.getLat();
                String lng = contactItem.getLng();
                boolean isSumaContact = contactItem.isSumaContact();

               if (isSumaContact) {
                   if (contactItem.getPrivacy().equals("Public") || contactItem.getPrivacy().equals("Allowed")) {
                       Intent returnRecipientIntent = ((Activity) context).getIntent();
                       returnRecipientIntent.putExtra("uID", uID);
                       returnRecipientIntent.putExtra("name", name);
                       returnRecipientIntent.putExtra("phoneNumber", phoneNumber);
                       returnRecipientIntent.putExtra("lat", lat);
                       returnRecipientIntent.putExtra("lng", lng);
                       returnRecipientIntent.putExtra("isSumaContact", true);

                       ((Activity) context).setResult(Activity.RESULT_OK, returnRecipientIntent);
                       ((Activity) context).finish();
                   } else {
                       Toast.makeText(context, R.string.recipients_search_disallowed_toast, Toast.LENGTH_LONG).show();
                   }
               } else {
                   Intent dropOffSearchIntent = new Intent(context, SelectDropoff.class);
                   ((Activity) context).startActivityForResult(dropOffSearchIntent, SELECT_DROPOFF_REQUEST_CODE);
               }
            });

            inviteButton.setOnClickListener(view -> {
                Intent sendInvite = new Intent(android.content.Intent.ACTION_VIEW);
                sendInvite.putExtra("address", contactItems.get(getAdapterPosition()).getPhoneNumber());
                sendInvite.putExtra("sms_body", context.getResources().getString(R.string.recipients_invite_link));
                sendInvite.setType("vnd.android-dir/mms-sms");
                try {
                    context.startActivity(sendInvite);
                } catch (Throwable t) {
                    Toast.makeText(context, "Sorry, invite not working. Please use the invite in your main menu", Toast.LENGTH_LONG).show();
                }
            });
        }
    }

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

    public void updateWithSearchFilter (List<RecipientsContactItem> newList) {
        contactItems = new LinkedList<>();
        contactItems.addAll(newList);
        notifyDataSetChanged();
    }
}

Here is the onQueryTextChange() in setOnQueryTextListener() where i filter the search and pass the result/new list to the adapter above

            public boolean onQueryTextChange(String newText) {
                
                String userInput = newText.toLowerCase();
                if (userInput.startsWith("0")) {userInput = userInput.substring(1);}


                List<RecipientsContactItem> newList = new LinkedList<>();

                for (RecipientsContactItem contactItem : sumaContacts) {
                    if (contactItem.getName().toLowerCase().contains(userInput) || contactItem.getPhoneNumber().contains(userInput)) {
                        newList.add(contactItem);
                    }
                }
                ((SearchRecipientHintsAdapter) searchRHintsAdapter).updateWithSearchFilter(newList);
                return true;
            }

Shot 1: the 2 contacts displayed are non-app contacts so their typeface is normal (not bold)

Shot 2. After filtering search to display an app-contact: the first contact is an contact (bold typeface) and the second is a non-app contact (normal typeface - not bold)

Shot 3. After clearing search filter to display contacts in shot 1: both contacts are non-app contacts and should be in a normal typeface (not bold). But the first contact is displayed as bold, because an app-contact (which is bold) was briefly displayed there (in shot 2) while filtering search

NB: The problem used to be caused by scrolling too. Till i @Override the getItemViewType() method of the Adapter

Initially, anytime i scroll the recyclerview, the Bold Typeface would be wrongly applied to rows/contacts that shouldn't be bold. Till i found a solution where i had to overrider the getItemViewType() method of the recyclerview adapter like this:

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

then it was fixed (for scrolling). till i realized that the problem persisted for filtering. So that's what i'm trying to fix now

The problem is

holder.name.setTypeface(holder.name.getTypeface(), Typeface.NORMAL);

When rebinding a viewholder with bold in place, holder.getTypeface() returns the bold typeface that was there earlier. Now, Typeface.NORMAL has the value 0 . Here's the setTypeface() implementation from cs.android.com :

public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) {
    if (style > 0) {
        if (tf == null) {
            tf = Typeface.defaultFromStyle(style);
        } else {
            tf = Typeface.create(tf, style);
        }

        setTypeface(tf);
        // now compute what (if any) algorithmic styling is needed
        int typefaceStyle = tf != null ? tf.getStyle() : 0;
        int need = style & ~typefaceStyle;
        mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
        mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
    } else {
        mTextPaint.setFakeBoldText(false);
        mTextPaint.setTextSkewX(0);
        setTypeface(tf);
    }
}

Note the if (style > 0) part there. So, passing in Typeface.NORMAL will just set the typeface as-is, without doing any styling on it, so your bold style will stay bold.

To fix that, either pass in a null for typeface if that is appropriate for you, or reset the typeface to a default that fits your needs.

In addition, there's also a perf problem in your

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

This makes each row have its own specific view type. But you really only have one view type, so you don't need to override this method at all. Or if you do, you can return a constant value.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM