TV RecyclerView 焦点处理笔记
<p>面对RecyclerView焦点,特别是复杂视图,多类型情况下,需求有时候不按系统定义的走,比如要求首次落焦在第二个,或者焦点移动到边界就不能移动</p><p>如果不遵循焦点流程直接粗暴处理,会导致系统分发事件出异常,焦点乱飞</p>
<p>默认焦点使用 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>-><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><View><span style="color: rgba(0, 0, 0, 1)">(R.id.btn_myself)
}
}
</span><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)">.onFocusSearchFailed(focused, focusDirection, recycler, state)
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>针对焦点移动时自动滚动列表到可见位置,可以使用 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>如果针对焦点到边界位置后不能移动,或者边界触底动效,可以使用焦点拦截 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>-> 4
<span style="color: rgba(0, 0, 255, 1)">else</span> -> 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>-><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>-><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 -><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 > 0 && position + 1 <<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> -> <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 <<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>-><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 < 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><View><span style="color: rgba(0, 0, 0, 1)">(R.id.btn_myself)
</span>?: focused.rootView.findViewById<View><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><View><span style="color: rgba(0, 0, 0, 1)">(R.id.btn_myself)
}
}
</span><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)">.onFocusSearchFailed(focused, focusDirection, recycler, state)
}
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>避免直接request或者clear焦点</p>
<p> </p><br><br>
来源:https://www.cnblogs.com/LiuZhen/p/18959703
頁:
[1]