RecyclerView添加消息(含图片)时列表异常,notifyItemChanged无效求助
Hey there, let's break down why your RecyclerView is acting up with message changes and image loading, and fix it step by step.
Key Issues in Your Current Code
- Repeated Picasso Instance Creation: Every time
onBindViewHolderruns, you're creating a new Picasso instance. This wastes resources and can cause inconsistent image loading behavior. - Unreset View States: When a message has no media, you don't hide
mViewMedia—so recycled views will show old images from previous messages. - Incorrect Position Usage: Using
holder.getAdapterPosition()insideonBindViewHolderis risky, especially for async operations like image loading. The position might change before the load completes. - Poor Update Notification: Using
notifyItemChanged()triggers a full redraw of the item, which causes flickering. For new messages, you should use a more targeted update method. - Manual Layout Params: Your manual
FrameLayout.LayoutParamsmight conflict with RecyclerView's default layout handling.
Step-by-Step Fixes
1. Use a Singleton Picasso Instance
Create a global Picasso instance once (e.g., in your Application class) instead of making new ones every time:
public class ChatApp extends Application { @Override public void onCreate() { super.onCreate(); // Initialize Picasso once Picasso.setSingletonInstance(new Picasso.Builder(this) .downloader(new OkHttp3Downloader(this)) .build()); } }
Don't forget to add your Application class to AndroidManifest.xml:
<application android:name=".ChatApp" <!-- other attributes --> >
2. Fix onCreateViewHolder Layout Params
Let the inflater handle layout params correctly by passing parent and false to inflate()—this avoids layout conflicts:
@NonNull @Override public MessageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View layoutView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_message, parent, false); return new MessageViewHolder(layoutView); }
3. Clean Up onBindViewHolder
Reset view states, use the correct position, and leverage the singleton Picasso:
@Override public void onBindViewHolder(@NonNull final MessageViewHolder holder, final int position) { final Message currentMessage = messageList.get(position); // Set text content holder.mMessage.setText(currentMessage.getMessage()); holder.mSender.setText(currentMessage.getSenderId()); // Load profile pic with singleton Picasso Picasso.get() .load(currentMessage.getProfilePic()) .placeholder(R.drawable.image) .tag(holder) // Tag with holder to cancel on recycle .into(holder.imgprofile); // Handle media visibility and loading if (!currentMessage.getMediaUrlList().isEmpty()) { holder.mViewMedia.setVisibility(View.VISIBLE); Picasso.get() .load(currentMessage.getMediaUrlList().get(0)) .placeholder(R.drawable.ic_launcher_background) .error(R.drawable.ic_launcher_background) .tag(holder) .into(holder.mViewMedia); } else { // Critical: Hide media view when there's no media to avoid old content showing holder.mViewMedia.setVisibility(View.GONE); // Optional: Clear the image to prevent residual bitmap Picasso.get().cancelRequest(holder.mViewMedia); holder.mViewMedia.setImageDrawable(null); } // Set click listener with safe position holder.mViewMedia.setOnClickListener(v -> { new ImageViewer.Builder(v.getContext(), currentMessage.getMediaUrlList()) .setStartPosition(0) .show(); }); }
4. Cancel Pending Requests on View Recycle
Add this to your Adapter to cancel ongoing image loads when the view is recycled, preventing incorrect images from loading into reused views:
// Override onViewRecycled in your Adapter @Override public void onViewRecycled(@NonNull MessageViewHolder holder) { super.onViewRecycled(holder); // Cancel all Picasso requests tagged with this holder Picasso.get().cancelTag(holder); } class MessageViewHolder extends RecyclerView.ViewHolder { TextView mMessage, mSender; ImageView imgprofile, mViewMedia; public MessageViewHolder(@NonNull View itemView) { super(itemView); // Initialize your views here... } }
5. Update RecyclerView Correctly When Adding Messages
Instead of notifyItemChanged(), use notifyItemInserted() when adding a new message to the list. This triggers a smooth insertion animation and only updates the new item, not existing ones:
// When adding a new message to your list: messageList.add(newMessage); notifyItemInserted(messageList.size() - 1); // Optional: Scroll to the bottom to show the new message recyclerView.scrollToPosition(messageList.size() - 1);
Why This Works
- Singleton Picasso: Ensures consistent image caching and loading behavior across your app.
- View State Reset: Hides media views and clears images when there's no media, preventing recycled views from showing old data.
- Safe Position Usage: Using the
positionparameter fromonBindViewHolderguarantees you're working with the correct data for that view at bind time. - Request Cancellation: Stops ongoing image loads when a view is recycled, so old requests don't overwrite new data.
- Targeted Updates:
notifyItemInserted()is more efficient and avoids flickering compared tonotifyItemChanged()ornotifyDataSetChanged().
内容的提问来源于stack exchange,提问作者Pooya Salehi

