Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
      5  * in compliance with the License. You may obtain a copy of the License at
      6  *
      7  * http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the License
     10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     11  * or implied. See the License for the specific language governing permissions and limitations under
     12  * the License.
     13  */
     14 package androidx.leanback.widget;
     15 
     16 import android.content.Context;
     17 import android.graphics.Color;
     18 import android.util.TypedValue;
     19 import android.view.KeyEvent;
     20 import android.view.LayoutInflater;
     21 import android.view.View;
     22 import android.view.ViewGroup;
     23 import android.view.ViewGroup.LayoutParams;
     24 import android.view.ViewGroup.MarginLayoutParams;
     25 import android.widget.ImageView;
     26 import android.widget.LinearLayout;
     27 
     28 import androidx.annotation.ColorInt;
     29 import androidx.core.view.ViewCompat;
     30 import androidx.leanback.R;
     31 import androidx.leanback.widget.ControlBarPresenter.OnControlClickedListener;
     32 import androidx.leanback.widget.ControlBarPresenter.OnControlSelectedListener;
     33 
     34 /**
     35  * A PlaybackControlsRowPresenter renders a {@link PlaybackControlsRow} to display a
     36  * series of playback control buttons. Typically this row will be the first row in a fragment
     37  * such as the {@link androidx.leanback.app.PlaybackFragment}.
     38  *
     39  * <p>The detailed description is rendered using a {@link Presenter} passed in
     40  * {@link #PlaybackControlsRowPresenter(Presenter)}.  Typically this will be an instance of
     41  * {@link AbstractDetailsDescriptionPresenter}.  The application can access the
     42  * detailed description ViewHolder from {@link ViewHolder#mDescriptionViewHolder}.
     43  * </p>
     44  */
     45 public class PlaybackControlsRowPresenter extends PlaybackRowPresenter {
     46 
     47     static class BoundData extends PlaybackControlsPresenter.BoundData {
     48         ViewHolder mRowViewHolder;
     49     }
     50 
     51     /**
     52      * A ViewHolder for the PlaybackControlsRow.
     53      */
     54     public class ViewHolder extends PlaybackRowPresenter.ViewHolder {
     55         public final Presenter.ViewHolder mDescriptionViewHolder;
     56         final ViewGroup mCard;
     57         final ViewGroup mCardRightPanel;
     58         final ImageView mImageView;
     59         final ViewGroup mDescriptionDock;
     60         final ViewGroup mControlsDock;
     61         final ViewGroup mSecondaryControlsDock;
     62         final View mSpacer;
     63         final View mBottomSpacer;
     64         View mBgView;
     65         int mControlsDockMarginStart;
     66         int mControlsDockMarginEnd;
     67         PlaybackControlsPresenter.ViewHolder mControlsVh;
     68         Presenter.ViewHolder mSecondaryControlsVh;
     69         BoundData mControlsBoundData = new BoundData();
     70         BoundData mSecondaryBoundData = new BoundData();
     71         Presenter.ViewHolder mSelectedViewHolder;
     72         Object mSelectedItem;
     73         final PlaybackControlsRow.OnPlaybackProgressCallback mListener =
     74                 new PlaybackControlsRow.OnPlaybackProgressCallback() {
     75             @Override
     76             public void onCurrentPositionChanged(PlaybackControlsRow row, long ms) {
     77                 mPlaybackControlsPresenter.setCurrentTimeLong(mControlsVh, ms);
     78             }
     79 
     80             @Override
     81             public void onDurationChanged(PlaybackControlsRow row, long ms) {
     82                 mPlaybackControlsPresenter.setTotalTimeLong(mControlsVh, ms);
     83             }
     84 
     85             @Override
     86             public void onBufferedPositionChanged(PlaybackControlsRow row, long ms) {
     87                 mPlaybackControlsPresenter.setSecondaryProgressLong(mControlsVh, ms);
     88             }
     89         };
     90 
     91         ViewHolder(View rootView, Presenter descriptionPresenter) {
     92             super(rootView);
     93             mCard = (ViewGroup) rootView.findViewById(R.id.controls_card);
     94             mCardRightPanel = (ViewGroup) rootView.findViewById(R.id.controls_card_right_panel);
     95             mImageView = (ImageView) rootView.findViewById(R.id.image);
     96             mDescriptionDock = (ViewGroup) rootView.findViewById(R.id.description_dock);
     97             mControlsDock = (ViewGroup) rootView.findViewById(R.id.controls_dock);
     98             mSecondaryControlsDock =
     99                     (ViewGroup) rootView.findViewById(R.id.secondary_controls_dock);
    100             mSpacer = rootView.findViewById(R.id.spacer);
    101             mBottomSpacer = rootView.findViewById(R.id.bottom_spacer);
    102             mDescriptionViewHolder = descriptionPresenter == null ? null :
    103                     descriptionPresenter.onCreateViewHolder(mDescriptionDock);
    104             if (mDescriptionViewHolder != null) {
    105                 mDescriptionDock.addView(mDescriptionViewHolder.view);
    106             }
    107         }
    108 
    109         void dispatchItemSelection() {
    110             if (!isSelected()) {
    111                 return;
    112             }
    113             if (mSelectedViewHolder == null) {
    114                 if (getOnItemViewSelectedListener() != null) {
    115                     getOnItemViewSelectedListener().onItemSelected(null, null,
    116                             ViewHolder.this, getRow());
    117                 }
    118             } else {
    119                 if (getOnItemViewSelectedListener() != null) {
    120                     getOnItemViewSelectedListener().onItemSelected(mSelectedViewHolder, mSelectedItem,
    121                             ViewHolder.this, getRow());
    122                 }
    123             }
    124         };
    125 
    126         Presenter getPresenter(boolean primary) {
    127             ObjectAdapter adapter = primary
    128                     ? ((PlaybackControlsRow) getRow()).getPrimaryActionsAdapter()
    129                     : ((PlaybackControlsRow) getRow()).getSecondaryActionsAdapter();
    130             if (adapter == null) {
    131                 return null;
    132             }
    133             if (adapter.getPresenterSelector() instanceof ControlButtonPresenterSelector) {
    134                 ControlButtonPresenterSelector selector =
    135                         (ControlButtonPresenterSelector) adapter.getPresenterSelector();
    136                 return primary ? selector.getPrimaryPresenter()
    137                         : selector.getSecondaryPresenter();
    138             }
    139             return adapter.getPresenter(adapter.size() > 0 ? adapter.get(0) : null);
    140         }
    141 
    142         void setOutline(View view) {
    143             if (mBgView != null) {
    144                 RoundedRectHelper.setClipToRoundedOutline(mBgView, false);
    145                 ViewCompat.setZ(mBgView, 0f);
    146             }
    147             mBgView = view;
    148             RoundedRectHelper.setClipToRoundedOutline(view, true);
    149 
    150             if (sShadowZ == 0) {
    151                 sShadowZ = view.getResources().getDimensionPixelSize(
    152                         R.dimen.lb_playback_controls_z);
    153             }
    154             ViewCompat.setZ(view, sShadowZ);
    155         }
    156     }
    157 
    158     private int mBackgroundColor = Color.TRANSPARENT;
    159     private boolean mBackgroundColorSet;
    160     private int mProgressColor = Color.TRANSPARENT;
    161     private boolean mProgressColorSet;
    162     private boolean mSecondaryActionsHidden;
    163     private Presenter mDescriptionPresenter;
    164     PlaybackControlsPresenter mPlaybackControlsPresenter;
    165     private ControlBarPresenter mSecondaryControlsPresenter;
    166     OnActionClickedListener mOnActionClickedListener;
    167     static float sShadowZ;
    168 
    169     private final OnControlSelectedListener mOnControlSelectedListener =
    170             new OnControlSelectedListener() {
    171         @Override
    172         public void onControlSelected(Presenter.ViewHolder itemViewHolder, Object item,
    173                 ControlBarPresenter.BoundData data) {
    174             ViewHolder vh = ((BoundData) data).mRowViewHolder;
    175             if (vh.mSelectedViewHolder != itemViewHolder || vh.mSelectedItem != item) {
    176                 vh.mSelectedViewHolder = itemViewHolder;
    177                 vh.mSelectedItem = item;
    178                 vh.dispatchItemSelection();
    179             }
    180         }
    181     };
    182 
    183     private final OnControlClickedListener mOnControlClickedListener =
    184             new OnControlClickedListener() {
    185         @Override
    186         public void onControlClicked(Presenter.ViewHolder itemViewHolder, Object item,
    187                 ControlBarPresenter.BoundData data) {
    188             ViewHolder vh = ((BoundData) data).mRowViewHolder;
    189             if (vh.getOnItemViewClickedListener() != null) {
    190                 vh.getOnItemViewClickedListener().onItemClicked(itemViewHolder, item,
    191                         vh, vh.getRow());
    192             }
    193             if (mOnActionClickedListener != null && item instanceof Action) {
    194                 mOnActionClickedListener.onActionClicked((Action) item);
    195             }
    196         }
    197     };
    198 
    199     /**
    200      * Constructor for a PlaybackControlsRowPresenter.
    201      *
    202      * @param descriptionPresenter Presenter for displaying item details.
    203      */
    204     public PlaybackControlsRowPresenter(Presenter descriptionPresenter) {
    205         setHeaderPresenter(null);
    206         setSelectEffectEnabled(false);
    207 
    208         mDescriptionPresenter = descriptionPresenter;
    209         mPlaybackControlsPresenter = new PlaybackControlsPresenter(R.layout.lb_playback_controls);
    210         mSecondaryControlsPresenter = new ControlBarPresenter(R.layout.lb_control_bar);
    211 
    212         mPlaybackControlsPresenter.setOnControlSelectedListener(mOnControlSelectedListener);
    213         mSecondaryControlsPresenter.setOnControlSelectedListener(mOnControlSelectedListener);
    214         mPlaybackControlsPresenter.setOnControlClickedListener(mOnControlClickedListener);
    215         mSecondaryControlsPresenter.setOnControlClickedListener(mOnControlClickedListener);
    216     }
    217 
    218     /**
    219      * Constructor for a PlaybackControlsRowPresenter.
    220      */
    221     public PlaybackControlsRowPresenter() {
    222         this(null);
    223     }
    224 
    225     /**
    226      * Sets the listener for {@link Action} click events.
    227      */
    228     public void setOnActionClickedListener(OnActionClickedListener listener) {
    229         mOnActionClickedListener = listener;
    230     }
    231 
    232     /**
    233      * Returns the listener for {@link Action} click events.
    234      */
    235     public OnActionClickedListener getOnActionClickedListener() {
    236         return mOnActionClickedListener;
    237     }
    238 
    239     /**
    240      * Sets the background color.  If not set, a default from the theme will be used.
    241      */
    242     public void setBackgroundColor(@ColorInt int color) {
    243         mBackgroundColor = color;
    244         mBackgroundColorSet = true;
    245     }
    246 
    247     /**
    248      * Returns the background color.  If no background color was set, transparent
    249      * is returned.
    250      */
    251     @ColorInt
    252     public int getBackgroundColor() {
    253         return mBackgroundColor;
    254     }
    255 
    256     /**
    257      * Sets the primary color for the progress bar.  If not set, a default from
    258      * the theme will be used.
    259      */
    260     public void setProgressColor(@ColorInt int color) {
    261         mProgressColor = color;
    262         mProgressColorSet = true;
    263     }
    264 
    265     /**
    266      * Returns the primary color for the progress bar.  If no color was set, transparent
    267      * is returned.
    268      */
    269     @ColorInt
    270     public int getProgressColor() {
    271         return mProgressColor;
    272     }
    273 
    274     /**
    275      * Sets the secondary actions to be hidden behind a "more actions" button.
    276      * When "more actions" is selected, the primary actions are replaced with
    277      * the secondary actions.
    278      */
    279     public void setSecondaryActionsHidden(boolean hidden) {
    280         mSecondaryActionsHidden = hidden;
    281     }
    282 
    283     /**
    284      * Returns true if secondary actions are hidden.
    285      */
    286     public boolean areSecondaryActionsHidden() {
    287         return mSecondaryActionsHidden;
    288     }
    289 
    290     /**
    291      * Shows or hides space at the bottom of the playback controls row.
    292      * This allows the row to hug the bottom of the display when no
    293      * other rows are present.
    294      */
    295     public void showBottomSpace(ViewHolder vh, boolean show) {
    296         vh.mBottomSpacer.setVisibility(show ? View.VISIBLE : View.GONE);
    297     }
    298 
    299     /**
    300      * Displays the primary actions.  This will override the user having selected "more actions"
    301      * to display the secondary actions; see {@link #setSecondaryActionsHidden(boolean)}.
    302      */
    303     public void showPrimaryActions(ViewHolder vh) {
    304         mPlaybackControlsPresenter.showPrimaryActions(vh.mControlsVh);
    305         if (vh.view.hasFocus()) {
    306             mPlaybackControlsPresenter.resetFocus(vh.mControlsVh);
    307         }
    308     }
    309 
    310     @Override
    311     public void onReappear(RowPresenter.ViewHolder rowViewHolder) {
    312         showPrimaryActions((ViewHolder) rowViewHolder);
    313     }
    314 
    315     private int getDefaultBackgroundColor(Context context) {
    316         TypedValue outValue = new TypedValue();
    317         if (context.getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true)) {
    318             return context.getResources().getColor(outValue.resourceId);
    319         }
    320         return context.getResources().getColor(R.color.lb_default_brand_color);
    321     }
    322 
    323     private int getDefaultProgressColor(Context context) {
    324         TypedValue outValue = new TypedValue();
    325         if (context.getTheme()
    326                 .resolveAttribute(R.attr.playbackProgressPrimaryColor, outValue, true)) {
    327             return context.getResources().getColor(outValue.resourceId);
    328         }
    329         return context.getResources().getColor(R.color.lb_playback_progress_color_no_theme);
    330     }
    331 
    332     @Override
    333     protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
    334         View v = LayoutInflater.from(parent.getContext())
    335             .inflate(R.layout.lb_playback_controls_row, parent, false);
    336         ViewHolder vh = new ViewHolder(v, mDescriptionPresenter);
    337         initRow(vh);
    338         return vh;
    339     }
    340 
    341     private void initRow(final ViewHolder vh) {
    342         MarginLayoutParams lp = (MarginLayoutParams) vh.mControlsDock.getLayoutParams();
    343         vh.mControlsDockMarginStart = lp.getMarginStart();
    344         vh.mControlsDockMarginEnd = lp.getMarginEnd();
    345 
    346         vh.mControlsVh = (PlaybackControlsPresenter.ViewHolder)
    347                 mPlaybackControlsPresenter.onCreateViewHolder(vh.mControlsDock);
    348         mPlaybackControlsPresenter.setProgressColor(vh.mControlsVh, mProgressColorSet
    349                 ? mProgressColor : getDefaultProgressColor(vh.mControlsDock.getContext()));
    350         mPlaybackControlsPresenter.setBackgroundColor(vh.mControlsVh, mBackgroundColorSet
    351                 ? mBackgroundColor : getDefaultBackgroundColor(vh.view.getContext()));
    352         vh.mControlsDock.addView(vh.mControlsVh.view);
    353 
    354         vh.mSecondaryControlsVh =
    355                 mSecondaryControlsPresenter.onCreateViewHolder(vh.mSecondaryControlsDock);
    356         if (!mSecondaryActionsHidden) {
    357             vh.mSecondaryControlsDock.addView(vh.mSecondaryControlsVh.view);
    358         }
    359         ((PlaybackControlsRowView) vh.view).setOnUnhandledKeyListener(
    360                 new PlaybackControlsRowView.OnUnhandledKeyListener() {
    361             @Override
    362             public boolean onUnhandledKey(KeyEvent event) {
    363                 if (vh.getOnKeyListener() != null) {
    364                     if (vh.getOnKeyListener().onKey(vh.view, event.getKeyCode(), event)) {
    365                         return true;
    366                     }
    367                 }
    368                 return false;
    369             }
    370         });
    371     }
    372 
    373     @Override
    374     protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
    375         super.onBindRowViewHolder(holder, item);
    376 
    377         ViewHolder vh = (ViewHolder) holder;
    378         PlaybackControlsRow row = (PlaybackControlsRow) vh.getRow();
    379 
    380         mPlaybackControlsPresenter.enableSecondaryActions(mSecondaryActionsHidden);
    381 
    382         if (row.getItem() == null) {
    383             vh.mDescriptionDock.setVisibility(View.GONE);
    384             vh.mSpacer.setVisibility(View.GONE);
    385         } else {
    386             vh.mDescriptionDock.setVisibility(View.VISIBLE);
    387             if (vh.mDescriptionViewHolder != null) {
    388                 mDescriptionPresenter.onBindViewHolder(vh.mDescriptionViewHolder, row.getItem());
    389             }
    390             vh.mSpacer.setVisibility(View.VISIBLE);
    391         }
    392 
    393         if (row.getImageDrawable() == null || row.getItem() == null) {
    394             vh.mImageView.setImageDrawable(null);
    395             updateCardLayout(vh, LayoutParams.WRAP_CONTENT);
    396         } else {
    397             vh.mImageView.setImageDrawable(row.getImageDrawable());
    398             updateCardLayout(vh, vh.mImageView.getLayoutParams().height);
    399         }
    400 
    401         vh.mControlsBoundData.adapter = row.getPrimaryActionsAdapter();
    402         vh.mControlsBoundData.secondaryActionsAdapter = row.getSecondaryActionsAdapter();
    403         vh.mControlsBoundData.presenter = vh.getPresenter(true);
    404         vh.mControlsBoundData.mRowViewHolder = vh;
    405         mPlaybackControlsPresenter.onBindViewHolder(vh.mControlsVh, vh.mControlsBoundData);
    406 
    407         vh.mSecondaryBoundData.adapter = row.getSecondaryActionsAdapter();
    408         vh.mSecondaryBoundData.presenter = vh.getPresenter(false);
    409         vh.mSecondaryBoundData.mRowViewHolder = vh;
    410         mSecondaryControlsPresenter.onBindViewHolder(vh.mSecondaryControlsVh,
    411                 vh.mSecondaryBoundData);
    412 
    413         mPlaybackControlsPresenter.setTotalTime(vh.mControlsVh, row.getTotalTime());
    414         mPlaybackControlsPresenter.setCurrentTime(vh.mControlsVh, row.getCurrentTime());
    415         mPlaybackControlsPresenter.setSecondaryProgress(vh.mControlsVh, row.getBufferedProgress());
    416         row.setOnPlaybackProgressChangedListener(vh.mListener);
    417     }
    418 
    419     private void updateCardLayout(ViewHolder vh, int height) {
    420         LayoutParams lp = vh.mCardRightPanel.getLayoutParams();
    421         lp.height = height;
    422         vh.mCardRightPanel.setLayoutParams(lp);
    423 
    424         MarginLayoutParams mlp = (MarginLayoutParams) vh.mControlsDock.getLayoutParams();
    425         LinearLayout.LayoutParams llp =
    426                 (LinearLayout.LayoutParams) vh.mDescriptionDock.getLayoutParams();
    427 
    428         if (height == LayoutParams.WRAP_CONTENT) {
    429             llp.height = LayoutParams.WRAP_CONTENT;
    430             mlp.setMarginStart(0);
    431             mlp.setMarginEnd(0);
    432             vh.mCard.setBackground(null);
    433             vh.setOutline(vh.mControlsDock);
    434             mPlaybackControlsPresenter.enableTimeMargins(vh.mControlsVh, true);
    435         } else {
    436             llp.height = 0;
    437             llp.weight = 1;
    438             mlp.setMarginStart(vh.mControlsDockMarginStart);
    439             mlp.setMarginEnd(vh.mControlsDockMarginEnd);
    440             vh.mCard.setBackgroundColor(mBackgroundColorSet ? mBackgroundColor :
    441                     getDefaultBackgroundColor(vh.mCard.getContext()));
    442             vh.setOutline(vh.mCard);
    443             mPlaybackControlsPresenter.enableTimeMargins(vh.mControlsVh, false);
    444         }
    445         vh.mDescriptionDock.setLayoutParams(llp);
    446         vh.mControlsDock.setLayoutParams(mlp);
    447     }
    448 
    449     @Override
    450     protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
    451         ViewHolder vh = (ViewHolder) holder;
    452         PlaybackControlsRow row = (PlaybackControlsRow) vh.getRow();
    453 
    454         if (vh.mDescriptionViewHolder != null) {
    455             mDescriptionPresenter.onUnbindViewHolder(vh.mDescriptionViewHolder);
    456         }
    457         mPlaybackControlsPresenter.onUnbindViewHolder(vh.mControlsVh);
    458         mSecondaryControlsPresenter.onUnbindViewHolder(vh.mSecondaryControlsVh);
    459         row.setOnPlaybackProgressChangedListener(null);
    460 
    461         super.onUnbindRowViewHolder(holder);
    462     }
    463 
    464     @Override
    465     protected void onRowViewSelected(RowPresenter.ViewHolder vh, boolean selected) {
    466         super.onRowViewSelected(vh, selected);
    467         if (selected) {
    468             ((ViewHolder) vh).dispatchItemSelection();
    469         }
    470     }
    471 
    472     @Override
    473     protected void onRowViewAttachedToWindow(RowPresenter.ViewHolder vh) {
    474         super.onRowViewAttachedToWindow(vh);
    475         if (mDescriptionPresenter != null) {
    476             mDescriptionPresenter.onViewAttachedToWindow(
    477                     ((ViewHolder) vh).mDescriptionViewHolder);
    478         }
    479     }
    480 
    481     @Override
    482     protected void onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh) {
    483         super.onRowViewDetachedFromWindow(vh);
    484         if (mDescriptionPresenter != null) {
    485             mDescriptionPresenter.onViewDetachedFromWindow(
    486                     ((ViewHolder) vh).mDescriptionViewHolder);
    487         }
    488     }
    489 }
    490