美文网首页Kotlin 实战
RecyclerView的Item以及子View点击事件

RecyclerView的Item以及子View点击事件

作者: 进击的小强 | 来源:发表于2018-04-01 20:40 被阅读128次

    各实现方法以及存在的问题

    想要实现RecyclerView的Item的点击事件,你可能会想到各种方法。
    比如:
    1.在创建ItemView时添加点击监听。
    也就是在 RecyclerView.Adapter<ViewHolder>内的onBindViewHolder()直接实现绑定。
    问题是“逻辑跟adapter类耦合严重”。
    2.通过RecyclerView已有的方法addOnItemTouchListener()实现.
    重载SimpleOnItemTouchListener类,用GestureDetectorCompat捕获item的点击事件。具体代Google一下即可。
    问题是“子view不能获取点击”

    依据“OnChildAttachStateChangeListener”实现点击监听

    今天我们重点不在于前面这两种方法。而是要具体讲解一下第三种Item点击事件的实现。也就是依托于OnChildAttachStateChangeListener的实现。
    目标:
    1.足够优雅。
    2.能够获取Item的点击,也能够实现子View的点击事件。
    实现:
    知道“OnChildAttachStateChangeListener”这个关键词,我们便可以Google一下此实现了。下面便是千篇一律的代码:

    class ItemClickSupport private constructor(private val mRecyclerView: RecyclerView) {
        private var mOnItemClickListener: OnItemClickListener? = null
        private var mOnItemLongClickListener: OnItemLongClickListener? = null
        private val mOnClickListener = View.OnClickListener { v ->
            if (mOnItemClickListener != null) {
                val holder = mRecyclerView.getChildViewHolder(v)
                mOnItemClickListener!!.onItemClicked(mRecyclerView, holder.adapterPosition, v)
            }
        }
        private val mOnLongClickListener = View.OnLongClickListener { v ->
            if (mOnItemLongClickListener != null) {
                val holder = mRecyclerView.getChildViewHolder(v)
                return@OnLongClickListener mOnItemLongClickListener!!.onItemLongClicked(mRecyclerView, holder.adapterPosition, v)
            }
            false
        }
        private val mAttachListener = object : RecyclerView.OnChildAttachStateChangeListener {
            override fun onChildViewAttachedToWindow(view: View) {
                if (mOnItemClickListener != null) {
                    view.setOnClickListener(mOnClickListener)
                }
                if (mOnItemLongClickListener != null) {
                    view.setOnLongClickListener(mOnLongClickListener)
                }
            }
    
            override fun onChildViewDetachedFromWindow(view: View) {
    
            }
        }
    
        init {
            mRecyclerView.setTag(R.id.item_click_support, this)
            mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener)
        }
    
        fun addOnItemClickListener(listener: OnItemClickListener): ItemClickSupport {
            mOnItemClickListener = listener
            return this
        }
    
        fun addOnItemLongClickListener(listener: OnItemLongClickListener): ItemClickSupport {
            mOnItemLongClickListener = listener
            return this
        }
    
        private fun detach(view: RecyclerView) {
            view.removeOnChildAttachStateChangeListener(mAttachListener)
            view.setTag(R.id.item_click_support, null)
        }
    
        // 点击接口
        public interface OnItemClickListener {
            fun onItemClicked(recyclerView: RecyclerView, position: Int, v: View)
        }
    
        // 长按接口
        interface OnItemLongClickListener {
            fun onItemLongClicked(recyclerView: RecyclerView, position: Int, v: View): Boolean
        }
    
        companion object {
    
            fun addTo(view: RecyclerView): ItemClickSupport {
                var support: ItemClickSupport? = view.getTag(R.id.item_click_support) as ItemClickSupport?
                if (support == null) {
                    support = ItemClickSupport(view)
                }
                return support
            }
    
            fun removeFrom(view: RecyclerView): ItemClickSupport? {
                val support = view.getTag(R.id.item_click_support) as ItemClickSupport
                support.detach(view)
                return support
            }
        }
    }
    

    该方法是根据国外的一篇博客实现,原文链接如下:Getting your clicks on RecyclerView

    具体使用时如下:

    // 点击
          ItemClickSupport.addTo(recycler_view).addOnItemClickListener(object : ItemClickSupport.OnItemClickListener {
                override fun onItemClicked(recyclerView: RecyclerView, position: Int, v: View) {
                    Snackbar.make(v, "recycler_view click", Snackbar.LENGTH_LONG).show()
                }
    
            })
    

    然而,此时这个类是不能实现子view的点击监听的。

    真正实现RecyclerView的点击事件监听

    为了让ItemClickSupport既可以实现Item的点击监听又可以实现子View的点击监听。我们需要改造上面的类。
    首先找到核心代码:

    private val mAttachListener = object : RecyclerView.OnChildAttachStateChangeListener {
            override fun onChildViewAttachedToWindow(view: View) {
                if (mOnItemClickListener != null) {
                    view.setOnClickListener(mOnClickListener)
                }
                if (mOnItemLongClickListener != null) {
                    view.setOnLongClickListener(mOnLongClickListener)
                }
            }
    
            override fun onChildViewDetachedFromWindow(view: View) {
    
            }
        }
    

    此处我们根据“mOnItemClickListener”实现了对Item事件的监听,既然如此,
    此时我们根据child = view.findViewById(resourceId)
    来获取我们想监听的子View,然后绑定一个点击事件不就可以了么?
    说干就干:

    • 首先要有一个childId来保存子View的id,并且可以赋值。
    • 要有一个OnChildClickListener接口来处理子View的点击事件
    • 最好能够做到链式调用。这样使用会比较方便。
    • 依旧将ItemClickSupport依托tag绑定到到recyclerView上,避免多个实例。
      根据上面几点要求,所以我想到了Builder模式,直接上代码。
      具体如下:
    class ItemClickSupport private constructor(val builder: Builder, private val mRecyclerView: RecyclerView) {
        var mOnItemClickListener: OnItemClickListener? = null
        var mOnChildClickListener: OnChildClickListener? = null
    
        var mOnItemLongClickListener: OnItemLongClickListener? = null
    
        private val onChildClickListener = View.OnClickListener { v ->
            if (mOnChildClickListener != null) {
                val holder = mRecyclerView.findContainingViewHolder(v)
                if (holder != null) {
                    mOnChildClickListener!!.onChildClicked(mRecyclerView, holder.adapterPosition, v)
                }
            }
        }
        private val mOnClickListener = View.OnClickListener { v ->
            if (mOnItemClickListener != null) {
                val holder = mRecyclerView.findContainingViewHolder(v)
                if (holder != null) {
                    mOnItemClickListener!!.onItemClicked(mRecyclerView, holder.adapterPosition, v)
                }
            }
        }
        private val mOnLongClickListener = View.OnLongClickListener { v ->
            if (mOnItemLongClickListener != null) {
                val holder = mRecyclerView.getChildViewHolder(v)
                return@OnLongClickListener mOnItemLongClickListener!!.onItemLongClicked(mRecyclerView, holder.adapterPosition, v)
            }
            false
        }
        private val mAttachListener = object : RecyclerView.OnChildAttachStateChangeListener {
            override fun onChildViewAttachedToWindow(view: View) {
                if (mOnItemClickListener != null) {
                    view.setOnClickListener(mOnClickListener)
                }
                if (mOnChildClickListener != null) {
                    if (builder.childId != -2) {
                        val child = view.findViewById<View>(builder.childId)
                        if (child != null) {
                            child.setOnClickListener(onChildClickListener)
                        }
                    }
                }
                if (mOnItemLongClickListener != null) {
                    view.setOnLongClickListener(mOnLongClickListener)
                }
            }
    
            override fun onChildViewDetachedFromWindow(view: View) {
            }
        }
    
        init {
            mRecyclerView.setTag(builder.getBindingTagId(), this)
            mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener)
        }
    
        fun addOnChildClickListener(listener: OnChildClickListener): ItemClickSupport {
            mOnChildClickListener = listener
            return this
        }
    
        fun addOnItemClickListener(listener: OnItemClickListener): ItemClickSupport {
            mOnItemClickListener = listener
            return this
        }
    
        fun addOnItemLongClickListener(listener: OnItemLongClickListener): ItemClickSupport {
            mOnItemLongClickListener = listener
            return this
        }
    
        private fun detach(view: RecyclerView) {
            view.removeOnChildAttachStateChangeListener(mAttachListener)
            view.setTag(builder.getBindingTagId(), null)
        }
    
        // 子View点击接口
        interface OnChildClickListener {
            fun onChildClicked(recyclerView: RecyclerView, position: Int, v: View)
        }
    
        // 点击接口
        interface OnItemClickListener {
            fun onItemClicked(recyclerView: RecyclerView, position: Int, v: View)
        }
    
        // 长按接口
        interface OnItemLongClickListener {
            fun onItemLongClicked(recyclerView: RecyclerView, position: Int, v: View): Boolean
        }
    
        class Builder() {
            //view默认id为-1
            internal var childId: Int = -2
    
            internal fun getBindingTagId(): Int {
                if (childId != -2) {
                    return childId
                } else {
                    return R.id.item_click_support
                }
            }
    
            fun withId(@IdRes id: Int): Builder {
                childId = id
                return this@Builder
            }
    
            fun buildTo(view: RecyclerView): ItemClickSupport {
                var support: ItemClickSupport? = view.getTag(getBindingTagId()) as ItemClickSupport?
                if (support == null) {
                    support = ItemClickSupport(this, view)
                }
                return support
            }
        }
    }
    

    如果只监听Item的,则可以这样写

     ItemClickSupport.Builder().buildTo(recycler_view).addOnItemClickListener(object : ItemClickSupport.OnItemClickListener {
                override fun onItemClicked(recyclerView: RecyclerView, position: Int, v: View) {
                    Snackbar.make(v, "recycler_view click", Snackbar.LENGTH_LONG).show()
                }
    
            })
    

    如果只监听子View的,则可以这样写

    ItemClickSupport.Builder().withId(R.id.exchange_btn)
                    .buildTo(rv).addOnChildClickListener(object : ItemClickSupport.OnChildClickListener {
                        override fun onChildClicked(recyclerView: RecyclerView, position: Int, v: View) {
                            Snackbar.make(v, "child click", Snackbar.LENGTH_LONG).show()
                        }
                    })
    

    如果两者都监听,可以这样写(当然也可以分开写):

    ItemClickSupport.Builder().withId(R.id.exchange_btn)
                    .buildTo(rv).addOnChildClickListener(object : ItemClickSupport.OnChildClickListener {
                        override fun onChildClicked(recyclerView: RecyclerView, position: Int, v: View) {
                            Snackbar.make(v, "child click", Snackbar.LENGTH_LONG).show()
                        }
                    }).addOnItemClickListener(object : ItemClickSupport.OnItemClickListener {
                        override fun onItemClicked(recyclerView: RecyclerView, position: Int, v: View) {
                            Snackbar.make(v, "item click", Snackbar.LENGTH_LONG).show()
                        }
                    })
    

    里面有一个小细节是

     internal fun getBindingTagId(): Int {
                if (childId != -2) {
                    return childId
                } else {
                    return R.id.item_click_support
                }
            }
    

    为何要这样写,可以自己琢磨一下 _

    注意:代码全部为kotlin。建议大伙也用一下。如果实在还是想用java,而又不想动手转的话,那就给我留个言……

    相关文章

      网友评论

      本文标题:RecyclerView的Item以及子View点击事件

      本文链接:https://www.haomeiwen.com/subject/kvuacftx.html