You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

升级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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 11:00:27