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 ViewHolder(View rootView, Presenter descriptionPresenter)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 dispatchItemSelection()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 getPresenter(boolean primary)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 setOutline(View view)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 */ PlaybackControlsRowPresenter(Presenter descriptionPresenter)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 */ PlaybackControlsRowPresenter()221 public PlaybackControlsRowPresenter() { 222 this(null); 223 } 224 225 /** 226 * Sets the listener for {@link Action} click events. 227 */ setOnActionClickedListener(OnActionClickedListener listener)228 public void setOnActionClickedListener(OnActionClickedListener listener) { 229 mOnActionClickedListener = listener; 230 } 231 232 /** 233 * Returns the listener for {@link Action} click events. 234 */ getOnActionClickedListener()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 */ setBackgroundColor(@olorInt int color)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 getBackgroundColor()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 */ setProgressColor(@olorInt int color)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 getProgressColor()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 */ setSecondaryActionsHidden(boolean hidden)279 public void setSecondaryActionsHidden(boolean hidden) { 280 mSecondaryActionsHidden = hidden; 281 } 282 283 /** 284 * Returns true if secondary actions are hidden. 285 */ areSecondaryActionsHidden()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 */ showBottomSpace(ViewHolder vh, boolean show)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 */ showPrimaryActions(ViewHolder vh)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 onReappear(RowPresenter.ViewHolder rowViewHolder)311 public void onReappear(RowPresenter.ViewHolder rowViewHolder) { 312 showPrimaryActions((ViewHolder) rowViewHolder); 313 } 314 getDefaultBackgroundColor(Context context)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 getDefaultProgressColor(Context context)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 createRowViewHolder(ViewGroup parent)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 initRow(final ViewHolder vh)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 onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item)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 updateCardLayout(ViewHolder vh, int height)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 onUnbindRowViewHolder(RowPresenter.ViewHolder holder)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 onRowViewSelected(RowPresenter.ViewHolder vh, boolean selected)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 onRowViewAttachedToWindow(RowPresenter.ViewHolder vh)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 onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh)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