PlaybackTransportRowPresenter.java revision 3bcad88cbf4488e747d84893c35f2351b8f84afe
1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package android.support.v17.leanback.widget; 17 18import android.content.Context; 19import android.graphics.Bitmap; 20import android.graphics.Color; 21import android.os.Build; 22import android.support.annotation.ColorInt; 23import android.support.v17.leanback.R; 24import android.support.v17.leanback.widget.ControlBarPresenter.OnControlClickedListener; 25import android.support.v17.leanback.widget.ControlBarPresenter.OnControlSelectedListener; 26import android.util.TypedValue; 27import android.view.KeyEvent; 28import android.view.LayoutInflater; 29import android.view.View; 30import android.view.ViewGroup; 31import android.widget.ImageView; 32import android.widget.TextView; 33 34import java.util.Arrays; 35 36/** 37 * A PlaybackTransportRowPresenter renders a {@link PlaybackControlsRow} to display a 38 * series of playback control buttons. Typically this row will be the first row in a fragment 39 * such as the {@link android.support.v17.leanback.app.PlaybackSupportFragment}. 40 * 41 * <p>The detailed description is rendered using a {@link Presenter} passed in 42 * {@link #setDescriptionPresenter(Presenter)}. This can be an instance of 43 * {@link AbstractDetailsDescriptionPresenter}. The application can access the 44 * detailed description ViewHolder from {@link ViewHolder#getDescriptionViewHolder()}. 45 * </p> 46 */ 47public class PlaybackTransportRowPresenter extends PlaybackRowPresenter { 48 49 static class BoundData extends PlaybackControlsPresenter.BoundData { 50 ViewHolder mRowViewHolder; 51 } 52 53 /** 54 * A ViewHolder for the PlaybackControlsRow supporting seek UI. 55 */ 56 public class ViewHolder extends PlaybackRowPresenter.ViewHolder implements PlaybackSeekUi { 57 final Presenter.ViewHolder mDescriptionViewHolder; 58 final ImageView mImageView; 59 final ViewGroup mDescriptionDock; 60 final ViewGroup mControlsDock; 61 final ViewGroup mSecondaryControlsDock; 62 final TextView mTotalTime; 63 final TextView mCurrentTime; 64 final SeekBar mProgressBar; 65 final ThumbsBar mThumbsBar; 66 long mTotalTimeInMs = Long.MIN_VALUE; 67 long mCurrentTimeInMs = Long.MIN_VALUE; 68 long mSecondaryProgressInMs; 69 final StringBuilder mTempBuilder = new StringBuilder(); 70 ControlBarPresenter.ViewHolder mControlsVh; 71 ControlBarPresenter.ViewHolder mSecondaryControlsVh; 72 BoundData mControlsBoundData = new BoundData(); 73 BoundData mSecondaryBoundData = new BoundData(); 74 Presenter.ViewHolder mSelectedViewHolder; 75 Object mSelectedItem; 76 PlaybackControlsRow.PlayPauseAction mPlayPauseAction; 77 int mThumbHeroIndex = -1; 78 79 Client mSeekClient; 80 boolean mInSeek; 81 PlaybackSeekDataProvider mSeekDataProvider; 82 long[] mPositions; 83 int mPositionsLength; 84 85 final PlaybackControlsRow.OnPlaybackProgressCallback mListener = 86 new PlaybackControlsRow.OnPlaybackProgressCallback() { 87 @Override 88 public void onCurrentPositionChanged(PlaybackControlsRow row, long ms) { 89 setCurrentPosition(ms); 90 } 91 92 @Override 93 public void onDurationChanged(PlaybackControlsRow row, long ms) { 94 setTotalTime(ms); 95 } 96 97 @Override 98 public void onBufferedPositionChanged(PlaybackControlsRow row, long ms) { 99 setBufferedPosition(ms); 100 } 101 }; 102 103 void updateProgressInSeek(boolean forward) { 104 long newPos; 105 long pos = mCurrentTimeInMs; 106 if (mPositionsLength > 0) { 107 int index = Arrays.binarySearch(mPositions, 0, mPositionsLength, pos); 108 int thumbHeroIndex; 109 if (forward) { 110 if (index >= 0) { 111 // found it, seek to neighbour key position at higher side 112 if (index < mPositionsLength - 1) { 113 newPos = mPositions[index + 1]; 114 thumbHeroIndex = index + 1; 115 } else { 116 newPos = mTotalTimeInMs; 117 thumbHeroIndex = index; 118 } 119 } else { 120 // not found, seek to neighbour key position at higher side. 121 int insertIndex = -1 - index; 122 if (insertIndex <= mPositionsLength - 1) { 123 newPos = mPositions[insertIndex]; 124 thumbHeroIndex = insertIndex; 125 } else { 126 newPos = mTotalTimeInMs; 127 thumbHeroIndex = insertIndex > 0 ? insertIndex - 1 : 0; 128 } 129 } 130 } else { 131 if (index >= 0) { 132 // found it, seek to neighbour key position at lower side. 133 if (index > 0) { 134 newPos = mPositions[index - 1]; 135 thumbHeroIndex = index - 1; 136 } else { 137 newPos = 0; 138 thumbHeroIndex = 0; 139 } 140 } else { 141 // not found, seek to neighbour key position at lower side. 142 int insertIndex = -1 - index; 143 if (insertIndex > 0) { 144 newPos = mPositions[insertIndex - 1]; 145 thumbHeroIndex = insertIndex - 1; 146 } else { 147 newPos = 0; 148 thumbHeroIndex = 0; 149 } 150 } 151 } 152 updateThumbsInSeek(thumbHeroIndex, forward); 153 } else { 154 long interval = (long) (mTotalTimeInMs * getDefaultSeekIncrement()); 155 newPos = pos + (forward ? interval : -interval); 156 if (newPos > mTotalTimeInMs) { 157 newPos = mTotalTimeInMs; 158 } else if (newPos < 0) { 159 newPos = 0; 160 } 161 } 162 double ratio = (double) newPos / mTotalTimeInMs; // Range: [0, 1] 163 mProgressBar.setProgress((int) (ratio * Integer.MAX_VALUE)); // Could safely cast to int 164 mSeekClient.onSeekPositionChanged(newPos); 165 } 166 167 void updateThumbsInSeek(int thumbHeroIndex, boolean forward) { 168 if (mThumbHeroIndex == thumbHeroIndex) { 169 return; 170 } 171 172 final int totalNum = mThumbsBar.getChildCount(); 173 if (totalNum < 0 || (totalNum & 1) == 0) { 174 throw new RuntimeException(); 175 } 176 final int heroChildIndex = totalNum / 2; 177 final int start = Math.max(thumbHeroIndex - (totalNum / 2), 0); 178 final int end = Math.min(thumbHeroIndex + (totalNum / 2), mPositionsLength - 1); 179 final int newRequestStart; 180 final int newRequestEnd; 181 182 if (mThumbHeroIndex < 0) { 183 // first time 184 newRequestStart = start; 185 newRequestEnd = end; 186 } else { 187 forward = thumbHeroIndex > mThumbHeroIndex; 188 final int oldStart = Math.max(mThumbHeroIndex - (totalNum / 2), 0); 189 final int oldEnd = Math.min(mThumbHeroIndex + (totalNum / 2), 190 mPositionsLength - 1); 191 if (forward) { 192 newRequestStart = Math.max(oldEnd + 1, start); 193 newRequestEnd = end; 194 // overlapping area directly assign bitmap from previous result 195 for (int i = start; i <= newRequestStart - 1; i++) { 196 mThumbsBar.setThumbBitmap(heroChildIndex + (i - thumbHeroIndex), 197 mThumbsBar.getThumbBitmap(heroChildIndex + (i - mThumbHeroIndex))); 198 } 199 } else { 200 newRequestEnd = Math.min(oldStart - 1, end); 201 newRequestStart = start; 202 // overlapping area directly assign bitmap from previous result in backward 203 for (int i = end; i >= newRequestEnd + 1; i--) { 204 mThumbsBar.setThumbBitmap(heroChildIndex + (i - thumbHeroIndex), 205 mThumbsBar.getThumbBitmap(heroChildIndex + (i - mThumbHeroIndex))); 206 } 207 } 208 } 209 // processing new requests with mThumbHeroIndex updated 210 mThumbHeroIndex = thumbHeroIndex; 211 if (forward) { 212 for (int i = newRequestStart; i <= newRequestEnd; i++) { 213 mSeekDataProvider.getThumbnail(i, mThumbResult); 214 } 215 } else { 216 for (int i = newRequestEnd; i >= newRequestStart; i--) { 217 mSeekDataProvider.getThumbnail(i, mThumbResult); 218 } 219 } 220 // set thumb bitmaps outside (start , end) to null 221 for (int childIndex = 0; childIndex < heroChildIndex - mThumbHeroIndex + start; 222 childIndex++) { 223 mThumbsBar.setThumbBitmap(childIndex, null); 224 } 225 for (int childIndex = heroChildIndex + end - mThumbHeroIndex + 1; 226 childIndex < totalNum; childIndex++) { 227 mThumbsBar.setThumbBitmap(childIndex, null); 228 } 229 } 230 231 PlaybackSeekDataProvider.ResultCallback mThumbResult = 232 new PlaybackSeekDataProvider.ResultCallback() { 233 @Override 234 public void onThumbnailLoaded(Bitmap bitmap, int index) { 235 int childIndex = index - (mThumbHeroIndex - mThumbsBar.getChildCount() / 2); 236 if (childIndex < 0 || childIndex >= mThumbsBar.getChildCount()) { 237 return; 238 } 239 mThumbsBar.setThumbBitmap(childIndex, bitmap); 240 } 241 }; 242 243 boolean onForward() { 244 if (!startSeek()) { 245 return false; 246 } 247 updateProgressInSeek(true); 248 return true; 249 } 250 251 boolean onBackward() { 252 if (!startSeek()) { 253 return false; 254 } 255 updateProgressInSeek(false); 256 return true; 257 } 258 /** 259 * Constructor of ViewHolder of PlaybackTransportRowPresenter 260 * @param rootView Root view of the ViewHolder. 261 * @param descriptionPresenter The presenter that will be used to create description 262 * ViewHolder. The description view will be added into tree. 263 */ 264 public ViewHolder(View rootView, Presenter descriptionPresenter) { 265 super(rootView); 266 mImageView = (ImageView) rootView.findViewById(R.id.image); 267 mDescriptionDock = (ViewGroup) rootView.findViewById(R.id.description_dock); 268 mCurrentTime = (TextView) rootView.findViewById(R.id.current_time); 269 mTotalTime = (TextView) rootView.findViewById(R.id.total_time); 270 mProgressBar = (SeekBar) rootView.findViewById(R.id.playback_progress); 271 mProgressBar.setOnClickListener(new View.OnClickListener() { 272 @Override 273 public void onClick(View view) { 274 onProgressBarClicked(ViewHolder.this); 275 } 276 }); 277 mProgressBar.setOnKeyListener(new View.OnKeyListener() { 278 279 @Override 280 public boolean onKey(View view, int keyCode, KeyEvent keyEvent) { 281 // when in seek only allow this keys 282 switch (keyCode) { 283 case KeyEvent.KEYCODE_DPAD_UP: 284 case KeyEvent.KEYCODE_DPAD_DOWN: 285 // eat DPAD UP/DOWN in seek mode 286 return mInSeek; 287 case KeyEvent.KEYCODE_DPAD_LEFT: 288 case KeyEvent.KEYCODE_MINUS: 289 case KeyEvent.KEYCODE_MEDIA_REWIND: 290 if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) { 291 onBackward(); 292 } 293 return true; 294 case KeyEvent.KEYCODE_DPAD_RIGHT: 295 case KeyEvent.KEYCODE_PLUS: 296 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 297 if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) { 298 onForward(); 299 } 300 return true; 301 case KeyEvent.KEYCODE_DPAD_CENTER: 302 case KeyEvent.KEYCODE_ENTER: 303 if (!mInSeek) { 304 return false; 305 } 306 if (keyEvent.getAction() == KeyEvent.ACTION_UP) { 307 stopSeek(false); 308 } 309 return true; 310 case KeyEvent.KEYCODE_BACK: 311 case KeyEvent.KEYCODE_ESCAPE: 312 if (!mInSeek) { 313 return false; 314 } 315 if (keyEvent.getAction() == KeyEvent.ACTION_UP) { 316 // SeekBar does not support cancel in accessibility mode, so always 317 // "confirm" if accessibility is on. 318 stopSeek(Build.VERSION.SDK_INT >= 21 319 ? !mProgressBar.isAccessibilityFocused() : true); 320 } 321 return true; 322 } 323 return false; 324 } 325 }); 326 mProgressBar.setAccessibilitySeekListener(new SeekBar.AccessibilitySeekListener() { 327 @Override 328 public boolean onAccessibilitySeekForward() { 329 return onForward(); 330 } 331 332 @Override 333 public boolean onAccessibilitySeekBackward() { 334 return onBackward(); 335 } 336 }); 337 mProgressBar.setMax(Integer.MAX_VALUE); //current progress will be a fraction of this 338 mControlsDock = (ViewGroup) rootView.findViewById(R.id.controls_dock); 339 mSecondaryControlsDock = 340 (ViewGroup) rootView.findViewById(R.id.secondary_controls_dock); 341 mDescriptionViewHolder = descriptionPresenter == null ? null : 342 descriptionPresenter.onCreateViewHolder(mDescriptionDock); 343 if (mDescriptionViewHolder != null) { 344 mDescriptionDock.addView(mDescriptionViewHolder.view); 345 } 346 mThumbsBar = (ThumbsBar) rootView.findViewById(R.id.thumbs_row); 347 } 348 349 /** 350 * @return The ViewHolder for description. 351 */ 352 public final Presenter.ViewHolder getDescriptionViewHolder() { 353 return mDescriptionViewHolder; 354 } 355 356 @Override 357 public void setPlaybackSeekUiClient(Client client) { 358 mSeekClient = client; 359 } 360 361 boolean startSeek() { 362 if (mInSeek) { 363 return true; 364 } 365 if (mSeekClient == null || !mSeekClient.isSeekEnabled() 366 || mTotalTimeInMs <= 0) { 367 return false; 368 } 369 mInSeek = true; 370 mSeekClient.onSeekStarted(); 371 mSeekDataProvider = mSeekClient.getPlaybackSeekDataProvider(); 372 mPositions = mSeekDataProvider != null ? mSeekDataProvider.getSeekPositions() : null; 373 if (mPositions != null) { 374 int pos = Arrays.binarySearch(mPositions, mTotalTimeInMs); 375 if (pos >= 0) { 376 mPositionsLength = pos + 1; 377 } else { 378 mPositionsLength = -1 - pos; 379 } 380 } else { 381 mPositionsLength = 0; 382 } 383 mControlsVh.view.setVisibility(View.INVISIBLE); 384 mSecondaryControlsVh.view.setVisibility(View.INVISIBLE); 385 mDescriptionViewHolder.view.setVisibility(View.INVISIBLE); 386 mThumbsBar.setVisibility(View.VISIBLE); 387 return true; 388 } 389 390 void stopSeek(boolean cancelled) { 391 if (!mInSeek) { 392 return; 393 } 394 mInSeek = false; 395 mSeekClient.onSeekFinished(cancelled); 396 if (mSeekDataProvider != null) { 397 mSeekDataProvider.reset(); 398 } 399 mThumbHeroIndex = -1; 400 mThumbsBar.clearThumbBitmaps(); 401 mSeekDataProvider = null; 402 mPositions = null; 403 mPositionsLength = 0; 404 mControlsVh.view.setVisibility(View.VISIBLE); 405 mSecondaryControlsVh.view.setVisibility(View.VISIBLE); 406 mDescriptionViewHolder.view.setVisibility(View.VISIBLE); 407 mThumbsBar.setVisibility(View.INVISIBLE); 408 } 409 410 void dispatchItemSelection() { 411 if (!isSelected()) { 412 return; 413 } 414 if (mSelectedViewHolder == null) { 415 if (getOnItemViewSelectedListener() != null) { 416 getOnItemViewSelectedListener().onItemSelected(null, null, 417 ViewHolder.this, getRow()); 418 } 419 } else { 420 if (getOnItemViewSelectedListener() != null) { 421 getOnItemViewSelectedListener().onItemSelected(mSelectedViewHolder, 422 mSelectedItem, ViewHolder.this, getRow()); 423 } 424 } 425 }; 426 427 Presenter getPresenter(boolean primary) { 428 ObjectAdapter adapter = primary 429 ? ((PlaybackControlsRow) getRow()).getPrimaryActionsAdapter() 430 : ((PlaybackControlsRow) getRow()).getSecondaryActionsAdapter(); 431 if (adapter == null) { 432 return null; 433 } 434 if (adapter.getPresenterSelector() instanceof ControlButtonPresenterSelector) { 435 ControlButtonPresenterSelector selector = 436 (ControlButtonPresenterSelector) adapter.getPresenterSelector(); 437 return selector.getSecondaryPresenter(); 438 } 439 return adapter.getPresenter(adapter.size() > 0 ? adapter.get(0) : null); 440 } 441 442 /** 443 * Returns the TextView that showing total time label. This method might be used in 444 * {@link #onSetDurationLabel}. 445 * @return The TextView that showing total time label. 446 */ 447 public final TextView getDurationView() { 448 return mTotalTime; 449 } 450 451 /** 452 * Called to update total time label. Default implementation updates the TextView 453 * {@link #getDurationView()}. Subclass might override. 454 * @param totalTimeMs Total duration of the media in milliseconds. 455 */ 456 protected void onSetDurationLabel(long totalTimeMs) { 457 if (mTotalTime != null) { 458 formatTime(totalTimeMs, mTempBuilder); 459 mTotalTime.setText(mTempBuilder.toString()); 460 } 461 } 462 463 void setTotalTime(long totalTimeMs) { 464 if (mTotalTimeInMs != totalTimeMs) { 465 mTotalTimeInMs = totalTimeMs; 466 onSetDurationLabel(totalTimeMs); 467 } 468 } 469 470 /** 471 * Returns the TextView that showing current position label. This method might be used in 472 * {@link #onSetCurrentPositionLabel}. 473 * @return The TextView that showing current position label. 474 */ 475 public final TextView getCurrentPositionView() { 476 return mCurrentTime; 477 } 478 479 /** 480 * Called to update current time label. Default implementation updates the TextView 481 * {@link #getCurrentPositionView}. Subclass might override. 482 * @param currentTimeMs Current playback position in milliseconds. 483 */ 484 protected void onSetCurrentPositionLabel(long currentTimeMs) { 485 if (mCurrentTime != null) { 486 formatTime(currentTimeMs, mTempBuilder); 487 mCurrentTime.setText(mTempBuilder.toString()); 488 } 489 } 490 491 void setCurrentPosition(long currentTimeMs) { 492 if (currentTimeMs != mCurrentTimeInMs) { 493 mCurrentTimeInMs = currentTimeMs; 494 onSetCurrentPositionLabel(currentTimeMs); 495 } 496 if (!mInSeek) { 497 int progressRatio = 0; 498 if (mTotalTimeInMs > 0) { 499 // Use ratio to represent current progres 500 double ratio = (double) mCurrentTimeInMs / mTotalTimeInMs; // Range: [0, 1] 501 progressRatio = (int) (ratio * Integer.MAX_VALUE); // Could safely cast to int 502 } 503 mProgressBar.setProgress((int) progressRatio); 504 } 505 } 506 507 void setBufferedPosition(long progressMs) { 508 mSecondaryProgressInMs = progressMs; 509 // Solve the progress bar by using ratio 510 double ratio = (double) progressMs / mTotalTimeInMs; // Range: [0, 1] 511 double progressRatio = ratio * Integer.MAX_VALUE; // Could safely cast to int 512 mProgressBar.setSecondaryProgress((int) progressRatio); 513 } 514 } 515 516 static void formatTime(long ms, StringBuilder sb) { 517 sb.setLength(0); 518 if (ms < 0) { 519 sb.append("--"); 520 return; 521 } 522 long seconds = ms / 1000; 523 long minutes = seconds / 60; 524 long hours = minutes / 60; 525 seconds -= minutes * 60; 526 minutes -= hours * 60; 527 528 if (hours > 0) { 529 sb.append(hours).append(':'); 530 if (minutes < 10) { 531 sb.append('0'); 532 } 533 } 534 sb.append(minutes).append(':'); 535 if (seconds < 10) { 536 sb.append('0'); 537 } 538 sb.append(seconds); 539 } 540 541 float mDefaultSeekIncrement = 0.01f; 542 int mProgressColor = Color.TRANSPARENT; 543 boolean mProgressColorSet; 544 Presenter mDescriptionPresenter; 545 ControlBarPresenter mPlaybackControlsPresenter; 546 ControlBarPresenter mSecondaryControlsPresenter; 547 OnActionClickedListener mOnActionClickedListener; 548 549 private final OnControlSelectedListener mOnControlSelectedListener = 550 new OnControlSelectedListener() { 551 @Override 552 public void onControlSelected(Presenter.ViewHolder itemViewHolder, Object item, 553 ControlBarPresenter.BoundData data) { 554 ViewHolder vh = ((BoundData) data).mRowViewHolder; 555 if (vh.mSelectedViewHolder != itemViewHolder || vh.mSelectedItem != item) { 556 vh.mSelectedViewHolder = itemViewHolder; 557 vh.mSelectedItem = item; 558 vh.dispatchItemSelection(); 559 } 560 } 561 }; 562 563 private final OnControlClickedListener mOnControlClickedListener = 564 new OnControlClickedListener() { 565 @Override 566 public void onControlClicked(Presenter.ViewHolder itemViewHolder, Object item, 567 ControlBarPresenter.BoundData data) { 568 ViewHolder vh = ((BoundData) data).mRowViewHolder; 569 if (vh.getOnItemViewClickedListener() != null) { 570 vh.getOnItemViewClickedListener().onItemClicked(itemViewHolder, item, 571 vh, vh.getRow()); 572 } 573 if (mOnActionClickedListener != null && item instanceof Action) { 574 mOnActionClickedListener.onActionClicked((Action) item); 575 } 576 } 577 }; 578 579 public PlaybackTransportRowPresenter() { 580 setHeaderPresenter(null); 581 setSelectEffectEnabled(false); 582 583 mPlaybackControlsPresenter = new ControlBarPresenter(R.layout.lb_control_bar); 584 mPlaybackControlsPresenter.setDefaultFocusToMiddle(false); 585 mSecondaryControlsPresenter = new ControlBarPresenter(R.layout.lb_control_bar); 586 mSecondaryControlsPresenter.setDefaultFocusToMiddle(false); 587 588 mPlaybackControlsPresenter.setOnControlSelectedListener(mOnControlSelectedListener); 589 mSecondaryControlsPresenter.setOnControlSelectedListener(mOnControlSelectedListener); 590 mPlaybackControlsPresenter.setOnControlClickedListener(mOnControlClickedListener); 591 mSecondaryControlsPresenter.setOnControlClickedListener(mOnControlClickedListener); 592 } 593 594 /** 595 * @param descriptionPresenter Presenter for displaying item details. 596 */ 597 public void setDescriptionPresenter(Presenter descriptionPresenter) { 598 mDescriptionPresenter = descriptionPresenter; 599 } 600 601 /** 602 * Sets the listener for {@link Action} click events. 603 */ 604 public void setOnActionClickedListener(OnActionClickedListener listener) { 605 mOnActionClickedListener = listener; 606 } 607 608 /** 609 * Returns the listener for {@link Action} click events. 610 */ 611 public OnActionClickedListener getOnActionClickedListener() { 612 return mOnActionClickedListener; 613 } 614 615 /** 616 * Sets the primary color for the progress bar. If not set, a default from 617 * the theme will be used. 618 */ 619 public void setProgressColor(@ColorInt int color) { 620 mProgressColor = color; 621 mProgressColorSet = true; 622 } 623 624 /** 625 * Returns the primary color for the progress bar. If no color was set, transparent 626 * is returned. 627 */ 628 @ColorInt 629 public int getProgressColor() { 630 return mProgressColor; 631 } 632 633 @Override 634 public void onReappear(RowPresenter.ViewHolder rowViewHolder) { 635 ViewHolder vh = (ViewHolder) rowViewHolder; 636 if (vh.view.hasFocus()) { 637 vh.mProgressBar.requestFocus(); 638 } 639 } 640 641 private int getDefaultProgressColor(Context context) { 642 TypedValue outValue = new TypedValue(); 643 if (context.getTheme() 644 .resolveAttribute(R.attr.playbackProgressPrimaryColor, outValue, true)) { 645 return context.getResources().getColor(outValue.resourceId); 646 } 647 return context.getResources().getColor(R.color.lb_playback_progress_color_no_theme); 648 } 649 650 @Override 651 protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) { 652 View v = LayoutInflater.from(parent.getContext()).inflate( 653 R.layout.lb_playback_transport_controls_row, parent, false); 654 ViewHolder vh = new ViewHolder(v, mDescriptionPresenter); 655 initRow(vh); 656 return vh; 657 } 658 659 private void initRow(final ViewHolder vh) { 660 vh.mControlsVh = (ControlBarPresenter.ViewHolder) mPlaybackControlsPresenter 661 .onCreateViewHolder(vh.mControlsDock); 662 vh.mProgressBar.setProgressColor(mProgressColorSet ? mProgressColor 663 : getDefaultProgressColor(vh.mControlsDock.getContext())); 664 vh.mControlsDock.addView(vh.mControlsVh.view); 665 666 vh.mSecondaryControlsVh = (ControlBarPresenter.ViewHolder) mSecondaryControlsPresenter 667 .onCreateViewHolder(vh.mSecondaryControlsDock); 668 vh.mSecondaryControlsDock.addView(vh.mSecondaryControlsVh.view); 669 ((PlaybackTransportRowView) vh.view).setOnUnhandledKeyListener( 670 new PlaybackTransportRowView.OnUnhandledKeyListener() { 671 @Override 672 public boolean onUnhandledKey(KeyEvent event) { 673 if (vh.getOnKeyListener() != null) { 674 if (vh.getOnKeyListener().onKey(vh.view, event.getKeyCode(), event)) { 675 return true; 676 } 677 } 678 return false; 679 } 680 }); 681 } 682 683 @Override 684 protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) { 685 super.onBindRowViewHolder(holder, item); 686 687 ViewHolder vh = (ViewHolder) holder; 688 PlaybackControlsRow row = (PlaybackControlsRow) vh.getRow(); 689 690 if (row.getItem() == null) { 691 vh.mDescriptionDock.setVisibility(View.GONE); 692 } else { 693 vh.mDescriptionDock.setVisibility(View.VISIBLE); 694 if (vh.mDescriptionViewHolder != null) { 695 mDescriptionPresenter.onBindViewHolder(vh.mDescriptionViewHolder, row.getItem()); 696 } 697 } 698 699 if (row.getImageDrawable() == null) { 700 vh.mImageView.setVisibility(View.GONE); 701 } else { 702 vh.mImageView.setVisibility(View.VISIBLE); 703 } 704 vh.mImageView.setImageDrawable(row.getImageDrawable()); 705 706 vh.mControlsBoundData.adapter = row.getPrimaryActionsAdapter(); 707 vh.mControlsBoundData.presenter = vh.getPresenter(true); 708 vh.mControlsBoundData.mRowViewHolder = vh; 709 mPlaybackControlsPresenter.onBindViewHolder(vh.mControlsVh, vh.mControlsBoundData); 710 711 vh.mSecondaryBoundData.adapter = row.getSecondaryActionsAdapter(); 712 vh.mSecondaryBoundData.presenter = vh.getPresenter(false); 713 vh.mSecondaryBoundData.mRowViewHolder = vh; 714 mSecondaryControlsPresenter.onBindViewHolder(vh.mSecondaryControlsVh, 715 vh.mSecondaryBoundData); 716 717 vh.setTotalTime(row.getDuration()); 718 vh.setCurrentPosition(row.getCurrentPosition()); 719 vh.setBufferedPosition(row.getBufferedPosition()); 720 row.setOnPlaybackProgressChangedListener(vh.mListener); 721 } 722 723 @Override 724 protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) { 725 ViewHolder vh = (ViewHolder) holder; 726 PlaybackControlsRow row = (PlaybackControlsRow) vh.getRow(); 727 728 if (vh.mDescriptionViewHolder != null) { 729 mDescriptionPresenter.onUnbindViewHolder(vh.mDescriptionViewHolder); 730 } 731 mPlaybackControlsPresenter.onUnbindViewHolder(vh.mControlsVh); 732 mSecondaryControlsPresenter.onUnbindViewHolder(vh.mSecondaryControlsVh); 733 row.setOnPlaybackProgressChangedListener(null); 734 735 super.onUnbindRowViewHolder(holder); 736 } 737 738 /** 739 * Client of progress bar is clicked, default implementation delegate click to 740 * PlayPauseAction. 741 * 742 * @param vh ViewHolder of PlaybackTransportRowPresenter 743 */ 744 protected void onProgressBarClicked(ViewHolder vh) { 745 if (vh != null) { 746 if (vh.mPlayPauseAction == null) { 747 vh.mPlayPauseAction = new PlaybackControlsRow.PlayPauseAction(vh.view.getContext()); 748 } 749 if (vh.getOnItemViewClickedListener() != null) { 750 vh.getOnItemViewClickedListener().onItemClicked(vh, vh.mPlayPauseAction, 751 vh, vh.getRow()); 752 } 753 if (mOnActionClickedListener != null) { 754 mOnActionClickedListener.onActionClicked(vh.mPlayPauseAction); 755 } 756 } 757 } 758 759 /** 760 * Set default seek increment if {@link PlaybackSeekDataProvider} is null. 761 * @param ratio float value between 0(inclusive) and 1(inclusive). 762 */ 763 public void setDefaultSeekIncrement(float ratio) { 764 mDefaultSeekIncrement = ratio; 765 } 766 767 /** 768 * Get default seek increment if {@link PlaybackSeekDataProvider} is null. 769 * @return float value between 0(inclusive) and 1(inclusive). 770 */ 771 public float getDefaultSeekIncrement() { 772 return mDefaultSeekIncrement; 773 } 774 775 @Override 776 protected void onRowViewSelected(RowPresenter.ViewHolder vh, boolean selected) { 777 super.onRowViewSelected(vh, selected); 778 if (selected) { 779 ((ViewHolder) vh).dispatchItemSelection(); 780 } 781 } 782 783 @Override 784 protected void onRowViewAttachedToWindow(RowPresenter.ViewHolder vh) { 785 super.onRowViewAttachedToWindow(vh); 786 if (mDescriptionPresenter != null) { 787 mDescriptionPresenter.onViewAttachedToWindow( 788 ((ViewHolder) vh).mDescriptionViewHolder); 789 } 790 } 791 792 @Override 793 protected void onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh) { 794 super.onRowViewDetachedFromWindow(vh); 795 if (mDescriptionPresenter != null) { 796 mDescriptionPresenter.onViewDetachedFromWindow( 797 ((ViewHolder) vh).mDescriptionViewHolder); 798 } 799 } 800 801} 802