TV RecyclerView 焦点处理笔记

面对RecyclerView焦点,特别是复杂视图,多类型情况下,需求有时候不按系统定义的走,比如要求首次落焦在第二个,或者焦点移动到边界就不能移动

如果不遵循焦点流程直接粗暴处理,会导致系统分发事件出异常,焦点乱飞

默认焦点使用 addOnChildAttachStateChangeListener 监听

图片[1]-TV RecyclerView 焦点处理笔记-牛翰网

recyclerView.addOnChildAttachStateChangeListener(object :
                RecyclerView.OnChildAttachStateChangeListener {
                override fun onChildViewAttachedToWindow(view: View) {
                    val position = recyclerView.getChildAdapterPosition(view)
                    log("position $position view $view")
                    if (position == 3) view.requestFocus()
                }

                override fun onChildViewDetachedFromWindow(view: View) {

                }
            })

View Code

对于超出边界时,系统会触发onFocusSearchFailed

图片[1]-TV RecyclerView 焦点处理笔记-牛翰网

    override fun onFocusSearchFailed(
        focused: View,
        focusDirection: Int,
        recycler: RecyclerView.Recycler,
        state: RecyclerView.State
    ): View? {
        return when (focusDirection) {
            View.FOCUS_UP -> {
                if (focused.parent.parent is RecyclerView) {
                    val position = (focused.parent as? View)?.let { getPosition(it) } ?: -1
                    //...
                } else {
                    focused.rootView.findViewById<View>(R.id.btn_myself)
                }
            }

            else -> super.onFocusSearchFailed(focused, focusDirection, recycler, state)
        }
    }

View Code

针对焦点移动时自动滚动列表到可见位置,可以使用 onRequestChildFocus

图片[1]-TV RecyclerView 焦点处理笔记-牛翰网

    override fun onRequestChildFocus(
        parent: RecyclerView,
        state: RecyclerView.State,
        child: View,
        focused: View?
    ): Boolean {
        val position = parent.getChildAdapterPosition(child)
        scrollToPosition(position)
        return super.onRequestChildFocus(parent, state, child, focused)
    }

View Code

如果针对焦点到边界位置后不能移动,或者边界触底动效,可以使用焦点拦截 onInterceptFocusSearch

完整代码

图片[1]-TV RecyclerView 焦点处理笔记-牛翰网

class ScreenMainGridManager(private val mAdapter: BaseAdapter, context: Context) :
    GridLayoutManager(context, 4) {

    private val marginStart = context.resources.getDimension(R.dimen.common_dp_54)

    init {
        spanSizeLookup = object : SpanSizeLookup() {
            override fun getSpanSize(position: Int): Int {
                val viewType = mAdapter.getItemViewType(position)
                return when (viewType) {
                    ViewType.TITLE.value, ViewType.BANNER.value -> 4
                    else -> 1
                }
            }
        }
    }

    override fun onInterceptFocusSearch(focused: View, direction: Int): View? {
        return when (direction) {
            View.FOCUS_LEFT -> {
                findLeftFocusView(focused) ?: super.onInterceptFocusSearch(focused, direction)
            }

            View.FOCUS_RIGHT -> {
                var isRightFocus = false
                findRecyclerView(focused)?.let {
                    findRootView(focused)?.let { root ->
                        val position = it.getChildAdapterPosition(root)
                        if (position > 0 && position + 1 < itemCount) {
                            val viewType = mAdapter.getItemViewType(position + 1)
                            if (viewType == ViewType.TITLE.value) {
                                isRightFocus = true
                            }
                        } else if (position + 1 == itemCount) {
                            isRightFocus = true
                        }
                    }
                }
                if (isRightFocus) focused else super.onInterceptFocusSearch(focused, direction)
            }

            else -> super.onInterceptFocusSearch(focused, direction)
        }
    }

    private fun findLeftFocusView(focused: View): View? {
        findRecyclerView(focused)?.let {
            val location = IntArray(2)
            focused.getLocationOnScreen(location)
            if (location[0] < marginStart) {
                return focused
            }
        }
        return null
    }

    private fun findRecyclerView(focused: View): RecyclerView? {
        var parent: ViewParent? = focused.parent
        for (i in 0 until 2) {
            if (parent is RecyclerView) break
            parent = parent?.parent
        }
        return parent as? RecyclerView
    }

    /** item root view */
    private fun findRootView(focused: View): View? {
        var result: View? = focused
        var parent = focused.parent
        for (i in 0 until 2) {
            if (parent is RecyclerView) {
                return result
            }
            result = parent as? View
            parent = parent?.parent
        }
        return result
    }

    override fun onRequestChildFocus(
        parent: RecyclerView,
        state: RecyclerView.State,
        child: View,
        focused: View?
    ): Boolean {
        val position = parent.getChildAdapterPosition(child)
        scrollToPosition(position)
        return super.onRequestChildFocus(parent, state, child, focused)
    }

    override fun onFocusSearchFailed(
        focused: View,
        focusDirection: Int,
        recycler: RecyclerView.Recycler,
        state: RecyclerView.State
    ): View? {
        return when (focusDirection) {
            View.FOCUS_UP -> {
                if (focused.parent.parent is RecyclerView) {
                    val position = (focused.parent as? View)?.let { getPosition(it) } ?: -1
                    if (position < 0) {
                        super.onFocusSearchFailed(focused, focusDirection, recycler, state)
                    } else {
                        val item = mAdapter.getItem(position)
                        if (item is MainBannerItem || item is MeAddItem) {
                            focused.rootView.findViewById<View>(R.id.btn_myself)
                                ?: focused.rootView.findViewById<View>(R.id.btn_back)
                        } else {
                            scrollToPosition(0)
                            super.onFocusSearchFailed(focused, focusDirection, recycler, state)
                        }
                    }
                } else {
                    focused.rootView.findViewById<View>(R.id.btn_myself)
                }
            }

            else -> super.onFocusSearchFailed(focused, focusDirection, recycler, state)
        }
    }

}

View Code

避免直接request或者clear焦点

 

来源链接:https://www.cnblogs.com/LiuZhen/p/18959703

© 版权声明
THE END
支持一下吧
点赞8 分享
评论 抢沙发
头像
请文明发言!
提交
头像

昵称

取消
昵称表情代码快捷回复

    暂无评论内容