确定度 發表於 2025-7-14 15:21:00

TV RecyclerView 焦点处理笔记

<p>面对RecyclerView焦点,特别是复杂视图,多类型情况下,需求有时候不按系统定义的走,比如要求首次落焦在第二个,或者焦点移动到边界就不能移动</p>
<p>如果不遵循焦点流程直接粗暴处理,会导致系统分发事件出异常,焦点乱飞</p>
<p>默认焦点使用&nbsp;addOnChildAttachStateChangeListener 监听</p>
<div class="cnblogs_code"><img id="code_img_closed_a834a5d5-ec2e-42cf-a679-fe25afcb0fb6" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_a834a5d5-ec2e-42cf-a679-fe25afcb0fb6" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_a834a5d5-ec2e-42cf-a679-fe25afcb0fb6" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 0, 1)">recyclerView.addOnChildAttachStateChangeListener(object :
                RecyclerView.OnChildAttachStateChangeListener {
                override fun onChildViewAttachedToWindow(view: View) {
                  val position </span>=<span style="color: rgba(0, 0, 0, 1)"> recyclerView.getChildAdapterPosition(view)
                  log(</span>"position $position view $view"<span style="color: rgba(0, 0, 0, 1)">)
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span> (position == 3<span style="color: rgba(0, 0, 0, 1)">) view.requestFocus()
                }

                override fun onChildViewDetachedFromWindow(view: View) {

                }
            })</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>对于超出边界时,系统会触发onFocusSearchFailed</p>
<div class="cnblogs_code"><img id="code_img_closed_807cefab-3fb3-4ec2-ac8c-42858c3dd96c" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_807cefab-3fb3-4ec2-ac8c-42858c3dd96c" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_807cefab-3fb3-4ec2-ac8c-42858c3dd96c" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 0, 1)">    override fun onFocusSearchFailed(
      focused: View,
      focusDirection: Int,
      recycler: RecyclerView.Recycler,
      state: RecyclerView.State
    ): View</span>?<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> when (focusDirection) {
            View.FOCUS_UP </span>-&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (focused.parent.parent is RecyclerView) {
                  val position </span>= (focused.parent as? View)?.let { getPosition(it) } ?: -1
                  <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">...</span>
                } <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                  focused.rootView.findViewById</span>&lt;View&gt;<span style="color: rgba(0, 0, 0, 1)">(R.id.btn_myself)
                }
            }

            </span><span style="color: rgba(0, 0, 255, 1)">else</span> -&gt; <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onFocusSearchFailed(focused, focusDirection, recycler, state)
      }
    }</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>针对焦点移动时自动滚动列表到可见位置,可以使用&nbsp;onRequestChildFocus</p>
<div class="cnblogs_code"><img id="code_img_closed_a2783c23-a8cb-4476-8296-6006bb61d8ec" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_a2783c23-a8cb-4476-8296-6006bb61d8ec" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_a2783c23-a8cb-4476-8296-6006bb61d8ec" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 0, 1)">    override fun onRequestChildFocus(
      parent: RecyclerView,
      state: RecyclerView.State,
      child: View,
      focused: View</span>?<span style="color: rgba(0, 0, 0, 1)">
    ): Boolean {
      val position </span>=<span style="color: rgba(0, 0, 0, 1)"> parent.getChildAdapterPosition(child)
      scrollToPosition(position)
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onRequestChildFocus(parent, state, child, focused)
    }</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>如果针对焦点到边界位置后不能移动,或者边界触底动效,可以使用焦点拦截&nbsp;onInterceptFocusSearch</p>
