CymChad / BaseRecyclerViewAdapterHelper

BRVAH:Powerful and flexible RecyclerAdapter
http://www.recyclerview.org/
MIT License
24.21k stars 5.14k forks source link

請問BaseNodeProvider中對View增加onclicklistener的正確方式 #3739

Closed Jikuai closed 1 year ago

Jikuai commented 1 year ago

version : 3.0.11 sample code:

`public class FirstProvider extends BaseNodeProvider {

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

@Override
public int getLayoutId() {
    return R.layout.item_node_first;
}

@Override
public void convert(@NotNull BaseViewHolder helper, @NotNull BaseNode data) {
    FirstNode entity = (FirstNode) data;
    helper.setText(R.id.title, entity.getTitle());
    helper.setImageResource(R.id.iv, R.mipmap.arrow_r);
}

@Override
public void onClick(@NotNull BaseViewHolder helper, @NotNull View view, BaseNode data, int position) {
    // 这里使用payload进行增量刷新(避免整个item刷新导致的闪烁,不自然)
    getAdapter().expandOrCollapse(position, true, true, NodeTreeAdapter.EXPAND_COLLAPSE_PAYLOAD);
}

} `

請教各位大佬,我在onClick嘗試去取得R.id.title的OnClicklistener,但該方法好像只監聽item是否被click 當使用view.getId()時,獲得的值都是-1 若需要監聽R.id.title的話,是不是直接在convert設定監聽就可以? 如: ` TextView tvTitle = helper.getView(R.id.tv_title);

    tvTitle.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
       tvTitle.setText("clicked");
        }
    });

`

請問這樣操作是否造成效能低落?還是有更好的監聽方法,謝謝大家

tianma8023 commented 1 year ago

@Jikuai 一切的疑惑都可以在源码中得到答案。 看一下 BaseNodeAdapter 的继承关系:

BaseNodeAdapter -> BaseProviderMultiAdapter -> BaseQuickAdapter -> RecyclerView.Adapter

创建 ViewHolder 以及绑定相关点击事件监听的逻辑执行顺序:

  1. BaseQuickAdapter#onCreateViewHolder():
    // 创建 ViewHolder
    val viewHolder = onCreateDefViewHolder(parent, viewType)
    // 绑定 View 点击事件到 viewHolder 上,我们关注这个流程
    bindViewClickListener(viewHolder, viewType)
  2. BaseQuickAdapter#bindViewClickListener():
    // 如果外部通过 adapter 设置了 OnItemClickListener; 那么绑定 item 的点击事件
    mOnItemClickListener?.let {
    viewHolder.itemView.setOnClickListener { v ->
        var position = viewHolder.adapterPosition
        if (position == RecyclerView.NO_POSITION) {
            return@setOnClickListener
        }
        position -= headerLayoutCount
        setOnItemClick(v, position)
    }
    }
    // 如果外部设置了 OnItemChildClickListner,那么就去 childClickViewIds 集合中寻找需要需要被点击的 item 中的元素(child),然后设置点击事件
    mOnItemChildClickListener?.let {
    for (id in getChildClickViewIds()) {
        viewHolder.itemView.findViewById<View>(id)?.let { childView ->
            if (!childView.isClickable) {
                childView.isClickable = true
            }
            childView.setOnClickListener { v ->
                var position = viewHolder.adapterPosition
                if (position == RecyclerView.NO_POSITION) {
                    return@setOnClickListener
                }
                position -= headerLayoutCount
                setOnItemChildClick(v, position)
            }
        }
    }
    }
  3. BaseProviderMultiAdapter#bindViewClickListener() 等:
    
    override fun bindViewClickListener(viewHolder: BaseViewHolder, viewType: Int) {
    // 先走父类的逻辑,也就是步骤2的逻辑
    super.bindViewClickListener(viewHolder, viewType)
    bindClick(viewHolder)
    bindChildClick(viewHolder, viewType)
    }

protected open fun bindClick(viewHolder: BaseViewHolder) { // 如果外部adapter没有设置 item 的点击监听,则回调给 itemProvider 的 onClick 方法 if (getOnItemClickListener() == null) { viewHolder.itemView.setOnClickListener { var position = viewHolder.adapterPosition if (position == RecyclerView.NO_POSITION) { return@setOnClickListener } position -= headerLayoutCount

  val itemViewType = viewHolder.itemViewType
  val provider = mItemProviders.get(itemViewType)

  provider.onClick(viewHolder, it, data[position], position)
}

} //... 省略 }

protected open fun bindChildClick(viewHolder: BaseViewHolder, viewType: Int) { // 如果外部adapter没有设置 item 内元素(child)的点击监听,则回调给 itemProvider 的 onChildClick 方法 if (getOnItemChildClickListener() == null) { val provider = getItemProvider(viewType) ?: return // 同样地,也需要在 provider 的 childClickViewIds 集合中寻找需要被点击的元素(child) val ids = provider.getChildClickViewIds() ids.forEach { id -> viewHolder.itemView.findViewById(id)?.let { if (!it.isClickable) { it.isClickable = true } it.setOnClickListener { v -> var position: Int = viewHolder.adapterPosition if (position == RecyclerView.NO_POSITION) { return@setOnClickListener } position -= headerLayoutCount provider.onChildClick(viewHolder, v, data[position], position) } } } } }


所以,BaseNodeAdapter 有以下推荐的设置item中的child元素的点击事件的方式:
1. 通过 BaseNodeProvider 体系来设置:
  ```kotlin
  // 1. 向 BaseNodeProvider 中添加可点击的child元素id,比如在Adapter初始化的时候设置
  class XXAdapter(nodes: MutableList<BaseNode>) : BaseNodeAdapter(nodes) {
    init { 
      val myNodeProvider = XXNodeProvider().apply {
        this.addChildClickViewIds(R.id.title)
      }
      addNodeProvider(myNodeProvider)
    }
  }
  // 2. 在 BaseNodeProvider 实现类中实现 onChildClick 方法
  class XXNodeProvider : BaseNodeProvider() {
    //.. 省略其他方法
    override fun onChildClick(helper: BaseViewHolder, view: View, data: BaseNode, position: Int) {
        when(view.id) {
          R.id.title -> {
            // ...
          }
        }
    }
  }
  1. 通过 BaseQuickAdapter 的体系来设置
    // 1. 向 BaseQuickAdapter 中添加可点击的child元素id
    xxAdapter.addChildClickViewIds(R.id.title)
    // 2. BaseQuickAdapter 设置 OnItemChildClickListener
    xxAdapter.setOnItemChildClickListener { adapter, view, position ->
    when(view.id) {
      R.id.title -> {
        // ...
      }
    }
    }

最后,在 convert 中设置child元素的点击事件是可行的,但是 convert 是用来刷新 ViewHolder 中的数据的,所以即便是同一个 ViewHolder 对象,可能也会因为 RecyclerView item 回收机制的原因,调用多次的 convert 方法。所以如无必要,不推荐这样设置。