Android TV: ability to select all buttons in the main player, as well as in the main fragment

This commit is contained in:
Avently 2020-07-25 04:14:29 +03:00
parent 7c79d421e8
commit 08db1d59e5
6 changed files with 216 additions and 168 deletions

View File

@ -34,6 +34,7 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
@ -2154,6 +2155,30 @@ public class VideoDetailFragment
// Bottom mini player // Bottom mini player
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
/**
* That's for Android TV support. Move focus from main fragment to the player or back
* based on what is currently selected
* @param toMain if true than the main fragment will be focused or the player otherwise
* */
private void moveFocusToMainFragment(final boolean toMain) {
final ViewGroup mainFragment = requireActivity().findViewById(R.id.fragment_holder);
// Hamburger button steels a focus even under bottomSheet
final Toolbar toolbar = requireActivity().findViewById(R.id.toolbar);
final int afterDescendants = ViewGroup.FOCUS_AFTER_DESCENDANTS;
final int blockDescendants = ViewGroup.FOCUS_BLOCK_DESCENDANTS;
if (toMain) {
mainFragment.setDescendantFocusability(afterDescendants);
toolbar.setDescendantFocusability(afterDescendants);
((ViewGroup) requireView()).setDescendantFocusability(blockDescendants);
mainFragment.requestFocus();
} else {
mainFragment.setDescendantFocusability(blockDescendants);
toolbar.setDescendantFocusability(blockDescendants);
((ViewGroup) requireView()).setDescendantFocusability(afterDescendants);
thumbnailBackgroundButton.requestFocus();
}
}
private void setupBottomPlayer() { private void setupBottomPlayer() {
final CoordinatorLayout.LayoutParams params = final CoordinatorLayout.LayoutParams params =
(CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams(); (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
@ -2177,15 +2202,17 @@ public class VideoDetailFragment
@Override @Override
public void onStateChanged(@NonNull final View bottomSheet, final int newState) { public void onStateChanged(@NonNull final View bottomSheet, final int newState) {
bottomSheetState = newState; bottomSheetState = newState;
final ViewGroup mainFragment = requireActivity().findViewById(R.id.fragment_holder);
switch (newState) { switch (newState) {
case BottomSheetBehavior.STATE_HIDDEN: case BottomSheetBehavior.STATE_HIDDEN:
mainFragment.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); moveFocusToMainFragment(true);
bottomSheetBehavior.setPeekHeight(0); bottomSheetBehavior.setPeekHeight(0);
cleanUp(); cleanUp();
break; break;
case BottomSheetBehavior.STATE_EXPANDED: case BottomSheetBehavior.STATE_EXPANDED:
mainFragment.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); moveFocusToMainFragment(false);
bottomSheetBehavior.setPeekHeight(peekHeight); bottomSheetBehavior.setPeekHeight(peekHeight);
// Disable click because overlay buttons located on top of buttons // Disable click because overlay buttons located on top of buttons
// from the player // from the player
@ -2202,8 +2229,8 @@ public class VideoDetailFragment
} }
break; break;
case BottomSheetBehavior.STATE_COLLAPSED: case BottomSheetBehavior.STATE_COLLAPSED:
mainFragment.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); moveFocusToMainFragment(true);
mainFragment.requestFocus();
// Re-enable clicks // Re-enable clicks
setOverlayElementsClickable(true); setOverlayElementsClickable(true);
if (player != null) { if (player != null) {

View File

@ -480,7 +480,6 @@ public class VideoPlayerImpl extends VideoPlayer
case KeyEvent.KEYCODE_BACK: case KeyEvent.KEYCODE_BACK:
if (DeviceUtils.isTv(service) && isControlsVisible()) { if (DeviceUtils.isTv(service) && isControlsVisible()) {
hideControls(0, 0); hideControls(0, 0);
hideSystemUIIfNeeded();
return true; return true;
} }
break; break;
@ -499,7 +498,9 @@ public class VideoPlayerImpl extends VideoPlayer
} }
if (!isControlsVisible()) { if (!isControlsVisible()) {
playPauseButton.requestFocus(); if (!queueVisible) {
playPauseButton.requestFocus();
}
showControlsThenHide(); showControlsThenHide();
showSystemUIPartially(); showSystemUIPartially();
return true; return true;
@ -805,7 +806,7 @@ public class VideoPlayerImpl extends VideoPlayer
if (v.getId() == playPauseButton.getId()) { if (v.getId() == playPauseButton.getId()) {
hideControls(0, 0); hideControls(0, 0);
} else { } else {
safeHideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
} }
} }
}); });
@ -830,6 +831,7 @@ public class VideoPlayerImpl extends VideoPlayer
updatePlaybackButtons(); updatePlaybackButtons();
getControlsRoot().setVisibility(View.INVISIBLE); getControlsRoot().setVisibility(View.INVISIBLE);
queueLayout.requestFocus();
animateView(queueLayout, SLIDE_AND_ALPHA, true, animateView(queueLayout, SLIDE_AND_ALPHA, true,
DEFAULT_CONTROLS_DURATION); DEFAULT_CONTROLS_DURATION);
@ -848,6 +850,7 @@ public class VideoPlayerImpl extends VideoPlayer
queueLayout.setTranslationY(-queueLayout.getHeight() * 5); queueLayout.setTranslationY(-queueLayout.getHeight() * 5);
}); });
queueVisible = false; queueVisible = false;
playPauseButton.requestFocus();
} }
private void onMoreOptionsClicked() { private void onMoreOptionsClicked() {
@ -1095,7 +1098,9 @@ public class VideoPlayerImpl extends VideoPlayer
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> { animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp); playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
animatePlayButtons(true, 200); animatePlayButtons(true, 200);
playPauseButton.requestFocus(); if (!queueVisible) {
playPauseButton.requestFocus();
}
}); });
updateWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS); updateWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS);
@ -1114,7 +1119,9 @@ public class VideoPlayerImpl extends VideoPlayer
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> { animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp); playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
animatePlayButtons(true, 200); animatePlayButtons(true, 200);
playPauseButton.requestFocus(); if (!queueVisible) {
playPauseButton.requestFocus();
}
}); });
updateWindowFlags(IDLE_WINDOW_FLAGS); updateWindowFlags(IDLE_WINDOW_FLAGS);
@ -1401,12 +1408,10 @@ public class VideoPlayerImpl extends VideoPlayer
return isFullscreen; return isFullscreen;
} }
@Override
public void showControlsThenHide() { public void showControlsThenHide() {
if (queueVisible) { if (DEBUG) {
return; Log.d(TAG, "showControlsThenHide() called");
} }
showOrHideButtons(); showOrHideButtons();
showSystemUIPartially(); showSystemUIPartially();
super.showControlsThenHide(); super.showControlsThenHide();
@ -1414,10 +1419,9 @@ public class VideoPlayerImpl extends VideoPlayer
@Override @Override
public void showControls(final long duration) { public void showControls(final long duration) {
if (queueVisible) { if (DEBUG) {
return; Log.d(TAG, "showControls() called with: duration = [" + duration + "]");
} }
showOrHideButtons(); showOrHideButtons();
showSystemUIPartially(); showSystemUIPartially();
super.showControls(duration); super.showControls(duration);

View File

@ -38,6 +38,7 @@ import android.view.ViewTreeObserver;
import android.view.Window; import android.view.Window;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.appcompat.view.WindowCallbackWrapper; import androidx.appcompat.view.WindowCallbackWrapper;
@ -113,7 +114,9 @@ public final class FocusOverlayView extends Drawable implements
if (focusedView != null) { if (focusedView != null) {
focusedView.getGlobalVisibleRect(focusRect); focusedView.getGlobalVisibleRect(focusRect);
} else { }
if (shouldClearFocusRect(focusedView, focusRect)) {
focusRect.setEmpty(); focusRect.setEmpty();
} }
@ -184,6 +187,16 @@ public final class FocusOverlayView extends Drawable implements
public void setColorFilter(final ColorFilter colorFilter) { public void setColorFilter(final ColorFilter colorFilter) {
} }
/*
* When any view in the player looses it's focus (after setVisibility(GONE)) the focus gets
* added to the whole fragment which has a width and height equal to the window frame.
* The easiest way to avoid the unneeded frame is to skip highlighting of rect that is
* equal to the overlayView bounds
* */
private boolean shouldClearFocusRect(@Nullable final View focusedView, final Rect focusedRect) {
return focusedView == null || focusedRect.equals(getBounds());
}
public static void setupFocusObserver(final Dialog dialog) { public static void setupFocusObserver(final Dialog dialog) {
Rect displayRect = new Rect(); Rect displayRect = new Rect();

View File

@ -38,81 +38,6 @@
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
tools:visibility="visible"/> tools:visibility="visible"/>
<RelativeLayout
android:id="@+id/playQueuePanel"
android:layout_width="380dp"
android:layout_alignParentEnd="true"
android:layout_height="match_parent"
android:visibility="gone"
android:background="?attr/queue_background_color"
tools:visibility="visible">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:id="@+id/playQueueControl">
<ImageButton
android:id="@+id/playQueueClose"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_marginEnd="40dp"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/ic_close_white_24dp"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/repeatButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="40dp"
android:layout_marginStart="40dp"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/exo_controls_repeat_off"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/shuffleButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/repeatButton"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
app:srcCompat="@drawable/ic_shuffle_white_24dp"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/playQueue"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/playQueueControl"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/play_queue_item"/>
</RelativeLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/playbackControlRoot" android:id="@+id/playbackControlRoot"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -147,6 +72,7 @@
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:orientation="vertical" android:orientation="vertical"
android:gravity="top" android:gravity="top"
android:descendantFocusability="afterDescendants"
android:paddingTop="@dimen/player_main_top_padding" android:paddingTop="@dimen/player_main_top_padding"
android:paddingStart="@dimen/player_main_controls_padding" android:paddingStart="@dimen/player_main_controls_padding"
android:paddingEnd="@dimen/player_main_controls_padding" android:paddingEnd="@dimen/player_main_controls_padding"
@ -158,6 +84,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="45dp" android:minHeight="45dp"
android:baselineAligned="false" android:baselineAligned="false"
android:descendantFocusability="afterDescendants"
android:gravity="top" android:gravity="top"
tools:ignore="RtlHardcoded"> tools:ignore="RtlHardcoded">
@ -431,6 +358,7 @@
android:paddingBottom="4dp" android:paddingBottom="4dp"
android:paddingTop="8dp" android:paddingTop="8dp"
tools:progress="25" tools:progress="25"
android:nextFocusDown="@id/screenRotationButton"
tools:secondaryProgress="50"/> tools:secondaryProgress="50"/>
<TextView <TextView
@ -469,6 +397,7 @@
android:scaleType="fitCenter" android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_fullscreen_white_24dp" app:srcCompat="@drawable/ic_fullscreen_white_24dp"
tools:ignore="ContentDescription,RtlHardcoded" tools:ignore="ContentDescription,RtlHardcoded"
android:nextFocusUp="@id/playbackSeekBar"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible"/> tools:visibility="visible"/>
</LinearLayout> </LinearLayout>
@ -522,6 +451,80 @@
</RelativeLayout> </RelativeLayout>
<RelativeLayout
android:id="@+id/playQueuePanel"
android:layout_width="380dp"
android:layout_alignParentEnd="true"
android:layout_height="match_parent"
android:visibility="gone"
android:background="?attr/queue_background_color"
tools:visibility="visible">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:id="@+id/playQueueControl">
<ImageButton
android:id="@+id/playQueueClose"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_marginEnd="40dp"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/ic_close_white_24dp"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/repeatButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="40dp"
android:layout_marginStart="40dp"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/exo_controls_repeat_off"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/shuffleButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/repeatButton"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
app:srcCompat="@drawable/ic_shuffle_white_24dp"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/playQueue"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/playQueueControl"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/play_queue_item"/>
</RelativeLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -47,6 +47,7 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:foreground="?attr/selectableItemBackground" android:foreground="?attr/selectableItemBackground"
android:descendantFocusability="afterDescendants"
app:layout_collapseMode="parallax"> app:layout_collapseMode="parallax">
<ImageView <ImageView
@ -156,6 +157,7 @@
android:id="@+id/player_placeholder" android:id="@+id/player_placeholder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:descendantFocusability="afterDescendants"
/> />
</FrameLayout> </FrameLayout>
@ -602,6 +604,7 @@
android:alpha="0.9" android:alpha="0.9"
android:paddingLeft="@dimen/video_item_search_padding" android:paddingLeft="@dimen/video_item_search_padding"
android:paddingRight="@dimen/video_item_search_padding" android:paddingRight="@dimen/video_item_search_padding"
android:descendantFocusability="blocksDescendants"
android:background="?attr/windowBackground" > android:background="?attr/windowBackground" >
<ImageButton <ImageButton

View File

@ -38,81 +38,6 @@
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
tools:visibility="visible"/> tools:visibility="visible"/>
<RelativeLayout
android:id="@+id/playQueuePanel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:background="?attr/queue_background_color"
tools:visibility="visible">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:id="@+id/playQueueControl">
<ImageButton
android:id="@+id/playQueueClose"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_marginEnd="40dp"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/ic_close_white_24dp"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/repeatButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="40dp"
android:layout_marginStart="40dp"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/exo_controls_repeat_off"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/shuffleButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/repeatButton"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
app:srcCompat="@drawable/ic_shuffle_white_24dp"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/playQueue"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/playQueueControl"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/play_queue_item"/>
</RelativeLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/playbackControlRoot" android:id="@+id/playbackControlRoot"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -523,6 +448,79 @@
</RelativeLayout> </RelativeLayout>
<RelativeLayout
android:id="@+id/playQueuePanel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:background="?attr/queue_background_color"
tools:visibility="visible">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:id="@+id/playQueueControl">
<ImageButton
android:id="@+id/playQueueClose"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_marginEnd="40dp"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/ic_close_white_24dp"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/repeatButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="40dp"
android:layout_marginStart="40dp"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/exo_controls_repeat_off"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/shuffleButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/repeatButton"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
app:srcCompat="@drawable/ic_shuffle_white_24dp"
android:background="?android:selectableItemBackground"
tools:ignore="ContentDescription,RtlHardcoded"/>
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/playQueue"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/playQueueControl"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/play_queue_item"/>
</RelativeLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"