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