diff --git a/app/src/main/java/com/fongmi/android/tv/ui/activity/TmdbDetailActivity.java b/app/src/main/java/com/fongmi/android/tv/ui/activity/TmdbDetailActivity.java index 2e3fa4fd55..4b8898be0c 100644 --- a/app/src/main/java/com/fongmi/android/tv/ui/activity/TmdbDetailActivity.java +++ b/app/src/main/java/com/fongmi/android/tv/ui/activity/TmdbDetailActivity.java @@ -6118,7 +6118,6 @@ public class TmdbDetailActivity extends PlaybackActivity implements TrackDialog. private void showTmdbEpisodeDetail(Episode episode, int episodeNumber, TmdbEpisode boundTmdbEpisode, RecyclerView returnRecycler) { android.content.DialogInterface.OnDismissListener dismissListener = d -> { if (binding == null || returnRecycler == null) return; - View previousFocus = returnRecycler.getRootView().findFocus(); returnRecycler.post(() -> { if (binding == null || isFinishing() || isDestroyed() || !returnRecycler.isAttachedToWindow()) return; rerenderEpisodeViewportOnly(false, true, true); @@ -6130,8 +6129,6 @@ public class TmdbDetailActivity extends PlaybackActivity implements TrackDialog. // post/postOnAnimation 不保证刷新已布局;在真实布局后的 pre-draw 恢复精确卡片。 OneShotPreDrawListener.add(returnRecycler, () -> { if (binding == null || isFinishing() || isDestroyed() || !returnRecycler.isShown()) return; - View focus = returnRecycler.getRootView().findFocus(); - if (focus != null && focus != previousFocus && !isFocusInside(focus, returnRecycler)) return; restoreEpisodeDetailFocus(returnRecycler, episode); }); recoverEpisodeViewportIfDetached(); @@ -10152,17 +10149,32 @@ public class TmdbDetailActivity extends PlaybackActivity implements TrackDialog. RecyclerView.Adapter adapter = recycler.getAdapter(); if (adapter == null || adapter.getItemCount() == 0) return false; int target = Math.max(0, Math.min(position, adapter.getItemCount() - 1)); - recycler.stopScroll(); - RecyclerView.ViewHolder visibleHolder = recycler.findViewHolderForAdapterPosition(target); - if (visibleHolder != null) { - visibleHolder.itemView.requestFocus(); + return focusTmdbRecyclerItem(recycler, target, 0); + } + + private boolean focusTmdbRecyclerItem(RecyclerView recycler, int target, int attempt) { + if (binding == null || recycler == null || recycler.getVisibility() != View.VISIBLE) return false; + RecyclerView.Adapter adapter = recycler.getAdapter(); + if (adapter == null || adapter.getItemCount() == 0) return false; + int boundedTarget = Math.max(0, Math.min(target, adapter.getItemCount() - 1)); + if (recycler.isComputingLayout()) { + if (attempt >= 6) return true; + recycler.postOnAnimation(() -> focusTmdbRecyclerItem(recycler, boundedTarget, attempt + 1)); return true; } - recycler.scrollToPosition(target); - recycler.post(() -> { - RecyclerView.ViewHolder holder = recycler.findViewHolderForAdapterPosition(target); - if (holder != null) holder.itemView.requestFocus(); - }); + recycler.stopScroll(); + RecyclerView.ViewHolder visibleHolder = recycler.findViewHolderForAdapterPosition(boundedTarget); + if (visibleHolder != null) { + boolean requested = visibleHolder.itemView.requestFocus(); + if (!requested) requested = visibleHolder.itemView.requestFocusFromTouch(); + if (requested && getCurrentFocus() == visibleHolder.itemView) return true; + if (attempt >= 6) return true; + recycler.postOnAnimation(() -> focusTmdbRecyclerItem(recycler, boundedTarget, attempt + 1)); + return true; + } + if (attempt == 0) recycler.scrollToPosition(boundedTarget); + if (attempt >= 6) return true; + recycler.postOnAnimation(() -> focusTmdbRecyclerItem(recycler, boundedTarget, attempt + 1)); return true; } diff --git a/app/src/testMobile/java/com/fongmi/android/tv/ui/activity/TmdbDetailActivityLayoutTest.java b/app/src/testMobile/java/com/fongmi/android/tv/ui/activity/TmdbDetailActivityLayoutTest.java index f32d2abe39..261986b0cd 100644 --- a/app/src/testMobile/java/com/fongmi/android/tv/ui/activity/TmdbDetailActivityLayoutTest.java +++ b/app/src/testMobile/java/com/fongmi/android/tv/ui/activity/TmdbDetailActivityLayoutTest.java @@ -1164,8 +1164,18 @@ public class TmdbDetailActivityLayoutTest { assertTrue("dismiss must not steal focus from another button or an already closed activity", dismiss.contains("!returnRecycler.isAttachedToWindow()") && dismiss.contains("isFinishing() || isDestroyed()") - && dismiss.contains("!returnRecycler.isShown()") - && dismiss.contains("focus != previousFocus && !isFocusInside(focus, returnRecycler)")); + && dismiss.contains("!returnRecycler.isShown()")); + int recyclerFocusStart = source.indexOf("private boolean focusTmdbRecyclerItem(RecyclerView recycler, int position)"); + int recyclerFocusEnd = source.indexOf("private boolean onDetailEpisodeContainerKey", recyclerFocusStart); + String recyclerFocus = recyclerFocusStart >= 0 && recyclerFocusEnd > recyclerFocusStart + ? source.substring(recyclerFocusStart, recyclerFocusEnd) : ""; + assertTrue("restoring an episode card must retry until its ViewHolder is attached and verify requestFocus succeeded", + recyclerFocus.contains("recycler.isComputingLayout()") + && recyclerFocus.contains("findViewHolderForAdapterPosition(boundedTarget)") + && recyclerFocus.contains("visibleHolder.itemView.requestFocus()") + && recyclerFocus.contains("requestFocusFromTouch()") + && recyclerFocus.contains("getCurrentFocus() == visibleHolder.itemView") + && recyclerFocus.contains("postOnAnimation(() -> focusTmdbRecyclerItem(recycler, boundedTarget, attempt + 1))")); } @Test @@ -2289,6 +2299,7 @@ public class TmdbDetailActivityLayoutTest { String navigationBody = navigation >= 0 && detailRows > navigation ? activity.substring(navigation, detailRows) : ""; String detailRowsBody = detailRows >= 0 && rowKey > detailRows ? activity.substring(detailRows, rowKey) : ""; String rowKeyBody = rowKey >= 0 && episodeKey > rowKey ? activity.substring(rowKey, episodeKey) : ""; + String focusBody = focusItem >= 0 && episodeKey > focusItem ? activity.substring(focusItem, episodeKey) : ""; assertTrue(activityPath + " is missing TMDB horizontal row key helpers", navigation >= 0 && detailRows > navigation && rowKey > detailRows && focusItem > rowKey && episodeKey > focusItem); @@ -2309,10 +2320,10 @@ public class TmdbDetailActivityLayoutTest { && rowKeyBody.contains("int target = KeyUtil.isLeftKey(event) ? position - 1 : position + 1;") && rowKeyBody.contains("if (target < 0 || target >= adapter.getItemCount()) return true;") && rowKeyBody.contains("focusTmdbRecyclerItem(recycler, target);") - && rowKeyBody.contains("RecyclerView.ViewHolder visibleHolder = recycler.findViewHolderForAdapterPosition(target);") - && rowKeyBody.contains("visibleHolder.itemView.requestFocus();") - && rowKeyBody.contains("recycler.scrollToPosition(target);") - && rowKeyBody.contains("holder.itemView.requestFocus();")); + && focusBody.contains("RecyclerView.ViewHolder visibleHolder = recycler.findViewHolderForAdapterPosition(boundedTarget);") + && focusBody.contains("visibleHolder.itemView.requestFocus()") + && focusBody.contains("recycler.scrollToPosition(boundedTarget);") + && focusBody.contains("postOnAnimation(() -> focusTmdbRecyclerItem(recycler, boundedTarget, attempt + 1)")); } @Test