package com.example.livestreaming; import android.graphics.Outline; import android.net.Uri; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.core.content.FileProvider; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class ConversationMessagesAdapter extends ListAdapter { public interface OnMessageLongClickListener { void onMessageLongClick(ChatMessage message, int position, View view); } public interface OnImageClickListener { void onImageClick(ChatMessage message, ImageView imageView); } public interface OnVoiceClickListener { void onVoiceClick(ChatMessage message); } private OnMessageLongClickListener longClickListener; private OnImageClickListener imageClickListener; private OnVoiceClickListener voiceClickListener; private static final int TYPE_TEXT_INCOMING = 1; private static final int TYPE_TEXT_OUTGOING = 2; private static final int TYPE_IMAGE_INCOMING = 3; private static final int TYPE_IMAGE_OUTGOING = 4; private static final int TYPE_VOICE_INCOMING = 5; private static final int TYPE_VOICE_OUTGOING = 6; private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm", Locale.getDefault()); public ConversationMessagesAdapter() { super(DIFF); } public void setOnMessageLongClickListener(OnMessageLongClickListener listener) { this.longClickListener = listener; } public void setOnImageClickListener(OnImageClickListener listener) { this.imageClickListener = listener; } public void setOnVoiceClickListener(OnVoiceClickListener listener) { this.voiceClickListener = listener; } @Override public int getItemViewType(int position) { ChatMessage msg = getItem(position); if (msg == null) return TYPE_TEXT_INCOMING; // 优先使用 isOutgoing 字段,如果没有设置则回退到检查用户名 boolean isOutgoing = msg.isOutgoing() || "我".equals(msg.getUsername()); ChatMessage.MessageType type = msg.getMessageType(); if (type == null) type = ChatMessage.MessageType.TEXT; switch (type) { case IMAGE: return isOutgoing ? TYPE_IMAGE_OUTGOING : TYPE_IMAGE_INCOMING; case VOICE: return isOutgoing ? TYPE_VOICE_OUTGOING : TYPE_VOICE_INCOMING; case TEXT: default: return isOutgoing ? TYPE_TEXT_OUTGOING : TYPE_TEXT_INCOMING; } } @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View v; switch (viewType) { case TYPE_TEXT_OUTGOING: v = inflater.inflate(R.layout.item_conversation_message_outgoing, parent, false); return new OutgoingTextVH(v); case TYPE_TEXT_INCOMING: v = inflater.inflate(R.layout.item_conversation_message_incoming, parent, false); return new IncomingTextVH(v); case TYPE_IMAGE_OUTGOING: v = inflater.inflate(R.layout.item_conversation_image_outgoing, parent, false); return new OutgoingImageVH(v); case TYPE_IMAGE_INCOMING: v = inflater.inflate(R.layout.item_conversation_image_incoming, parent, false); return new IncomingImageVH(v); case TYPE_VOICE_OUTGOING: v = inflater.inflate(R.layout.item_conversation_voice_outgoing, parent, false); return new OutgoingVoiceVH(v); case TYPE_VOICE_INCOMING: v = inflater.inflate(R.layout.item_conversation_voice_incoming, parent, false); return new IncomingVoiceVH(v); default: v = inflater.inflate(R.layout.item_conversation_message_incoming, parent, false); return new IncomingTextVH(v); } } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { ChatMessage msg = getItem(position); if (holder instanceof IncomingTextVH) { ((IncomingTextVH) holder).bind(msg, longClickListener); } else if (holder instanceof OutgoingTextVH) { ((OutgoingTextVH) holder).bind(msg, longClickListener); } else if (holder instanceof IncomingImageVH) { ((IncomingImageVH) holder).bind(msg, longClickListener, imageClickListener); } else if (holder instanceof OutgoingImageVH) { ((OutgoingImageVH) holder).bind(msg, longClickListener, imageClickListener); } else if (holder instanceof IncomingVoiceVH) { ((IncomingVoiceVH) holder).bind(msg, longClickListener, voiceClickListener); } else if (holder instanceof OutgoingVoiceVH) { ((OutgoingVoiceVH) holder).bind(msg, longClickListener, voiceClickListener); } } // ========== 文本消息 ViewHolder ========== static class IncomingTextVH extends RecyclerView.ViewHolder { private final ImageView avatarView; private final TextView nameText; private final TextView msgText; private final TextView timeText; private final com.google.android.flexbox.FlexboxLayout reactionsContainer; IncomingTextVH(@NonNull View itemView) { super(itemView); avatarView = itemView.findViewById(R.id.avatarView); nameText = itemView.findViewById(R.id.nameText); msgText = itemView.findViewById(R.id.messageText); timeText = itemView.findViewById(R.id.timeText); reactionsContainer = itemView.findViewById(R.id.reactionsContainer); setupAvatarOutline(); } private void setupAvatarOutline() { if (avatarView == null) return; // 设置圆形裁剪,确保在模拟器上也能正常工作 // 使用post确保在View布局完成后再设置outline avatarView.post(() -> { if (avatarView.getWidth() > 0 && avatarView.getHeight() > 0) { avatarView.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { outline.setOval(0, 0, view.getWidth(), view.getHeight()); } }); avatarView.setClipToOutline(true); } }); } void bind(ChatMessage message, OnMessageLongClickListener listener) { if (message == null) return; nameText.setText(message.getUsername() != null ? message.getUsername() : ""); msgText.setText(message.getMessage() != null ? message.getMessage() : ""); timeText.setText(TIME_FORMAT.format(new Date(message.getTimestamp()))); // 显示表情回应 displayReactions(message); // 清除之前的监听器,避免重复绑定 itemView.setOnClickListener(null); itemView.setOnLongClickListener(null); // 设置长按监听 itemView.setOnLongClickListener(v -> { if (listener != null) { int position = getBindingAdapterPosition(); if (position != RecyclerView.NO_POSITION) { listener.onMessageLongClick(message, position, v); return true; } } return false; }); // 确保头像圆形裁剪设置正确 setupAvatarOutline(); // 加载对方头像(优先使用消息中的头像 URL,如果没有则使用默认头像) loadAvatar(message); } private void displayReactions(ChatMessage message) { if (reactionsContainer == null) return; reactionsContainer.removeAllViews(); if (message.getReactions() == null || message.getReactions().isEmpty()) { reactionsContainer.setVisibility(View.GONE); return; } reactionsContainer.setVisibility(View.VISIBLE); for (com.example.livestreaming.net.MessageReaction reaction : message.getReactions()) { View reactionView = LayoutInflater.from(itemView.getContext()) .inflate(R.layout.item_message_reaction, reactionsContainer, false); TextView emojiText = reactionView.findViewById(R.id.emojiText); TextView countText = reactionView.findViewById(R.id.countText); emojiText.setText(reaction.getEmoji()); countText.setText(String.valueOf(reaction.getCount())); // 如果当前用户已回应,高亮显示 if (reaction.isReactedByMe()) { reactionView.setBackgroundResource(R.drawable.reaction_background_selected); } else { reactionView.setBackgroundResource(R.drawable.reaction_background); } reactionsContainer.addView(reactionView); } } private void loadAvatar(ChatMessage message) { // TODO: 接入后端接口 - 加载消息发送者头像 // 接口路径: GET /api/users/{userId}/avatar 或直接从ChatMessage的avatarUrl字段获取 // ChatMessage对象应包含avatarUrl字段,如果为空,则根据userId调用接口获取 // 建议后端在返回消息列表时,每条消息都包含发送者的avatarUrl,避免额外请求 if (avatarView == null) return; try { // 优先使用消息中的头像 URL(从后端获取) String avatarUrl = message != null ? message.getAvatarUrl() : null; if (!TextUtils.isEmpty(avatarUrl)) { Glide.with(avatarView) .load(avatarUrl) .circleCrop() .error(R.drawable.ic_account_circle_24) .placeholder(R.drawable.ic_account_circle_24) .into(avatarView); return; } // 如果没有头像 URL,暂时根据用户名生成不同的默认头像 // 这样不同发送者至少能通过不同的默认头像区分开 String username = message != null ? message.getUsername() : null; int defaultAvatarRes = getDefaultAvatarForUsername(username); Glide.with(avatarView) .load(defaultAvatarRes) .circleCrop() .into(avatarView); } catch (Exception e) { // 如果加载失败,使用默认头像 Glide.with(avatarView) .load(R.drawable.ic_account_circle_24) .circleCrop() .into(avatarView); } } /** * 根据用户名生成一个稳定的默认头像资源 * 这样同一个用户名的消息会显示相同的默认头像 * TODO: 后续接入后端接口后,这个方法可以改为从后端获取头像 URL */ private int getDefaultAvatarForUsername(String username) { if (TextUtils.isEmpty(username)) { return R.drawable.ic_account_circle_24; } // 使用用户名的哈希值来选择不同的默认头像 // 这样不同的发送者至少能通过不同的默认头像区分开 int hash = Math.abs(username.hashCode()); int[] defaultAvatars = { R.drawable.ic_account_circle_24, // 如果有其他默认头像资源,可以在这里添加 // R.drawable.default_avatar_1, // R.drawable.default_avatar_2, }; return defaultAvatars[hash % defaultAvatars.length]; } } static class OutgoingTextVH extends RecyclerView.ViewHolder { private final ImageView avatarView; private final TextView msgText; private final TextView timeText; private final ImageView statusIcon; private final com.google.android.flexbox.FlexboxLayout reactionsContainer; OutgoingTextVH(@NonNull View itemView) { super(itemView); avatarView = itemView.findViewById(R.id.avatarView); msgText = itemView.findViewById(R.id.messageText); timeText = itemView.findViewById(R.id.timeText); statusIcon = itemView.findViewById(R.id.statusIcon); reactionsContainer = itemView.findViewById(R.id.reactionsContainer); setupAvatarOutline(); } private void setupAvatarOutline() { if (avatarView == null) return; // 设置圆形裁剪,确保在模拟器上也能正常工作 // 使用post确保在View布局完成后再设置outline avatarView.post(() -> { if (avatarView.getWidth() > 0 && avatarView.getHeight() > 0) { avatarView.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { outline.setOval(0, 0, view.getWidth(), view.getHeight()); } }); avatarView.setClipToOutline(true); } }); } void bind(ChatMessage message, OnMessageLongClickListener listener) { if (message == null) return; msgText.setText(message.getMessage() != null ? message.getMessage() : ""); timeText.setText(TIME_FORMAT.format(new Date(message.getTimestamp()))); // 显示表情回应 displayReactions(message); // 显示消息状态图标(仅对发送的消息显示) if (statusIcon != null && message.getStatus() != null) { switch (message.getStatus()) { case SENDING: statusIcon.setImageResource(R.drawable.ic_clock_24); statusIcon.setColorFilter(android.graphics.Color.GRAY, android.graphics.PorterDuff.Mode.SRC_IN); statusIcon.setVisibility(View.VISIBLE); break; case SENT: statusIcon.setImageResource(R.drawable.ic_check_24); statusIcon.setColorFilter(android.graphics.Color.GRAY, android.graphics.PorterDuff.Mode.SRC_IN); statusIcon.setVisibility(View.VISIBLE); break; case READ: statusIcon.setImageResource(R.drawable.ic_check_double_24); statusIcon.setColorFilter(android.graphics.Color.parseColor("#4CAF50"), android.graphics.PorterDuff.Mode.SRC_IN); statusIcon.setVisibility(View.VISIBLE); break; default: statusIcon.setVisibility(View.GONE); break; } } // 清除之前的监听器,避免重复绑定 itemView.setOnClickListener(null); itemView.setOnLongClickListener(null); // 设置长按监听 itemView.setOnLongClickListener(v -> { if (listener != null) { int position = getBindingAdapterPosition(); if (position != RecyclerView.NO_POSITION) { listener.onMessageLongClick(message, position, v); return true; } } return false; }); // 确保头像圆形裁剪设置正确 setupAvatarOutline(); // 加载用户头像 loadAvatar(); } private void displayReactions(ChatMessage message) { if (reactionsContainer == null) return; reactionsContainer.removeAllViews(); if (message.getReactions() == null || message.getReactions().isEmpty()) { reactionsContainer.setVisibility(View.GONE); return; } reactionsContainer.setVisibility(View.VISIBLE); for (com.example.livestreaming.net.MessageReaction reaction : message.getReactions()) { View reactionView = LayoutInflater.from(itemView.getContext()) .inflate(R.layout.item_message_reaction, reactionsContainer, false); TextView emojiText = reactionView.findViewById(R.id.emojiText); TextView countText = reactionView.findViewById(R.id.countText); emojiText.setText(reaction.getEmoji()); countText.setText(String.valueOf(reaction.getCount())); // 如果当前用户已回应,高亮显示 if (reaction.isReactedByMe()) { reactionView.setBackgroundResource(R.drawable.reaction_background_selected); } else { reactionView.setBackgroundResource(R.drawable.reaction_background); } reactionsContainer.addView(reactionView); } } private void loadAvatar() { if (avatarView == null) return; try { String avatarUri = avatarView.getContext() .getSharedPreferences("profile_prefs", android.content.Context.MODE_PRIVATE) .getString("profile_avatar_uri", null); if (!TextUtils.isEmpty(avatarUri)) { Uri uri = Uri.parse(avatarUri); // 如果是 file:// 协议,尝试转换为 FileProvider URI if ("file".equals(uri.getScheme())) { try { java.io.File file = new java.io.File(uri.getPath()); if (file.exists()) { uri = FileProvider.getUriForFile( avatarView.getContext(), avatarView.getContext().getPackageName() + ".fileprovider", file ); } } catch (Exception e) { // 如果转换失败,使用原始 URI } } Glide.with(avatarView) .load(uri) .circleCrop() .error(R.drawable.ic_account_circle_24) .placeholder(R.drawable.ic_account_circle_24) .into(avatarView); return; } int avatarRes = avatarView.getContext() .getSharedPreferences("profile_prefs", android.content.Context.MODE_PRIVATE) .getInt("profile_avatar_res", 0); if (avatarRes != 0) { Glide.with(avatarView) .load(avatarRes) .circleCrop() .error(R.drawable.ic_account_circle_24) .into(avatarView); } else { Glide.with(avatarView) .load(R.drawable.ic_account_circle_24) .circleCrop() .into(avatarView); } } catch (Exception e) { Glide.with(avatarView) .load(R.drawable.ic_account_circle_24) .circleCrop() .into(avatarView); } } } // ========== 图片消息 ViewHolder ========== static class IncomingImageVH extends RecyclerView.ViewHolder { private final ImageView avatarView; private final TextView nameText; private final ImageView messageImageView; private final TextView timeText; private final View imageContainer; IncomingImageVH(@NonNull View itemView) { super(itemView); avatarView = itemView.findViewById(R.id.avatarView); nameText = itemView.findViewById(R.id.nameText); messageImageView = itemView.findViewById(R.id.messageImageView); timeText = itemView.findViewById(R.id.timeText); imageContainer = itemView.findViewById(R.id.imageContainer); setupAvatarOutline(); } private void setupAvatarOutline() { if (avatarView == null) return; avatarView.post(() -> { if (avatarView.getWidth() > 0 && avatarView.getHeight() > 0) { avatarView.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { outline.setOval(0, 0, view.getWidth(), view.getHeight()); } }); avatarView.setClipToOutline(true); } }); } void bind(ChatMessage message, OnMessageLongClickListener longClickListener, OnImageClickListener imageClickListener) { if (message == null) return; nameText.setText(message.getUsername() != null ? message.getUsername() : ""); timeText.setText(TIME_FORMAT.format(new Date(message.getTimestamp()))); // 加载图片 loadImage(message); // 设置点击事件 if (imageContainer != null) { imageContainer.setOnClickListener(v -> { if (imageClickListener != null) { imageClickListener.onImageClick(message, messageImageView); } }); } // 设置长按监听 itemView.setOnLongClickListener(v -> { if (longClickListener != null) { int position = getBindingAdapterPosition(); if (position != RecyclerView.NO_POSITION) { longClickListener.onMessageLongClick(message, position, v); return true; } } return false; }); setupAvatarOutline(); loadAvatar(message); } private void loadImage(ChatMessage message) { if (messageImageView == null) return; // TODO: 接入后端接口 - 加载图片消息 // 优先使用 mediaUrl(从后端获取的图片 URL) // 如果 mediaUrl 为空,使用 localMediaPath(本地路径,用于发送前预览) // // 后端接口说明: // 接口路径: GET /api/messages/{messageId}/media // 或者直接使用 message.getMediaUrl() 返回的完整图片 URL // // 返回数据: 图片文件流或图片 URL // // 前端处理: // 1. 如果 message.getMediaUrl() 不为空,直接加载该 URL // 2. 如果为空但 message.getLocalMediaPath() 不为空,加载本地文件 // 3. 都为空则显示占位图 String imageUrl = message.getMediaUrl(); String localPath = message.getLocalMediaPath(); if (!TextUtils.isEmpty(imageUrl)) { // 从后端 URL 加载图片 Glide.with(messageImageView) .load(imageUrl) .placeholder(R.drawable.ic_image_24) .error(R.drawable.ic_broken_image_24) .into(messageImageView); } else if (!TextUtils.isEmpty(localPath)) { // 从本地路径加载图片(发送前预览) Glide.with(messageImageView) .load(localPath) .placeholder(R.drawable.ic_image_24) .error(R.drawable.ic_broken_image_24) .into(messageImageView); } else { // 显示占位图 messageImageView.setImageResource(R.drawable.ic_image_24); } } private void loadAvatar(ChatMessage message) { if (avatarView == null) return; try { String avatarUrl = message != null ? message.getAvatarUrl() : null; if (!TextUtils.isEmpty(avatarUrl)) { Glide.with(avatarView) .load(avatarUrl) .circleCrop() .error(R.drawable.ic_account_circle_24) .placeholder(R.drawable.ic_account_circle_24) .into(avatarView); return; } Glide.with(avatarView) .load(R.drawable.ic_account_circle_24) .circleCrop() .into(avatarView); } catch (Exception e) { Glide.with(avatarView) .load(R.drawable.ic_account_circle_24) .circleCrop() .into(avatarView); } } } static class OutgoingImageVH extends RecyclerView.ViewHolder { private final ImageView avatarView; private final ImageView messageImageView; private final TextView timeText; private final ImageView statusIcon; private final View imageContainer; OutgoingImageVH(@NonNull View itemView) { super(itemView); avatarView = itemView.findViewById(R.id.avatarView); messageImageView = itemView.findViewById(R.id.messageImageView); timeText = itemView.findViewById(R.id.timeText); statusIcon = itemView.findViewById(R.id.statusIcon); imageContainer = itemView.findViewById(R.id.imageContainer); setupAvatarOutline(); } private void setupAvatarOutline() { if (avatarView == null) return; avatarView.post(() -> { if (avatarView.getWidth() > 0 && avatarView.getHeight() > 0) { avatarView.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { outline.setOval(0, 0, view.getWidth(), view.getHeight()); } }); avatarView.setClipToOutline(true); } }); } void bind(ChatMessage message, OnMessageLongClickListener longClickListener, OnImageClickListener imageClickListener) { if (message == null) return; timeText.setText(TIME_FORMAT.format(new Date(message.getTimestamp()))); // 显示消息状态 updateStatus(message); // 加载图片 loadImage(message); // 设置点击事件 if (imageContainer != null) { imageContainer.setOnClickListener(v -> { if (imageClickListener != null) { imageClickListener.onImageClick(message, messageImageView); } }); } // 设置长按监听 itemView.setOnLongClickListener(v -> { if (longClickListener != null) { int position = getBindingAdapterPosition(); if (position != RecyclerView.NO_POSITION) { longClickListener.onMessageLongClick(message, position, v); return true; } } return false; }); setupAvatarOutline(); loadAvatar(); } private void updateStatus(ChatMessage message) { if (statusIcon != null && message.getStatus() != null) { switch (message.getStatus()) { case SENDING: statusIcon.setImageResource(R.drawable.ic_clock_24); statusIcon.setColorFilter(itemView.getContext().getColor(android.R.color.darker_gray)); statusIcon.setVisibility(View.VISIBLE); break; case SENT: statusIcon.setImageResource(R.drawable.ic_check_24); statusIcon.setColorFilter(itemView.getContext().getColor(android.R.color.darker_gray)); statusIcon.setVisibility(View.VISIBLE); break; case READ: statusIcon.setImageResource(R.drawable.ic_check_double_24); statusIcon.setColorFilter(0xFF4CAF50); statusIcon.setVisibility(View.VISIBLE); break; default: statusIcon.setVisibility(View.GONE); break; } } } private void loadImage(ChatMessage message) { if (messageImageView == null) return; String imageUrl = message.getMediaUrl(); String localPath = message.getLocalMediaPath(); if (!TextUtils.isEmpty(imageUrl)) { Glide.with(messageImageView) .load(imageUrl) .placeholder(R.drawable.ic_image_24) .error(R.drawable.ic_broken_image_24) .into(messageImageView); } else if (!TextUtils.isEmpty(localPath)) { Glide.with(messageImageView) .load(localPath) .placeholder(R.drawable.ic_image_24) .error(R.drawable.ic_broken_image_24) .into(messageImageView); } else { messageImageView.setImageResource(R.drawable.ic_image_24); } } private void loadAvatar() { if (avatarView == null) return; try { String avatarUri = avatarView.getContext() .getSharedPreferences("profile_prefs", android.content.Context.MODE_PRIVATE) .getString("profile_avatar_uri", null); if (!TextUtils.isEmpty(avatarUri)) { Uri uri = Uri.parse(avatarUri); if ("file".equals(uri.getScheme())) { try { java.io.File file = new java.io.File(uri.getPath()); if (file.exists()) { uri = FileProvider.getUriForFile( avatarView.getContext(), avatarView.getContext().getPackageName() + ".fileprovider", file ); } } catch (Exception e) { // 使用原始 URI } } Glide.with(avatarView) .load(uri) .circleCrop() .error(R.drawable.ic_account_circle_24) .placeholder(R.drawable.ic_account_circle_24) .into(avatarView); return; } Glide.with(avatarView) .load(R.drawable.ic_account_circle_24) .circleCrop() .into(avatarView); } catch (Exception e) { Glide.with(avatarView) .load(R.drawable.ic_account_circle_24) .circleCrop() .into(avatarView); } } } // ========== 语音消息 ViewHolder ========== static class IncomingVoiceVH extends RecyclerView.ViewHolder { private final ImageView avatarView; private final TextView nameText; private final View voiceContainer; private final ImageView voiceIcon; private final TextView durationText; private final TextView timeText; IncomingVoiceVH(@NonNull View itemView) { super(itemView); avatarView = itemView.findViewById(R.id.avatarView); nameText = itemView.findViewById(R.id.nameText); voiceContainer = itemView.findViewById(R.id.voiceContainer); voiceIcon = itemView.findViewById(R.id.voiceIcon); durationText = itemView.findViewById(R.id.durationText); timeText = itemView.findViewById(R.id.timeText); setupAvatarOutline(); } private void setupAvatarOutline() { if (avatarView == null) return; avatarView.post(() -> { if (avatarView.getWidth() > 0 && avatarView.getHeight() > 0) { avatarView.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { outline.setOval(0, 0, view.getWidth(), view.getHeight()); } }); avatarView.setClipToOutline(true); } }); } void bind(ChatMessage message, OnMessageLongClickListener longClickListener, OnVoiceClickListener voiceClickListener) { if (message == null) return; nameText.setText(message.getUsername() != null ? message.getUsername() : ""); timeText.setText(TIME_FORMAT.format(new Date(message.getTimestamp()))); // 显示语音时长 int duration = message.getVoiceDuration(); durationText.setText(duration + "\""); // 设置点击事件 if (voiceContainer != null) { voiceContainer.setOnClickListener(v -> { if (voiceClickListener != null) { voiceClickListener.onVoiceClick(message); } }); } // 设置长按监听 itemView.setOnLongClickListener(v -> { if (longClickListener != null) { int position = getBindingAdapterPosition(); if (position != RecyclerView.NO_POSITION) { longClickListener.onMessageLongClick(message, position, v); return true; } } return false; }); setupAvatarOutline(); loadAvatar(message); } private void loadAvatar(ChatMessage message) { if (avatarView == null) return; try { String avatarUrl = message != null ? message.getAvatarUrl() : null; if (!TextUtils.isEmpty(avatarUrl)) { Glide.with(avatarView) .load(avatarUrl) .circleCrop() .error(R.drawable.ic_account_circle_24) .placeholder(R.drawable.ic_account_circle_24) .into(avatarView); return; } Glide.with(avatarView) .load(R.drawable.ic_account_circle_24) .circleCrop() .into(avatarView); } catch (Exception e) { Glide.with(avatarView) .load(R.drawable.ic_account_circle_24) .circleCrop() .into(avatarView); } } } static class OutgoingVoiceVH extends RecyclerView.ViewHolder { private final ImageView avatarView; private final View voiceContainer; private final ImageView voiceIcon; private final TextView durationText; private final TextView timeText; private final ImageView statusIcon; OutgoingVoiceVH(@NonNull View itemView) { super(itemView); avatarView = itemView.findViewById(R.id.avatarView); voiceContainer = itemView.findViewById(R.id.voiceContainer); voiceIcon = itemView.findViewById(R.id.voiceIcon); durationText = itemView.findViewById(R.id.durationText); timeText = itemView.findViewById(R.id.timeText); statusIcon = itemView.findViewById(R.id.statusIcon); setupAvatarOutline(); } private void setupAvatarOutline() { if (avatarView == null) return; avatarView.post(() -> { if (avatarView.getWidth() > 0 && avatarView.getHeight() > 0) { avatarView.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { outline.setOval(0, 0, view.getWidth(), view.getHeight()); } }); avatarView.setClipToOutline(true); } }); } void bind(ChatMessage message, OnMessageLongClickListener longClickListener, OnVoiceClickListener voiceClickListener) { if (message == null) return; timeText.setText(TIME_FORMAT.format(new Date(message.getTimestamp()))); // 显示消息状态 updateStatus(message); // 显示语音时长 int duration = message.getVoiceDuration(); durationText.setText(duration + "\""); // 设置点击事件 if (voiceContainer != null) { voiceContainer.setOnClickListener(v -> { if (voiceClickListener != null) { voiceClickListener.onVoiceClick(message); } }); } // 设置长按监听 itemView.setOnLongClickListener(v -> { if (longClickListener != null) { int position = getBindingAdapterPosition(); if (position != RecyclerView.NO_POSITION) { longClickListener.onMessageLongClick(message, position, v); return true; } } return false; }); setupAvatarOutline(); loadAvatar(); } private void updateStatus(ChatMessage message) { if (statusIcon != null && message.getStatus() != null) { switch (message.getStatus()) { case SENDING: statusIcon.setImageResource(R.drawable.ic_clock_24); statusIcon.setColorFilter(itemView.getContext().getColor(android.R.color.darker_gray)); statusIcon.setVisibility(View.VISIBLE); break; case SENT: statusIcon.setImageResource(R.drawable.ic_check_24); statusIcon.setColorFilter(itemView.getContext().getColor(android.R.color.darker_gray)); statusIcon.setVisibility(View.VISIBLE); break; case READ: statusIcon.setImageResource(R.drawable.ic_check_double_24); statusIcon.setColorFilter(0xFF4CAF50); statusIcon.setVisibility(View.VISIBLE); break; default: statusIcon.setVisibility(View.GONE); break; } } } private void loadAvatar() { if (avatarView == null) return; try { String avatarUri = avatarView.getContext() .getSharedPreferences("profile_prefs", android.content.Context.MODE_PRIVATE) .getString("profile_avatar_uri", null); if (!TextUtils.isEmpty(avatarUri)) { Uri uri = Uri.parse(avatarUri); if ("file".equals(uri.getScheme())) { try { java.io.File file = new java.io.File(uri.getPath()); if (file.exists()) { uri = FileProvider.getUriForFile( avatarView.getContext(), avatarView.getContext().getPackageName() + ".fileprovider", file ); } } catch (Exception e) { // 使用原始 URI } } Glide.with(avatarView) .load(uri) .circleCrop() .error(R.drawable.ic_account_circle_24) .placeholder(R.drawable.ic_account_circle_24) .into(avatarView); return; } Glide.with(avatarView) .load(R.drawable.ic_account_circle_24) .circleCrop() .into(avatarView); } catch (Exception e) { Glide.with(avatarView) .load(R.drawable.ic_account_circle_24) .circleCrop() .into(avatarView); } } } private static final DiffUtil.ItemCallback DIFF = new DiffUtil.ItemCallback() { @Override public boolean areItemsTheSame(@NonNull ChatMessage oldItem, @NonNull ChatMessage newItem) { // 使用messageId作为唯一标识 return oldItem.getMessageId() != null && oldItem.getMessageId().equals(newItem.getMessageId()); } @Override public boolean areContentsTheSame(@NonNull ChatMessage oldItem, @NonNull ChatMessage newItem) { // 比较消息内容、状态等是否相同 return oldItem.getMessageId() != null && oldItem.getMessageId().equals(newItem.getMessageId()) && oldItem.getStatus() == newItem.getStatus() && oldItem.getMessage() != null && oldItem.getMessage().equals(newItem.getMessage()); } }; }