升级AGP8.0:将ButterKnife多ViewHolder RecyclerView重构为ViewBinding
层级式多ViewHolder的RecyclerView迁移ViewBinding方案
核心思路
你的场景核心问题是自定义View作为RecyclerView的item布局,其内部嵌套了实际控件所在的布局,所以不能直接用itemView的Binding,而是要先获取自定义View内部的布局Binding,再做控件绑定。下面分步骤给出改造方案:
1. 暴露自定义View的内部Binding
以MessageTextViewLeft为例,它的实际控件都在widget_text_msg_left.xml里,开启ViewBinding后会自动生成WidgetTextMsgLeftBinding,在自定义View中添加方法暴露这个Binding:
public class MessageTextViewLeft extends LinearLayout { private WidgetTextMsgLeftBinding binding; public MessageTextViewLeft(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { binding = WidgetTextMsgLeftBinding.inflate(LayoutInflater.from(getContext()), this, true); } // 对外提供内部Binding的访问入口 public WidgetTextMsgLeftBinding getBinding() { return binding; } }
2. 逐层级改造ViewHolder
基类ViewHolder(无改动)
public class ViewHolder extends RecyclerView.ViewHolder { ViewHolder(View v) { super(v); } }
SeparatorViewHolder改造
直接用对应布局的Binding替换ButterKnife绑定:
public class SeparatorViewHolder extends ViewHolder { private WidgetMessageSeparatorBinding binding; SeparatorViewHolder(WidgetMessageSeparatorBinding binding) { super(binding.getRoot()); this.binding = binding; } // 使用控件直接通过binding.mDate访问 }
MessageViewHolder改造
通过自定义View获取内部Binding,替换@BindView,手动设置点击事件替代@OnClick:
public class MessageViewHolder extends ViewHolder { protected WidgetTextMsgLeftBinding binding; private long id; private MessageStatus mMessageStatus; MessageViewHolder(View v) { super(v); // 从自定义View拿到内部布局的Binding if (v instanceof MessageTextViewLeft) { this.binding = ((MessageTextViewLeft) v).getBinding(); } // 原初始化逻辑改用Binding实现 binding.messageCustomWidget.setVisibility(View.GONE); binding.messageTextviewSenderName.setVisibility(View.GONE); if (binding.messageTextviewResponseLayout != null) { binding.messageTextviewResponseLayout.setVisibility(View.GONE); } // 替换@OnClick,手动设置点击事件 binding.messageTextviewAck.setOnClickListener(v1 -> { easyLog.info("[onMessageAckClick] Ho cliccato sull'ack del messaggio con id -> "+ id); showMessageAckConfirmDialog(id, true); }); binding.messageTextviewNack.setOnClickListener(v1 -> { easyLog.info("[onMessageAckClick] Ho cliccato sul nack del messaggio con id -> "+ id); showMessageAckConfirmDialog(id, false); }); } public void setId(long id) { this.id = id; } }
AudioViewHolder改造
复用父类的Binding,自身加载的音频布局也用ViewBinding处理:
public class AudioViewHolder extends MessageViewHolder { private ListItemAudioLeftBinding audioBinding; AudioViewHolder(View v, int audioLayout) { super(v); // 用ViewBinding加载音频布局 audioBinding = ListItemAudioLeftBinding.inflate(LayoutInflater.from(v.getContext())); binding.messageCustomWidget.addView(audioBinding.getRoot()); binding.messageCustomWidget.setVisibility(View.VISIBLE); // 直接通过Binding访问音频控件 AudioControlView mAudioControlView = audioBinding.playerView; } }
ImageViewHolder改造
同理,用ViewBinding加载图片布局:
public class ImageViewHolder extends MessageViewHolder { private MessageImageView mMessageImageView; private ImageView mImageView; ImageViewHolder(View v, int imageLayout) { super(v); // 根据布局类型加载对应Binding if (imageLayout == R.layout.list_item_image_left) { ListItemImageLeftBinding imageBinding = ListItemImageLeftBinding.inflate(LayoutInflater.from(v.getContext())); mMessageImageView = imageBinding.messageImage; } else { ListItemImageRightBinding imageBinding = ListItemImageRightBinding.inflate(LayoutInflater.from(v.getContext())); mMessageImageView = imageBinding.messageImage; } mImageView = mMessageImageView.getImageView(); binding.messageCustomWidget.addView(mMessageImageView); binding.messageCustomWidget.setVisibility(View.VISIBLE); } }
3. 调整onCreateViewHolder逻辑
创建ViewHolder时直接传递对应Binding,或保持原View初始化逻辑(内部已改用Binding):
@Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); switch(viewType) { case TYPE_SEPARATOR: WidgetMessageSeparatorBinding separatorBinding = WidgetMessageSeparatorBinding.inflate(inflater, parent, false); return new SeparatorViewHolder(separatorBinding); case TYPE_MESSAGE_TEXT_LEFT: View textLeftView = inflater.inflate(R.layout.list_item_text_left, parent, false); return new MessageViewHolder(textLeftView); case TYPE_MESSAGE_TEXT_WITH_IMAGE_LEFT: View imageLeftView = inflater.inflate(R.layout.list_item_text_left, parent, false); return new ImageViewHolder(imageLeftView, R.layout.list_item_image_left); case TYPE_MESSAGE_TEXT_WITH_AUDIO_LEFT: View audioLeftView = inflater.inflate(R.layout.list_item_text_left, parent, false); return new AudioViewHolder(audioLeftView, R.layout.list_item_audio_left); // 剩余case保持原逻辑,内部已替换为Binding实现 default: return null; } }
4. 修改onBindViewHolder的控件绑定逻辑
通过ViewHolder的Binding直接访问控件完成数据绑定:
private void onBindTextLeftViewHolder(MessageViewHolder holder, InboxMessage item) { holder.binding.messageTextviewText.setText(item.getContent()); holder.binding.messageTextviewTime.setText(item.getTime()); holder.setId(item.getId()); // 其他绑定逻辑... }
注意事项
- 确保所有用到的布局都开启ViewBinding(AGP8.0默认开启,若未开启需在module的build.gradle中配置
viewBinding { enabled = true }) - ViewBinding会自动处理空安全,但如果布局中可能不存在目标控件,仍需添加非空判断
- 自定义View的Binding必须在构造方法中正确初始化并暴露,否则ViewHolder无法获取内部控件
内容的提问来源于stack exchange,提问作者Lubron
相关产品推荐
相关产品推荐