<p>完整代码</p>
<div class="cnblogs_code"><img id="code_img_closed_70825bf2-4c6e-44ba-9bc2-bcd832660202" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_70825bf2-4c6e-44ba-9bc2-bcd832660202" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_70825bf2-4c6e-44ba-9bc2-bcd832660202" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">class</span> ScreenMainGridManager(<span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> val mAdapter: BaseAdapter, context: Context) :
    GridLayoutManager(context, </span>4<span style="color: rgba(0, 0, 0, 1)">) {

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val marginStart =<span style="color: rgba(0, 0, 0, 1)"> context.resources.getDimension(R.dimen.common_dp_54)

    init {
      spanSizeLookup </span>=<span style="color: rgba(0, 0, 0, 1)"> object : SpanSizeLookup() {
            override fun getSpanSize(position: Int): Int {
                val viewType </span>=<span style="color: rgba(0, 0, 0, 1)"> mAdapter.getItemViewType(position)
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> when (viewType) {
                  ViewType.TITLE.value, ViewType.BANNER.value </span>-&gt; 4
                  <span style="color: rgba(0, 0, 255, 1)">else</span> -&gt; 1<span style="color: rgba(0, 0, 0, 1)">
                }
            }
      }
    }

    override fun onInterceptFocusSearch(focused: View, direction: Int): View</span>?<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> when (direction) {
            View.FOCUS_LEFT </span>-&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                findLeftFocusView(focused) </span>?: <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onInterceptFocusSearch(focused, direction)
            }

            View.FOCUS_RIGHT </span>-&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                var isRightFocus </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
                findRecyclerView(focused)</span>?<span style="color: rgba(0, 0, 0, 1)">.let {
                  findRootView(focused)</span>?.let { root -&gt;<span style="color: rgba(0, 0, 0, 1)">
                        val position </span>=<span style="color: rgba(0, 0, 0, 1)"> it.getChildAdapterPosition(root)
                        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (position &gt; 0 &amp;&amp; position + 1 &lt;<span style="color: rgba(0, 0, 0, 1)"> itemCount) {
                            val viewType </span>= mAdapter.getItemViewType(position + 1<span style="color: rgba(0, 0, 0, 1)">)
                            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (viewType ==<span style="color: rgba(0, 0, 0, 1)"> ViewType.TITLE.value) {
                              isRightFocus </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
                            }
                        } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (position + 1 ==<span style="color: rgba(0, 0, 0, 1)"> itemCount) {
                            isRightFocus </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
                        }
                  }
                }
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (isRightFocus) focused <span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onInterceptFocusSearch(focused, direction)
            }

            </span><span style="color: rgba(0, 0, 255, 1)">else</span> -&gt; <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onInterceptFocusSearch(focused, direction)
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> fun findLeftFocusView(focused: View): View?<span style="color: rgba(0, 0, 0, 1)"> {
      findRecyclerView(focused)</span>?<span style="color: rgba(0, 0, 0, 1)">.let {
            val location </span>= IntArray(2<span style="color: rgba(0, 0, 0, 1)">)
            focused.getLocationOnScreen(location)
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (location &lt;<span style="color: rgba(0, 0, 0, 1)"> marginStart) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> focused
            }
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> fun findRecyclerView(focused: View): RecyclerView?<span style="color: rgba(0, 0, 0, 1)"> {
      var parent: ViewParent</span>? =<span style="color: rgba(0, 0, 0, 1)"> focused.parent
      </span><span style="color: rgba(0, 0, 255, 1)">for</span> (i in 0 until 2<span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (parent is RecyclerView) <span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">
            parent </span>= parent?<span style="color: rgba(0, 0, 0, 1)">.parent
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> parent as?<span style="color: rgba(0, 0, 0, 1)"> RecyclerView
    }

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> item root view </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">private</span> fun findRootView(focused: View): View?<span style="color: rgba(0, 0, 0, 1)"> {
      var result: View</span>? =<span style="color: rgba(0, 0, 0, 1)"> focused
      var parent </span>=<span style="color: rgba(0, 0, 0, 1)"> focused.parent
      </span><span style="color: rgba(0, 0, 255, 1)">for</span> (i in 0 until 2<span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (parent is RecyclerView) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result
            }
            result </span>= parent as?<span style="color: rgba(0, 0, 0, 1)"> View
            parent </span>= parent?<span style="color: rgba(0, 0, 0, 1)">.parent
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result
    }

    override fun onRequestChildFocus(
      parent: RecyclerView,
      state: RecyclerView.State,
      child: View,
      focused: View</span>?<span style="color: rgba(0, 0, 0, 1)">
    ): Boolean {
      val position </span>=<span style="color: rgba(0, 0, 0, 1)"> parent.getChildAdapterPosition(child)
      scrollToPosition(position)
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onRequestChildFocus(parent, state, child, focused)
    }

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

            </span><span style="color: rgba(0, 0, 255, 1)">else</span> -&gt; <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onFocusSearchFailed(focused, focusDirection, recycler, state)
      }
    }

}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>避免直接request或者clear焦点</p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/LiuZhen/p/18959703
頁: [1]
查看完整版本: TV RecyclerView 焦点处理笔记