Subclasses:
VideoSupportFragment
Gradle dependencies
compile group: 'androidx.leanback', name: 'leanback', version: '1.2.0-alpha04'
- groupId: androidx.leanback
- artifactId: leanback
- version: 1.2.0-alpha04
Artifact androidx.leanback:leanback:1.2.0-alpha04 it located at Google repository (https://maven.google.com/)
Androidx artifact mapping:
androidx.leanback:leanback com.android.support:leanback-v17
Androidx class mapping:
androidx.leanback.app.PlaybackSupportFragment android.support.v17.leanback.app.PlaybackSupportFragment
Overview
A fragment for displaying playback controls and related content.
A PlaybackSupportFragment renders the elements of its ObjectAdapter as a set
of rows in a vertical list. The Adapter's PresenterSelector must maintain subclasses
of RowPresenter.
A playback row is a row rendered by PlaybackRowPresenter.
App can call PlaybackSupportFragment.setPlaybackRow(Row) to set playback row for the first element of adapter.
App can call PlaybackSupportFragment.setPlaybackRowPresenter(PlaybackRowPresenter) to set presenter for it.
PlaybackSupportFragment.setPlaybackRow(Row) and PlaybackSupportFragment.setPlaybackRowPresenter(PlaybackRowPresenter) are
optional, app can pass playback row and PlaybackRowPresenter in the adapter using
PlaybackSupportFragment.setAdapter(ObjectAdapter).
Hiding and showing controls: the controls are initially visible and automatically show/hide
when play/pause or user interacts with fragment.
Summary
Methods |
---|
public void | fadeOut()
Fades out the playback overlay immediately. |
public ObjectAdapter | getAdapter()
|
public int | getBackgroundType()
Returns the background type. |
public PlaybackSupportFragment.OnFadeCompleteListener | getFadeCompleteListener()
Returns the listener to be called when fade in or out has completed. |
public ProgressBarManager | getProgressBarManager()
Returns the ProgressBarManager that will show or hide progress bar in
PlaybackSupportFragment.onBufferingStateChanged(boolean). |
public void | hideControlsOverlay(boolean runAnimation)
Hide controls overlay. |
public boolean | isControlsOverlayAutoHideEnabled()
Returns true if controls will be auto hidden after a delay when fragment is resumed. |
public boolean | isControlsOverlayVisible()
Returns true if controls overlay is visible, false otherwise. |
public boolean | isFadingEnabled()
|
public boolean | isShowOrHideControlsOverlayOnUserInteraction()
Returns true if showing and auto-hiding controls when user interacts; false otherwise. |
public void | notifyPlaybackRowChanged()
Updates the ui when the row data changes. |
protected void | onBufferingStateChanged(boolean start)
Called when media has start or stop buffering. |
public void | onCreate(Bundle savedInstanceState)
Called to do initial creation of a fragment. |
public View | onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
Called to have the fragment instantiate its user interface view. |
public void | onDestroy()
Called when the fragment is no longer in use. |
public void | onDestroyView()
Called when the view previously created by Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle) has
been detached from the fragment. |
protected void | onError(int errorCode, java.lang.CharSequence errorMessage)
Called when media has error. |
public void | onPause()
Called when the Fragment is no longer resumed. |
public void | onResume()
Called when the fragment is visible to the user and actively running. |
public void | onStart()
Called when the Fragment is visible to the user. |
public void | onStop()
Called when the Fragment is no longer started. |
protected void | onVideoSizeChanged(int videoWidth, int videoHeight)
Called when size of the video changes. |
public void | onViewCreated(View view, Bundle savedInstanceState)
Called immediately after Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle)
has returned, but before any saved state has been restored in to the view. |
public void | resetFocus()
Resets the focus on the button in the middle of control row. |
public void | setAdapter(ObjectAdapter adapter)
Sets the list of rows for the fragment. |
public void | setBackgroundType(int type)
Sets the background type. |
public void | setControlsOverlayAutoHideEnabled(boolean enabled)
Enables or disables auto hiding controls overlay after a short delay fragment is resumed. |
public void | setFadeCompleteListener(PlaybackSupportFragment.OnFadeCompleteListener listener)
Sets the listener to be called when fade in or out has completed. |
public void | setFadingEnabled(boolean enabled)
|
public void | setHostCallback(PlaybackGlueHost.HostCallback hostCallback)
Sets the . |
public void | setOnItemViewClickedListener(BaseOnItemViewClickedListener listener)
This listener is called every time there is a click in RowsSupportFragment. |
public void | setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener)
This listener is called every time there is a selection in RowsSupportFragment. |
public final void | setOnKeyInterceptListener(View.OnKeyListener handler)
Sets the input event handler. |
public void | setOnPlaybackItemViewClickedListener(BaseOnItemViewClickedListener listener)
Sets the BaseOnItemViewClickedListener that would be invoked for clicks
only on PlaybackRowPresenter.ViewHolder. |
public void | setPlaybackRow(Row row)
Sets the playback row for the playback controls. |
public void | setPlaybackRowPresenter(PlaybackRowPresenter presenter)
Sets the presenter for rendering the playback row set by PlaybackSupportFragment.setPlaybackRow(Row). |
public void | setPlaybackSeekUiClient(PlaybackSeekUi.Client client)
Interface to be implemented by UI widget to support PlaybackSeekUi. |
public void | setSelectedPosition(int position)
Sets the selected row position with smooth animation. |
public void | setSelectedPosition(int position, boolean smooth)
Sets the selected row position. |
public void | setShowOrHideControlsOverlayOnUserInteraction(boolean showOrHideControlsOverlayOnUserInteraction)
Enables or disables showing and auto-hiding controls when user interacts. |
public void | showControlsOverlay(boolean runAnimation)
Show controls overlay. |
public void | tickle()
Tickles the playback controls. |
from Fragment | dump, equals, getActivity, getAllowEnterTransitionOverlap, getAllowReturnTransitionOverlap, getArguments, getChildFragmentManager, getContext, getDefaultViewModelCreationExtras, getDefaultViewModelProviderFactory, getEnterTransition, getExitTransition, getFragmentManager, getHost, getId, getLayoutInflater, getLayoutInflater, getLifecycle, getLoaderManager, getParentFragment, getParentFragmentManager, getReenterTransition, getResources, getRetainInstance, getReturnTransition, getSavedStateRegistry, getSharedElementEnterTransition, getSharedElementReturnTransition, getString, getString, getTag, getTargetFragment, getTargetRequestCode, getText, getUserVisibleHint, getView, getViewLifecycleOwner, getViewLifecycleOwnerLiveData, getViewModelStore, hashCode, hasOptionsMenu, instantiate, instantiate, isAdded, isDetached, isHidden, isInLayout, isMenuVisible, isRemoving, isResumed, isStateSaved, isVisible, onActivityCreated, onActivityResult, onAttach, onAttach, onAttachFragment, onConfigurationChanged, onContextItemSelected, onCreateAnimation, onCreateAnimator, onCreateContextMenu, onCreateOptionsMenu, onDestroyOptionsMenu, onDetach, onGetLayoutInflater, onHiddenChanged, onInflate, onInflate, onLowMemory, onMultiWindowModeChanged, onOptionsItemSelected, onOptionsMenuClosed, onPictureInPictureModeChanged, onPrepareOptionsMenu, onPrimaryNavigationFragmentChanged, onRequestPermissionsResult, onSaveInstanceState, onViewStateRestored, postponeEnterTransition, postponeEnterTransition, registerForActivityResult, registerForActivityResult, registerForContextMenu, requestPermissions, requireActivity, requireArguments, requireContext, requireFragmentManager, requireHost, requireParentFragment, requireView, setAllowEnterTransitionOverlap, setAllowReturnTransitionOverlap, setArguments, setEnterSharedElementCallback, setEnterTransition, setExitSharedElementCallback, setExitTransition, setHasOptionsMenu, setInitialSavedState, setMenuVisibility, setReenterTransition, setRetainInstance, setReturnTransition, setSharedElementEnterTransition, setSharedElementReturnTransition, setTargetFragment, setUserVisibleHint, shouldShowRequestPermissionRationale, startActivity, startActivity, startActivityForResult, startActivityForResult, startIntentSenderForResult, startPostponedEnterTransition, toString, unregisterForContextMenu |
from java.lang.Object | clone, finalize, getClass, notify, notifyAll, wait, wait, wait |
Fields
public static final int
BG_NONENo background.
public static final int
BG_DARKA dark translucent background.
public static final int
BG_LIGHTA light translucent background.
Constructors
public
PlaybackSupportFragment()
Methods
Resets the focus on the button in the middle of control row.
public void
setShowOrHideControlsOverlayOnUserInteraction(boolean showOrHideControlsOverlayOnUserInteraction)
Enables or disables showing and auto-hiding controls when user interacts. Enabled by default.
Auto-hide timer length is defined by .
public boolean
isShowOrHideControlsOverlayOnUserInteraction()
Returns true if showing and auto-hiding controls when user interacts; false otherwise.
public void
setControlsOverlayAutoHideEnabled(boolean enabled)
Enables or disables auto hiding controls overlay after a short delay fragment is resumed.
If enabled and fragment is resumed, the view will fade out after a time period.
User interaction will kill the timer, next time fragment is resumed,
the timer will be started again if PlaybackSupportFragment.isControlsOverlayAutoHideEnabled() is true.
In most cases app should not directly call setControlsOverlayAutoHideEnabled() as it's
called by PlaybackBaseControlGlue on play or pause.
public boolean
isControlsOverlayAutoHideEnabled()
Returns true if controls will be auto hidden after a delay when fragment is resumed.
public void
setFadingEnabled(boolean enabled)
Deprecated: Uses PlaybackSupportFragment.setControlsOverlayAutoHideEnabled(boolean)
public boolean
isFadingEnabled()
Deprecated: Uses PlaybackSupportFragment.isControlsOverlayAutoHideEnabled()
Sets the listener to be called when fade in or out has completed.
Returns the listener to be called when fade in or out has completed.
public final void
setOnKeyInterceptListener(View.OnKeyListener handler)
Sets the input event handler.
Tickles the playback controls. Fades in the view if it was faded out. PlaybackSupportFragment.tickle() will
kill and re-create a timer if is
positive.
In most cases app does not need call tickle() as it's automatically called on user
interactions.
public void
onViewCreated(View view, Bundle savedInstanceState)
Called immediately after Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle)
has returned, but before any saved state has been restored in to the view.
This gives subclasses a chance to initialize themselves once
they know their view hierarchy has been completely created. The fragment's
view hierarchy is not however attached to its parent at this point.
Parameters:
view: The View returned by Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle).
savedInstanceState: If non-null, this fragment is being re-constructed
from a previous saved state as given here.
Called when the fragment is visible to the user and actively running.
This is generally
tied to of the containing
Activity's lifecycle.
Deprecated: Call PlaybackSupportFragment.hideControlsOverlay(boolean)
Fades out the playback overlay immediately.
public void
showControlsOverlay(boolean runAnimation)
Show controls overlay.
Parameters:
runAnimation: True to run animation, false otherwise.
public boolean
isControlsOverlayVisible()
Returns true if controls overlay is visible, false otherwise.
Returns:
True if controls overlay is visible, false otherwise.
See also: PlaybackSupportFragment.showControlsOverlay(boolean), PlaybackSupportFragment.hideControlsOverlay(boolean)
public void
hideControlsOverlay(boolean runAnimation)
Hide controls overlay.
Parameters:
runAnimation: True to run animation, false otherwise.
public void
setSelectedPosition(int position)
Sets the selected row position with smooth animation.
public void
setSelectedPosition(int position, boolean smooth)
Sets the selected row position.
public void
onCreate(Bundle savedInstanceState)
Called to do initial creation of a fragment. This is called after
Fragment.onAttach(Activity) and before
Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle).
Note that this can be called while the fragment's activity is
still in the process of being created. As such, you can not rely
on things like the activity's content view hierarchy being initialized
at this point. If you want to do work once the activity itself is
created, add a LifecycleObserver on the
activity's Lifecycle, removing it when it receives the
callback.
Any restored child fragments will be created before the base
Fragment.onCreate
method returns.
Parameters:
savedInstanceState: If the fragment is being re-created from
a previous saved state, this is the state.
public void
setBackgroundType(int type)
Sets the background type.
Parameters:
type: One of BG_LIGHT, BG_DARK, or BG_NONE.
public int
getBackgroundType()
Returns the background type.
public View
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
Called to have the fragment instantiate its user interface view.
This is optional, and non-graphical fragments can return null. This will be called between
Fragment.onCreate(Bundle) and Fragment.onViewCreated(View, Bundle).
A default View can be returned by calling Fragment.Fragment(int) in your
constructor. Otherwise, this method returns null.
It is recommended to only inflate the layout in this method and move
logic that operates on the returned View to Fragment.onViewCreated(View, Bundle).
If you return a View from here, you will later be called in
Fragment.onDestroyView() when the view is being released.
Parameters:
inflater: The LayoutInflater object that can be used to inflate
any views in the fragment,
container: If non-null, this is the parent view that the fragment's
UI should be attached to. The fragment should not add the view itself,
but this can be used to generate the LayoutParams of the view.
savedInstanceState: If non-null, this fragment is being re-constructed
from a previous saved state as given here.
Returns:
Return the View for the fragment's UI, or null.
Sets the . Implementor of this interface will
take appropriate actions to take action when the hosting fragment starts/stops processing.
Called when the Fragment is visible to the user. This is generally
tied to of the containing
Activity's lifecycle.
Called when the Fragment is no longer started. This is generally
tied to of the containing
Activity's lifecycle.
Called when the Fragment is no longer resumed. This is generally
tied to of the containing
Activity's lifecycle.
This listener is called every time there is a selection in RowsSupportFragment. This can
be used by users to take additional actions such as animations.
This listener is called every time there is a click in RowsSupportFragment. This can
be used by users to take additional actions such as animations.
Sets the BaseOnItemViewClickedListener that would be invoked for clicks
only on PlaybackRowPresenter.ViewHolder.
public void
onDestroyView()
Called when the view previously created by Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle) has
been detached from the fragment. The next time the fragment needs
to be displayed, a new view will be created. This is called
after Fragment.onStop() and before Fragment.onDestroy(). It is called
regardless of whether Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle) returned a
non-null view. Internally it is called after the view's state has
been saved but before it has been removed from its parent.
Called when the fragment is no longer in use. This is called
after Fragment.onStop() and before Fragment.onDetach().
public void
setPlaybackRow(
Row row)
Sets the playback row for the playback controls. The row will be set as first element
of adapter if the adapter is ArrayObjectAdapter or SparseArrayObjectAdapter.
Parameters:
row: The row that represents the playback.
Sets the presenter for rendering the playback row set by PlaybackSupportFragment.setPlaybackRow(Row). If
adapter does not set a PresenterSelector, PlaybackSupportFragment.setAdapter(ObjectAdapter) will
create a ClassPresenterSelector by default and map from the row object class to this
PlaybackRowPresenter.
Parameters:
presenter: Presenter used to render PlaybackSupportFragment.setPlaybackRow(Row).
public void
notifyPlaybackRowChanged()
Updates the ui when the row data changes.
Sets the list of rows for the fragment. A default ClassPresenterSelector will be
created if ObjectAdapter.getPresenterSelector() is null. if user provides
PlaybackSupportFragment.setPlaybackRow(Row) and PlaybackSupportFragment.setPlaybackRowPresenter(PlaybackRowPresenter),
the row and presenter will be set onto the adapter.
Parameters:
adapter: The adapter that contains related rows and optional playback row.
Interface to be implemented by UI widget to support PlaybackSeekUi.
protected void
onVideoSizeChanged(int videoWidth, int videoHeight)
Called when size of the video changes. App may override.
Parameters:
videoWidth: Intrinsic width of video
videoHeight: Intrinsic height of video
protected void
onBufferingStateChanged(boolean start)
Called when media has start or stop buffering. App may override. The default initial state
is not buffering.
Parameters:
start: True for buffering start, false otherwise.
protected void
onError(int errorCode, java.lang.CharSequence errorMessage)
Called when media has error. App may override.
Parameters:
errorCode: Optional error code for specific implementation.
errorMessage: Optional error message for specific implementation.
Returns the ProgressBarManager that will show or hide progress bar in
PlaybackSupportFragment.onBufferingStateChanged(boolean).
Returns:
The ProgressBarManager that will show or hide progress bar in
PlaybackSupportFragment.onBufferingStateChanged(boolean).
Source
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package androidx.leanback.app;
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.util.TypedValue;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.fragment.app.Fragment;
import androidx.leanback.R;
import androidx.leanback.animation.LogAccelerateInterpolator;
import androidx.leanback.animation.LogDecelerateInterpolator;
import androidx.leanback.media.PlaybackGlueHost;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.BaseOnItemViewClickedListener;
import androidx.leanback.widget.BaseOnItemViewSelectedListener;
import androidx.leanback.widget.ClassPresenterSelector;
import androidx.leanback.widget.ItemAlignmentFacet;
import androidx.leanback.widget.ItemBridgeAdapter;
import androidx.leanback.widget.ObjectAdapter;
import androidx.leanback.widget.PlaybackRowPresenter;
import androidx.leanback.widget.PlaybackSeekDataProvider;
import androidx.leanback.widget.PlaybackSeekUi;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.PresenterSelector;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;
import androidx.leanback.widget.SparseArrayObjectAdapter;
import androidx.leanback.widget.VerticalGridView;
import androidx.recyclerview.widget.RecyclerView;
/**
* A fragment for displaying playback controls and related content.
*
* <p>
* A PlaybackSupportFragment renders the elements of its {@link ObjectAdapter} as a set
* of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses
* of {@link RowPresenter}.
* </p>
* <p>
* A playback row is a row rendered by {@link PlaybackRowPresenter}.
* App can call {@link #setPlaybackRow(Row)} to set playback row for the first element of adapter.
* App can call {@link #setPlaybackRowPresenter(PlaybackRowPresenter)} to set presenter for it.
* {@link #setPlaybackRow(Row)} and {@link #setPlaybackRowPresenter(PlaybackRowPresenter)} are
* optional, app can pass playback row and PlaybackRowPresenter in the adapter using
* {@link #setAdapter(ObjectAdapter)}.
* </p>
* <p>
* Hiding and showing controls: the controls are initially visible and automatically show/hide
* when play/pause or user interacts with fragment.
* <ul>
* <li>
* App may manually call {@link #showControlsOverlay(boolean)} or
* {@link #hideControlsOverlay(boolean)} to show or hide the controls.
* </li>
* <li>
* The controls are visible by default upon onViewCreated(). To make it initially invisible,
* call hideControlsOverlay(false) in overridden onViewCreated().
* </li>
* <li>
* Upon play or pause, PlaybackControlGlue or PlaybackTransportControlGlue will fade-in
* the controls and automatically fade out after a delay customized by
* {@link R.attr#playbackControlsAutoHideTimeout}. To disable the fade in and fade out
* behavior: call {@link androidx.leanback.media.PlaybackBaseControlGlue
* #setControlsOverlayAutoHideEnabled(boolean)} with false.
* </li>
* <li>
* Upon user interaction event, fragment will fade-in the controls and automatically fade
* out after a delay customized by {@link R.attr#playbackControlsAutoHideTickleTimeout}.
* To disable the fade in and fade out behavior, call {@link
* #setShowOrHideControlsOverlayOnUserInteraction} with false.
* </li>
* </ul>
*/
public class PlaybackSupportFragment extends Fragment {
static final String BUNDLE_CONTROL_VISIBLE_ON_CREATEVIEW = "controlvisible_oncreateview";
/**
* No background.
*/
public static final int BG_NONE = 0;
/**
* A dark translucent background.
*/
public static final int BG_DARK = 1;
PlaybackGlueHost.HostCallback mHostCallback;
PlaybackSeekUi.Client mSeekUiClient;
boolean mInSeek;
ProgressBarManager mProgressBarManager = new ProgressBarManager();
/**
* Resets the focus on the button in the middle of control row.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public void resetFocus() {
ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView()
.findViewHolderForAdapterPosition(0);
if (vh != null && vh.getPresenter() instanceof PlaybackRowPresenter) {
((PlaybackRowPresenter) vh.getPresenter()).onReappear(
(RowPresenter.ViewHolder) vh.getViewHolder());
}
}
private class SetSelectionRunnable implements Runnable {
int mPosition;
boolean mSmooth = true;
SetSelectionRunnable() {
}
@Override
public void run() {
if (mRowsSupportFragment == null) {
return;
}
mRowsSupportFragment.setSelectedPosition(mPosition, mSmooth);
}
}
/**
* A light translucent background.
*/
public static final int BG_LIGHT = 2;
RowsSupportFragment mRowsSupportFragment;
ObjectAdapter mAdapter;
PlaybackRowPresenter mPresenter;
Row mRow;
BaseOnItemViewSelectedListener mExternalItemSelectedListener;
BaseOnItemViewClickedListener mExternalItemClickedListener;
BaseOnItemViewClickedListener mPlaybackItemClickedListener;
private final BaseOnItemViewClickedListener mOnItemViewClickedListener =
new BaseOnItemViewClickedListener() {
@Override
@SuppressWarnings("unchecked")
public void onItemClicked(Presenter.ViewHolder itemViewHolder,
Object item,
RowPresenter.ViewHolder rowViewHolder,
Object row) {
if (mPlaybackItemClickedListener != null
&& rowViewHolder instanceof PlaybackRowPresenter.ViewHolder) {
mPlaybackItemClickedListener.onItemClicked(
itemViewHolder, item, rowViewHolder, row);
}
if (mExternalItemClickedListener != null) {
mExternalItemClickedListener.onItemClicked(
itemViewHolder, item, rowViewHolder, row);
}
}
};
private final BaseOnItemViewSelectedListener mOnItemViewSelectedListener =
new BaseOnItemViewSelectedListener() {
@Override
@SuppressWarnings("unchecked")
public void onItemSelected(Presenter.ViewHolder itemViewHolder,
Object item,
RowPresenter.ViewHolder rowViewHolder,
Object row) {
if (mExternalItemSelectedListener != null) {
mExternalItemSelectedListener.onItemSelected(
itemViewHolder, item, rowViewHolder, row);
}
}
};
private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
public ObjectAdapter getAdapter() {
return mAdapter;
}
/**
* Listener allowing the application to receive notification of fade in and/or fade out
* completion events.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static class OnFadeCompleteListener {
public void onFadeInComplete() {
}
public void onFadeOutComplete() {
}
}
private static final String TAG = "PlaybackSupportFragment";
private static final boolean DEBUG = false;
private static final int ANIMATION_MULTIPLIER = 1;
private static final int START_FADE_OUT = 1;
int mPaddingBottom;
int mOtherRowsCenterToBottom;
View mRootView;
View mBackgroundView;
int mBackgroundType = BG_DARK;
int mBgDarkColor;
int mBgLightColor;
int mAutohideTimerAfterPlayingInMs;
int mAutohideTimerAfterTickleInMs;
int mMajorFadeTranslateY, mMinorFadeTranslateY;
int mAnimationTranslateY;
OnFadeCompleteListener mFadeCompleteListener;
View.OnKeyListener mInputEventHandler;
boolean mFadingEnabled = true;
boolean mControlVisibleBeforeOnCreateView = true;
boolean mControlVisible = true;
boolean mShowOrHideControlsOverlayOnUserInteraction = true;
int mBgAlpha;
ValueAnimator mBgFadeInAnimator, mBgFadeOutAnimator;
ValueAnimator mControlRowFadeInAnimator, mControlRowFadeOutAnimator;
ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
private final Animator.AnimatorListener mFadeListener =
new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
enableVerticalGridAnimations(false);
}
@Override
public void onAnimationRepeat(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
if (DEBUG) Log.v(TAG, "onAnimationEnd " + mBgAlpha);
if (mBgAlpha > 0) {
enableVerticalGridAnimations(true);
if (mFadeCompleteListener != null) {
mFadeCompleteListener.onFadeInComplete();
}
} else {
VerticalGridView verticalView = getVerticalGridView();
// reset focus to the primary actions only if the selected row was the controls row
if (verticalView != null && verticalView.getSelectedPosition() == 0) {
ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
verticalView.findViewHolderForAdapterPosition(0);
if (vh != null && vh.getPresenter() instanceof PlaybackRowPresenter) {
((PlaybackRowPresenter)vh.getPresenter()).onReappear(
(RowPresenter.ViewHolder) vh.getViewHolder());
}
}
if (mFadeCompleteListener != null) {
mFadeCompleteListener.onFadeOutComplete();
}
}
}
};
public PlaybackSupportFragment() {
mProgressBarManager.setInitialDelay(500);
}
VerticalGridView getVerticalGridView() {
if (mRowsSupportFragment == null) {
return null;
}
return mRowsSupportFragment.getVerticalGridView();
}
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message message) {
if (message.what == START_FADE_OUT && mFadingEnabled) {
hideControlsOverlay(true);
}
}
};
private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
new VerticalGridView.OnTouchInterceptListener() {
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return onInterceptInputEvent(event);
}
};
private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
new VerticalGridView.OnKeyInterceptListener() {
@Override
public boolean onInterceptKeyEvent(KeyEvent event) {
return onInterceptInputEvent(event);
}
};
@SuppressWarnings("WeakerAccess") /* synthetic access */
void setBgAlpha(int alpha) {
mBgAlpha = alpha;
if (mBackgroundView != null) {
mBackgroundView.getBackground().setAlpha(alpha);
}
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
void enableVerticalGridAnimations(boolean enable) {
if (getVerticalGridView() != null) {
getVerticalGridView().setAnimateChildLayout(enable);
}
}
/**
* Enables or disables showing and auto-hiding controls when user interacts. Enabled by default.
* Auto-hide timer length is defined by {@link R.attr#playbackControlsAutoHideTickleTimeout}.
*/
public void setShowOrHideControlsOverlayOnUserInteraction(boolean
showOrHideControlsOverlayOnUserInteraction) {
mShowOrHideControlsOverlayOnUserInteraction = showOrHideControlsOverlayOnUserInteraction;
}
/**
* Returns true if showing and auto-hiding controls when user interacts; false otherwise.
*/
public boolean isShowOrHideControlsOverlayOnUserInteraction() {
return mShowOrHideControlsOverlayOnUserInteraction;
}
/**
* Enables or disables auto hiding controls overlay after a short delay fragment is resumed.
* If enabled and fragment is resumed, the view will fade out after a time period.
* User interaction will kill the timer, next time fragment is resumed,
* the timer will be started again if {@link #isControlsOverlayAutoHideEnabled()} is true.
* <p>
* In most cases app should not directly call setControlsOverlayAutoHideEnabled() as it's
* called by {@link androidx.leanback.media.PlaybackBaseControlGlue} on play or pause.
*/
public void setControlsOverlayAutoHideEnabled(boolean enabled) {
if (DEBUG) Log.v(TAG, "setControlsOverlayAutoHideEnabled " + enabled);
if (enabled != mFadingEnabled) {
mFadingEnabled = enabled;
if (isResumed() && getView().hasFocus()) {
showControlsOverlay(true);
if (enabled) {
// StateGraph 7->2 5->2
startFadeTimer(mAutohideTimerAfterPlayingInMs);
} else {
// StateGraph 4->5 2->5
stopFadeTimer();
}
} else {
// StateGraph 6->1 1->6
}
}
}
/**
* Returns true if controls will be auto hidden after a delay when fragment is resumed.
*/
public boolean isControlsOverlayAutoHideEnabled() {
return mFadingEnabled;
}
/**
* @deprecated Uses {@link #setControlsOverlayAutoHideEnabled(boolean)}
*/
@Deprecated
public void setFadingEnabled(boolean enabled) {
setControlsOverlayAutoHideEnabled(enabled);
}
/**
* @deprecated Uses {@link #isControlsOverlayAutoHideEnabled()}
*/
@Deprecated
public boolean isFadingEnabled() {
return isControlsOverlayAutoHideEnabled();
}
/**
* Sets the listener to be called when fade in or out has completed.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public void setFadeCompleteListener(OnFadeCompleteListener listener) {
mFadeCompleteListener = listener;
}
/**
* Returns the listener to be called when fade in or out has completed.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public OnFadeCompleteListener getFadeCompleteListener() {
return mFadeCompleteListener;
}
/**
* Sets the input event handler.
*/
public final void setOnKeyInterceptListener(View.OnKeyListener handler) {
mInputEventHandler = handler;
}
/**
* Tickles the playback controls. Fades in the view if it was faded out. {@link #tickle()} will
* kill and re-create a timer if {@link R.attr#playbackControlsAutoHideTickleTimeout} is
* positive.
* <p>
* In most cases app does not need call tickle() as it's automatically called on user
* interactions.
*/
public void tickle() {
if (DEBUG) Log.v(TAG, "tickle enabled " + mFadingEnabled + " isResumed " + isResumed());
//StateGraph 2->4
stopFadeTimer();
showControlsOverlay(true);
// Optionally start fading out timer if it's currently playing (mFadingEnabled is true)
if (mAutohideTimerAfterTickleInMs > 0 && mFadingEnabled) {
startFadeTimer(mAutohideTimerAfterTickleInMs);
}
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
boolean onInterceptInputEvent(InputEvent event) {
final boolean controlsHidden = !mControlVisible;
if (DEBUG) Log.v(TAG, "onInterceptInputEvent hidden " + controlsHidden + " " + event);
boolean consumeEvent = false;
int keyCode = KeyEvent.KEYCODE_UNKNOWN;
int keyAction = 0;
if (event instanceof KeyEvent) {
keyCode = ((KeyEvent) event).getKeyCode();
keyAction = ((KeyEvent) event).getAction();
if (mInputEventHandler != null) {
consumeEvent = mInputEventHandler.onKey(getView(), keyCode, (KeyEvent) event);
}
}
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_DPAD_DOWN:
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_RIGHT:
// Event may be consumed; regardless, if controls are hidden then these keys will
// bring up the controls.
if (controlsHidden) {
consumeEvent = true;
}
if (mShowOrHideControlsOverlayOnUserInteraction
&& keyAction == KeyEvent.ACTION_DOWN) {
tickle();
}
break;
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_ESCAPE:
if (mInSeek) {
// when in seek, the SeekUi will handle the BACK.
return false;
}
// If controls are not hidden, back will be consumed to fade
// them out (even if the key was consumed by the handler).
if (mShowOrHideControlsOverlayOnUserInteraction && !controlsHidden) {
consumeEvent = true;
if (((KeyEvent) event).getAction() == KeyEvent.ACTION_UP) {
hideControlsOverlay(true);
}
}
break;
default:
if (mShowOrHideControlsOverlayOnUserInteraction && consumeEvent) {
if (keyAction == KeyEvent.ACTION_DOWN) {
tickle();
}
}
}
return consumeEvent;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// controls view are initially visible, make it invisible
// if app has called hideControlsOverlay() before view created.
mControlVisible = true;
if (!mControlVisibleBeforeOnCreateView) {
showControlsOverlay(false, false);
mControlVisibleBeforeOnCreateView = true;
}
}
@Override
public void onResume() {
super.onResume();
if (mControlVisible) {
//StateGraph: 6->5 1->2
if (mFadingEnabled) {
// StateGraph 1->2
startFadeTimer(mAutohideTimerAfterPlayingInMs);
}
} else {
//StateGraph: 6->7 1->3
}
getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
if (mHostCallback != null) {
mHostCallback.onHostResume();
}
}
private void stopFadeTimer() {
if (mHandler != null) {
mHandler.removeMessages(START_FADE_OUT);
}
}
private void startFadeTimer(int fadeOutTimeout) {
if (mHandler != null) {
mHandler.removeMessages(START_FADE_OUT);
mHandler.sendEmptyMessageDelayed(START_FADE_OUT, fadeOutTimeout);
}
}
private static ValueAnimator loadAnimator(Context context, int resId) {
ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context, resId);
animator.setDuration(animator.getDuration() * ANIMATION_MULTIPLIER);
return animator;
}
private void loadBgAnimator() {
AnimatorUpdateListener listener = new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator arg0) {
setBgAlpha((Integer) arg0.getAnimatedValue());
}
};
Context context = getContext();
mBgFadeInAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_in);
mBgFadeInAnimator.addUpdateListener(listener);
mBgFadeInAnimator.addListener(mFadeListener);
mBgFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_out);
mBgFadeOutAnimator.addUpdateListener(listener);
mBgFadeOutAnimator.addListener(mFadeListener);
}
private TimeInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100, 0);
private TimeInterpolator mLogAccelerateInterpolator = new LogAccelerateInterpolator(100, 0);
private void loadControlRowAnimator() {
final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator arg0) {
if (getVerticalGridView() == null) {
return;
}
RecyclerView.ViewHolder vh = getVerticalGridView()
.findViewHolderForAdapterPosition(0);
if (vh == null) {
return;
}
View view = vh.itemView;
if (view != null) {
final float fraction = (Float) arg0.getAnimatedValue();
if (DEBUG) Log.v(TAG, "fraction " + fraction);
view.setAlpha(fraction);
view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
}
}
};
Context context = getContext();
mControlRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
mControlRowFadeInAnimator.addUpdateListener(updateListener);
mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
mControlRowFadeOutAnimator = loadAnimator(context,
R.animator.lb_playback_controls_fade_out);
mControlRowFadeOutAnimator.addUpdateListener(updateListener);
mControlRowFadeOutAnimator.setInterpolator(mLogAccelerateInterpolator);
}
private void loadOtherRowAnimator() {
final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator arg0) {
if (getVerticalGridView() == null) {
return;
}
final float fraction = (Float) arg0.getAnimatedValue();
final int count = getVerticalGridView().getChildCount();
for (int i = 0; i < count; i++) {
View view = getVerticalGridView().getChildAt(i);
if (getVerticalGridView().getChildAdapterPosition(view) > 0) {
view.setAlpha(fraction);
view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
}
}
}
};
Context context = getContext();
mOtherRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
mOtherRowFadeInAnimator.addUpdateListener(updateListener);
mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
mOtherRowFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_out);
mOtherRowFadeOutAnimator.addUpdateListener(updateListener);
mOtherRowFadeOutAnimator.setInterpolator(new AccelerateInterpolator());
}
/**
* Fades out the playback overlay immediately.
* @deprecated Call {@link #hideControlsOverlay(boolean)}
*/
@Deprecated
public void fadeOut() {
showControlsOverlay(false, false);
}
/**
* Show controls overlay.
*
* @param runAnimation True to run animation, false otherwise.
*/
public void showControlsOverlay(boolean runAnimation) {
showControlsOverlay(true, runAnimation);
}
/**
* Returns true if controls overlay is visible, false otherwise.
*
* @return True if controls overlay is visible, false otherwise.
* @see #showControlsOverlay(boolean)
* @see #hideControlsOverlay(boolean)
*/
public boolean isControlsOverlayVisible() {
return mControlVisible;
}
/**
* Hide controls overlay.
*
* @param runAnimation True to run animation, false otherwise.
*/
public void hideControlsOverlay(boolean runAnimation) {
showControlsOverlay(false, runAnimation);
}
/**
* if first animator is still running, reverse it; otherwise start second animator.
*/
static void reverseFirstOrStartSecond(ValueAnimator first, ValueAnimator second,
boolean runAnimation) {
if (first.isStarted()) {
first.reverse();
if (!runAnimation) {
first.end();
}
} else {
second.start();
if (!runAnimation) {
second.end();
}
}
}
/**
* End first or second animator if they are still running.
*/
static void endAll(ValueAnimator first, ValueAnimator second) {
if (first.isStarted()) {
first.end();
} else if (second.isStarted()) {
second.end();
}
}
/**
* Fade in or fade out rows and background.
*
* @param show True to fade in, false to fade out.
* @param animation True to run animation.
*/
void showControlsOverlay(boolean show, boolean animation) {
if (DEBUG) Log.v(TAG, "showControlsOverlay " + show);
if (getView() == null) {
mControlVisibleBeforeOnCreateView = show;
return;
}
// force no animation when fragment is not resumed
if (!isResumed()) {
animation = false;
}
if (show == mControlVisible) {
if (!animation) {
// End animation if needed
endAll(mBgFadeInAnimator, mBgFadeOutAnimator);
endAll(mControlRowFadeInAnimator, mControlRowFadeOutAnimator);
endAll(mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator);
}
return;
}
// StateGraph: 7<->5 4<->3 2->3
mControlVisible = show;
if (!mControlVisible) {
// StateGraph 2->3
stopFadeTimer();
}
mAnimationTranslateY = (getVerticalGridView() == null
|| getVerticalGridView().getSelectedPosition() == 0)
? mMajorFadeTranslateY : mMinorFadeTranslateY;
if (show) {
reverseFirstOrStartSecond(mBgFadeOutAnimator, mBgFadeInAnimator, animation);
reverseFirstOrStartSecond(mControlRowFadeOutAnimator, mControlRowFadeInAnimator,
animation);
reverseFirstOrStartSecond(mOtherRowFadeOutAnimator, mOtherRowFadeInAnimator, animation);
} else {
reverseFirstOrStartSecond(mBgFadeInAnimator, mBgFadeOutAnimator, animation);
reverseFirstOrStartSecond(mControlRowFadeInAnimator, mControlRowFadeOutAnimator,
animation);
reverseFirstOrStartSecond(mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator, animation);
}
if (animation) {
getView().announceForAccessibility(getString(show
? R.string.lb_playback_controls_shown
: R.string.lb_playback_controls_hidden));
}
}
/**
* Sets the selected row position with smooth animation.
*/
public void setSelectedPosition(int position) {
setSelectedPosition(position, true);
}
/**
* Sets the selected row position.
*/
public void setSelectedPosition(int position, boolean smooth) {
mSetSelectionRunnable.mPosition = position;
mSetSelectionRunnable.mSmooth = smooth;
if (getView() != null && getView().getHandler() != null) {
getView().getHandler().post(mSetSelectionRunnable);
}
}
private void setupChildFragmentLayout() {
setVerticalGridViewLayout(mRowsSupportFragment.getVerticalGridView());
}
void setVerticalGridViewLayout(VerticalGridView listview) {
if (listview == null) {
return;
}
// we set the base line of alignment to -paddingBottom
listview.setWindowAlignmentOffset(-mPaddingBottom);
listview.setWindowAlignmentOffsetPercent(
VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
// align other rows that arent the last to center of screen, since our baseline is
// -mPaddingBottom, we need subtract that from mOtherRowsCenterToBottom.
listview.setItemAlignmentOffset(mOtherRowsCenterToBottom - mPaddingBottom);
listview.setItemAlignmentOffsetPercent(50);
// Push last row to the bottom padding
// Padding affects alignment when last row is focused
listview.setPadding(listview.getPaddingLeft(), listview.getPaddingTop(),
listview.getPaddingRight(), mPaddingBottom);
listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mOtherRowsCenterToBottom = getResources()
.getDimensionPixelSize(R.dimen.lb_playback_other_rows_center_to_bottom);
mPaddingBottom =
getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
mBgDarkColor =
getResources().getColor(R.color.lb_playback_controls_background_dark);
mBgLightColor =
getResources().getColor(R.color.lb_playback_controls_background_light);
TypedValue outValue = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.playbackControlsAutoHideTimeout, outValue, true);
mAutohideTimerAfterPlayingInMs = outValue.data;
getContext().getTheme().resolveAttribute(
R.attr.playbackControlsAutoHideTickleTimeout, outValue, true);
mAutohideTimerAfterTickleInMs = outValue.data;
mMajorFadeTranslateY =
getResources().getDimensionPixelSize(R.dimen.lb_playback_major_fade_translate_y);
mMinorFadeTranslateY =
getResources().getDimensionPixelSize(R.dimen.lb_playback_minor_fade_translate_y);
loadBgAnimator();
loadControlRowAnimator();
loadOtherRowAnimator();
}
/**
* Sets the background type.
*
* @param type One of BG_LIGHT, BG_DARK, or BG_NONE.
*/
public void setBackgroundType(int type) {
switch (type) {
case BG_LIGHT:
case BG_DARK:
case BG_NONE:
if (type != mBackgroundType) {
mBackgroundType = type;
updateBackground();
}
break;
default:
throw new IllegalArgumentException("Invalid background type");
}
}
/**
* Returns the background type.
*/
public int getBackgroundType() {
return mBackgroundType;
}
private void updateBackground() {
if (mBackgroundView != null) {
int color = mBgDarkColor;
switch (mBackgroundType) {
case BG_DARK:
break;
case BG_LIGHT:
color = mBgLightColor;
break;
case BG_NONE:
color = Color.TRANSPARENT;
break;
}
mBackgroundView.setBackground(new ColorDrawable(color));
setBgAlpha(mBgAlpha);
}
}
private final ItemBridgeAdapter.AdapterListener mAdapterListener =
new ItemBridgeAdapter.AdapterListener() {
@Override
public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
if (DEBUG) Log.v(TAG, "onAttachedToWindow " + vh.getViewHolder().view);
if (!mControlVisible) {
if (DEBUG) Log.v(TAG, "setting alpha to 0");
vh.getViewHolder().view.setAlpha(0);
}
}
@Override
public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
Presenter.ViewHolder viewHolder = vh.getViewHolder();
if (viewHolder instanceof PlaybackSeekUi) {
((PlaybackSeekUi) viewHolder).setPlaybackSeekUiClient(mChainedClient);
}
}
@Override
public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
if (DEBUG) Log.v(TAG, "onDetachedFromWindow " + vh.getViewHolder().view);
// Reset animation state
vh.getViewHolder().view.setAlpha(1f);
vh.getViewHolder().view.setTranslationY(0);
vh.getViewHolder().view.setAlpha(1f);
}
@Override
public void onBind(ItemBridgeAdapter.ViewHolder vh) {
}
};
@Override
@Nullable
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
mRootView = inflater.inflate(R.layout.lb_playback_fragment, container, false);
mBackgroundView = mRootView.findViewById(R.id.playback_fragment_background);
mRowsSupportFragment = (RowsSupportFragment) getChildFragmentManager().findFragmentById(
R.id.playback_controls_dock);
if (mRowsSupportFragment == null) {
mRowsSupportFragment = new RowsSupportFragment();
getChildFragmentManager().beginTransaction()
.replace(R.id.playback_controls_dock, mRowsSupportFragment)
.commit();
}
if (mAdapter == null) {
setAdapter(new ArrayObjectAdapter(new ClassPresenterSelector()));
} else {
mRowsSupportFragment.setAdapter(mAdapter);
}
mRowsSupportFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
mBgAlpha = 255;
updateBackground();
mRowsSupportFragment.setExternalAdapterListener(mAdapterListener);
ProgressBarManager progressBarManager = getProgressBarManager();
if (progressBarManager != null) {
progressBarManager.setRootView((ViewGroup) mRootView);
}
return mRootView;
}
/**
* Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
* take appropriate actions to take action when the hosting fragment starts/stops processing.
*/
public void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
this.mHostCallback = hostCallback;
}
@Override
public void onStart() {
super.onStart();
setupChildFragmentLayout();
mRowsSupportFragment.setAdapter(mAdapter);
if (mHostCallback != null) {
mHostCallback.onHostStart();
}
}
@Override
public void onStop() {
if (mHostCallback != null) {
mHostCallback.onHostStop();
}
super.onStop();
}
@Override
public void onPause() {
if (mHostCallback != null) {
mHostCallback.onHostPause();
}
if (mHandler.hasMessages(START_FADE_OUT)) {
// StateGraph: 2->1
mHandler.removeMessages(START_FADE_OUT);
} else {
// StateGraph: 5->6, 7->6, 4->1, 3->1
}
super.onPause();
}
/**
* This listener is called every time there is a selection in {@link RowsSupportFragment}. This can
* be used by users to take additional actions such as animations.
*/
public void setOnItemViewSelectedListener(final BaseOnItemViewSelectedListener listener) {
mExternalItemSelectedListener = listener;
}
/**
* This listener is called every time there is a click in {@link RowsSupportFragment}. This can
* be used by users to take additional actions such as animations.
*/
public void setOnItemViewClickedListener(final BaseOnItemViewClickedListener listener) {
mExternalItemClickedListener = listener;
}
/**
* Sets the {@link BaseOnItemViewClickedListener} that would be invoked for clicks
* only on {@link androidx.leanback.widget.PlaybackRowPresenter.ViewHolder}.
*/
public void setOnPlaybackItemViewClickedListener(final BaseOnItemViewClickedListener listener) {
mPlaybackItemClickedListener = listener;
}
@Override
public void onDestroyView() {
mRootView = null;
mBackgroundView = null;
super.onDestroyView();
}
@Override
public void onDestroy() {
if (mHostCallback != null) {
mHostCallback.onHostDestroy();
}
super.onDestroy();
}
/**
* Sets the playback row for the playback controls. The row will be set as first element
* of adapter if the adapter is {@link ArrayObjectAdapter} or {@link SparseArrayObjectAdapter}.
* @param row The row that represents the playback.
*/
public void setPlaybackRow(Row row) {
this.mRow = row;
setupRow();
setupPresenter();
}
/**
* Sets the presenter for rendering the playback row set by {@link #setPlaybackRow(Row)}. If
* adapter does not set a {@link PresenterSelector}, {@link #setAdapter(ObjectAdapter)} will
* create a {@link ClassPresenterSelector} by default and map from the row object class to this
* {@link PlaybackRowPresenter}.
*
* @param presenter Presenter used to render {@link #setPlaybackRow(Row)}.
*/
public void setPlaybackRowPresenter(PlaybackRowPresenter presenter) {
this.mPresenter = presenter;
setupPresenter();
setPlaybackRowPresenterAlignment();
}
void setPlaybackRowPresenterAlignment() {
if (mAdapter != null && mAdapter.getPresenterSelector() != null) {
Presenter[] presenters = mAdapter.getPresenterSelector().getPresenters();
if (presenters != null) {
for (int i = 0; i < presenters.length; i++) {
if (presenters[i] instanceof PlaybackRowPresenter
&& presenters[i].getFacet(ItemAlignmentFacet.class) == null) {
ItemAlignmentFacet itemAlignment = new ItemAlignmentFacet();
ItemAlignmentFacet.ItemAlignmentDef def =
new ItemAlignmentFacet.ItemAlignmentDef();
def.setItemAlignmentOffset(0);
def.setItemAlignmentOffsetPercent(100);
itemAlignment.setAlignmentDefs(new ItemAlignmentFacet.ItemAlignmentDef[]
{def});
presenters[i].setFacet(ItemAlignmentFacet.class, itemAlignment);
}
}
}
}
}
/**
* Updates the ui when the row data changes.
*/
public void notifyPlaybackRowChanged() {
if (mAdapter == null) {
return;
}
mAdapter.notifyItemRangeChanged(0, 1);
}
/**
* Sets the list of rows for the fragment. A default {@link ClassPresenterSelector} will be
* created if {@link ObjectAdapter#getPresenterSelector()} is null. if user provides
* {@link #setPlaybackRow(Row)} and {@link #setPlaybackRowPresenter(PlaybackRowPresenter)},
* the row and presenter will be set onto the adapter.
*
* @param adapter The adapter that contains related rows and optional playback row.
*/
public void setAdapter(ObjectAdapter adapter) {
mAdapter = adapter;
setupRow();
setupPresenter();
setPlaybackRowPresenterAlignment();
if (mRowsSupportFragment != null) {
mRowsSupportFragment.setAdapter(adapter);
}
}
private void setupRow() {
if (mAdapter instanceof ArrayObjectAdapter && mRow != null) {
ArrayObjectAdapter adapter = ((ArrayObjectAdapter) mAdapter);
if (adapter.size() == 0) {
adapter.add(mRow);
} else {
adapter.replace(0, mRow);
}
} else if (mAdapter instanceof SparseArrayObjectAdapter && mRow != null) {
SparseArrayObjectAdapter adapter = ((SparseArrayObjectAdapter) mAdapter);
adapter.set(0, mRow);
}
}
private void setupPresenter() {
if (mAdapter != null && mRow != null && mPresenter != null) {
PresenterSelector selector = mAdapter.getPresenterSelector();
if (selector == null) {
selector = new ClassPresenterSelector();
((ClassPresenterSelector) selector).addClassPresenter(mRow.getClass(), mPresenter);
mAdapter.setPresenterSelector(selector);
} else if (selector instanceof ClassPresenterSelector) {
((ClassPresenterSelector) selector).addClassPresenter(mRow.getClass(), mPresenter);
}
}
}
final PlaybackSeekUi.Client mChainedClient = new PlaybackSeekUi.Client() {
@Override
public boolean isSeekEnabled() {
return mSeekUiClient == null ? false : mSeekUiClient.isSeekEnabled();
}
@Override
public void onSeekStarted() {
if (mSeekUiClient != null) {
mSeekUiClient.onSeekStarted();
}
setSeekMode(true);
}
@Override
public PlaybackSeekDataProvider getPlaybackSeekDataProvider() {
return mSeekUiClient == null ? null : mSeekUiClient.getPlaybackSeekDataProvider();
}
@Override
public void onSeekPositionChanged(long pos) {
if (mSeekUiClient != null) {
mSeekUiClient.onSeekPositionChanged(pos);
}
}
@Override
public void onSeekFinished(boolean cancelled) {
if (mSeekUiClient != null) {
mSeekUiClient.onSeekFinished(cancelled);
}
setSeekMode(false);
}
};
/**
* Interface to be implemented by UI widget to support PlaybackSeekUi.
*/
public void setPlaybackSeekUiClient(PlaybackSeekUi.Client client) {
mSeekUiClient = client;
}
/**
* Show or hide other rows other than PlaybackRow.
* @param inSeek True to make other rows visible, false to make other rows invisible.
*/
void setSeekMode(boolean inSeek) {
if (mInSeek == inSeek) {
return;
}
mInSeek = inSeek;
getVerticalGridView().setSelectedPosition(0);
if (mInSeek) {
stopFadeTimer();
}
// immediately fade in control row.
showControlsOverlay(true);
final int count = getVerticalGridView().getChildCount();
for (int i = 0; i < count; i++) {
View view = getVerticalGridView().getChildAt(i);
if (getVerticalGridView().getChildAdapterPosition(view) > 0) {
view.setVisibility(mInSeek ? View.INVISIBLE : View.VISIBLE);
}
}
}
/**
* Called when size of the video changes. App may override.
* @param videoWidth Intrinsic width of video
* @param videoHeight Intrinsic height of video
*/
protected void onVideoSizeChanged(int videoWidth, int videoHeight) {
}
/**
* Called when media has start or stop buffering. App may override. The default initial state
* is not buffering.
* @param start True for buffering start, false otherwise.
*/
protected void onBufferingStateChanged(boolean start) {
ProgressBarManager progressBarManager = getProgressBarManager();
if (progressBarManager != null) {
if (start) {
progressBarManager.show();
} else {
progressBarManager.hide();
}
}
}
/**
* Called when media has error. App may override.
* @param errorCode Optional error code for specific implementation.
* @param errorMessage Optional error message for specific implementation.
*/
protected void onError(int errorCode, CharSequence errorMessage) {
}
/**
* Returns the ProgressBarManager that will show or hide progress bar in
* {@link #onBufferingStateChanged(boolean)}.
* @return The ProgressBarManager that will show or hide progress bar in
* {@link #onBufferingStateChanged(boolean)}.
*/
public ProgressBarManager getProgressBarManager() {
return mProgressBarManager;
}
}