Kotlin 实战

RecyclerView的Item以及子View点击事件

2018-04-01  本文已影响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,然后绑定一个点击事件不就可以了么?
说干就干:

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,而又不想动手转的话,那就给我留个言……

上一篇 下一篇

猜你喜欢

热点阅读