DetailsFragment.java revision 6525e063fbbd691a8553f4fc77f3960f93bea34d
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 */ 14package android.support.v17.leanback.app; 15 16import android.app.Activity; 17import android.app.Fragment; 18import android.app.FragmentTransaction; 19import android.graphics.Rect; 20import android.graphics.drawable.Drawable; 21import android.os.Build; 22import android.os.Bundle; 23import android.support.annotation.CallSuper; 24import android.support.v17.leanback.R; 25import android.support.v17.leanback.transition.TransitionHelper; 26import android.support.v17.leanback.transition.TransitionListener; 27import android.support.v17.leanback.widget.BaseOnItemViewClickedListener; 28import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener; 29import android.support.v17.leanback.widget.BrowseFrameLayout; 30import android.support.v17.leanback.widget.DetailsParallax; 31import android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter; 32import android.support.v17.leanback.widget.ItemAlignmentFacet; 33import android.support.v17.leanback.widget.ItemBridgeAdapter; 34import android.support.v17.leanback.widget.ObjectAdapter; 35import android.support.v17.leanback.widget.Presenter; 36import android.support.v17.leanback.widget.PresenterSelector; 37import android.support.v17.leanback.widget.RowPresenter; 38import android.support.v17.leanback.widget.VerticalGridView; 39import android.util.Log; 40import android.view.KeyEvent; 41import android.view.LayoutInflater; 42import android.view.View; 43import android.view.ViewGroup; 44 45import java.lang.ref.WeakReference; 46 47/** 48 * A fragment for creating Leanback details screens. 49 * 50 * <p> 51 * A DetailsFragment renders the elements of its {@link ObjectAdapter} as a set 52 * of rows in a vertical list.The Adapter's {@link PresenterSelector} must maintain subclasses 53 * of {@link RowPresenter}. 54 * </p> 55 * 56 * When {@link FullWidthDetailsOverviewRowPresenter} is found in adapter, DetailsFragment will 57 * setup default behavior of the DetailsOverviewRow: 58 * <li> 59 * The alignment of FullWidthDetailsOverviewRowPresenter is setup in 60 * {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)}. 61 * </li> 62 * <li> 63 * The view status switching of FullWidthDetailsOverviewRowPresenter is done in 64 * {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter, 65 * FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)}. 66 * </li> 67 * 68 * <p> 69 * The recommended activity themes to use with a DetailsFragment are 70 * <li> 71 * {@link android.support.v17.leanback.R.style#Theme_Leanback_Details} with activity 72 * shared element transition for {@link FullWidthDetailsOverviewRowPresenter}. 73 * </li> 74 * <li> 75 * {@link android.support.v17.leanback.R.style#Theme_Leanback_Details_NoSharedElementTransition} 76 * if shared element transition is not needed, for example if first row is not rendered by 77 * {@link FullWidthDetailsOverviewRowPresenter}. 78 * </li> 79 * </p> 80 */ 81public class DetailsFragment extends BaseFragment { 82 static final String TAG = "DetailsFragment"; 83 static boolean DEBUG = false; 84 85 /** 86 * Flag for "possibly" having enter transition not finished yet. 87 * @see #mStartAndTransitionFlag 88 */ 89 static final int PF_ENTER_TRANSITION_PENDING = 0x1 << 0; 90 /** 91 * Flag for having entrance transition not finished yet. 92 * @see #mStartAndTransitionFlag 93 */ 94 static final int PF_ENTRANCE_TRANSITION_PENDING = 0x1 << 1; 95 /** 96 * Flag that onStart() has been called and about to call onSafeStart() when 97 * pending transitions are finished. 98 * @see #mStartAndTransitionFlag 99 */ 100 static final int PF_PENDING_START = 0x1 << 2; 101 102 private class SetSelectionRunnable implements Runnable { 103 int mPosition; 104 boolean mSmooth = true; 105 106 SetSelectionRunnable() { 107 } 108 109 @Override 110 public void run() { 111 if (mRowsFragment == null) { 112 return; 113 } 114 mRowsFragment.setSelectedPosition(mPosition, mSmooth); 115 } 116 } 117 118 /** 119 * Start this task when first DetailsOverviewRow is created, if there is no entrance transition 120 * started, it will clear PF_ENTRANCE_TRANSITION_PENDING. 121 * @see #mStartAndTransitionFlag 122 */ 123 static class WaitEnterTransitionTimeout implements Runnable { 124 static final long WAIT_ENTERTRANSITION_START = 200; 125 126 final WeakReference<DetailsFragment> mRef; 127 128 WaitEnterTransitionTimeout(DetailsFragment f) { 129 mRef = new WeakReference(f); 130 f.getView().postDelayed(this, WAIT_ENTERTRANSITION_START); 131 } 132 133 @Override 134 public void run() { 135 DetailsFragment f = mRef.get(); 136 if (f != null) { 137 f.clearPendingEnterTransition(); 138 } 139 } 140 } 141 142 /** 143 * @see #mStartAndTransitionFlag 144 */ 145 TransitionListener mEnterTransitionListener = new TransitionListener() { 146 @Override 147 public void onTransitionStart(Object transition) { 148 if (mWaitEnterTransitionTimeout != null) { 149 // cancel task of WaitEnterTransitionTimeout, we will clearPendingEnterTransition 150 // when transition finishes. 151 mWaitEnterTransitionTimeout.mRef.clear(); 152 } 153 } 154 155 @Override 156 public void onTransitionCancel(Object transition) { 157 clearPendingEnterTransition(); 158 } 159 160 @Override 161 public void onTransitionEnd(Object transition) { 162 clearPendingEnterTransition(); 163 } 164 }; 165 166 TransitionListener mReturnTransitionListener = new TransitionListener() { 167 @Override 168 public void onTransitionStart(Object transition) { 169 onReturnTransitionStart(); 170 } 171 }; 172 173 BrowseFrameLayout mRootView; 174 View mBackgroundView; 175 Drawable mBackgroundDrawable; 176 Fragment mVideoFragment; 177 DetailsParallax mDetailsParallax; 178 RowsFragment mRowsFragment; 179 ObjectAdapter mAdapter; 180 int mContainerListAlignTop; 181 BaseOnItemViewSelectedListener mExternalOnItemViewSelectedListener; 182 BaseOnItemViewClickedListener mOnItemViewClickedListener; 183 DetailsFragmentBackgroundController mDetailsBackgroundController; 184 185 186 /** 187 * Flags for enter transition, entrance transition and onStart. When onStart() is called 188 * and both enter transiton and entrance transition are finished, we could call onSafeStart(). 189 * 1. in onCreate: 190 * if user call prepareEntranceTransition, set PF_ENTRANCE_TRANSITION_PENDING 191 * if there is enterTransition, set PF_ENTER_TRANSITION_PENDING, but we dont know if 192 * user will run enterTransition or not. 193 * 2. when user add row, start WaitEnterTransitionTimeout to wait possible enter transition 194 * start. If enter transition onTransitionStart is not invoked with a period, we can assume 195 * there is no enter transition running, then WaitEnterTransitionTimeout will clear 196 * PF_ENTER_TRANSITION_PENDING. 197 * 3. When enterTransition runs (either postponed or not), we will stop the 198 * WaitEnterTransitionTimeout, and let onTransitionEnd/onTransitionCancel to clear 199 * PF_ENTER_TRANSITION_PENDING. 200 */ 201 int mStartAndTransitionFlag = 0; 202 WaitEnterTransitionTimeout mWaitEnterTransitionTimeout; 203 204 Object mSceneAfterEntranceTransition; 205 206 final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable(); 207 208 final BaseOnItemViewSelectedListener<Object> mOnItemViewSelectedListener = 209 new BaseOnItemViewSelectedListener<Object>() { 210 @Override 211 public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, 212 RowPresenter.ViewHolder rowViewHolder, Object row) { 213 int position = mRowsFragment.getVerticalGridView().getSelectedPosition(); 214 int subposition = mRowsFragment.getVerticalGridView().getSelectedSubPosition(); 215 if (DEBUG) Log.v(TAG, "row selected position " + position 216 + " subposition " + subposition); 217 onRowSelected(position, subposition); 218 if (mExternalOnItemViewSelectedListener != null) { 219 mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item, 220 rowViewHolder, row); 221 } 222 } 223 }; 224 225 /** 226 * Sets the list of rows for the fragment. 227 */ 228 public void setAdapter(ObjectAdapter adapter) { 229 mAdapter = adapter; 230 Presenter[] presenters = adapter.getPresenterSelector().getPresenters(); 231 if (presenters != null) { 232 for (int i = 0; i < presenters.length; i++) { 233 setupPresenter(presenters[i]); 234 } 235 } else { 236 Log.e(TAG, "PresenterSelector.getPresenters() not implemented"); 237 } 238 if (mRowsFragment != null) { 239 mRowsFragment.setAdapter(adapter); 240 } 241 } 242 243 /** 244 * Returns the list of rows. 245 */ 246 public ObjectAdapter getAdapter() { 247 return mAdapter; 248 } 249 250 /** 251 * Sets an item selection listener. 252 */ 253 public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) { 254 mExternalOnItemViewSelectedListener = listener; 255 } 256 257 /** 258 * Sets an item clicked listener. 259 */ 260 public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) { 261 if (mOnItemViewClickedListener != listener) { 262 mOnItemViewClickedListener = listener; 263 if (mRowsFragment != null) { 264 mRowsFragment.setOnItemViewClickedListener(listener); 265 } 266 } 267 } 268 269 /** 270 * Returns the item clicked listener. 271 */ 272 public BaseOnItemViewClickedListener getOnItemViewClickedListener() { 273 return mOnItemViewClickedListener; 274 } 275 276 @Override 277 public void onCreate(Bundle savedInstanceState) { 278 super.onCreate(savedInstanceState); 279 mContainerListAlignTop = 280 getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top); 281 282 Activity activity = getActivity(); 283 if (activity != null) { 284 Object transition = TransitionHelper.getEnterTransition(activity.getWindow()); 285 if (transition != null) { 286 mStartAndTransitionFlag |= PF_ENTER_TRANSITION_PENDING; 287 TransitionHelper.addTransitionListener(transition, mEnterTransitionListener); 288 } 289 transition = TransitionHelper.getReturnTransition(activity.getWindow()); 290 if (transition != null) { 291 TransitionHelper.addTransitionListener(transition, mReturnTransitionListener); 292 } 293 } 294 } 295 296 @Override 297 public View onCreateView(LayoutInflater inflater, ViewGroup container, 298 Bundle savedInstanceState) { 299 mRootView = (BrowseFrameLayout) inflater.inflate( 300 R.layout.lb_details_fragment, container, false); 301 mBackgroundView = mRootView.findViewById(R.id.details_background_view); 302 if (mBackgroundView != null) { 303 mBackgroundView.setBackground(mBackgroundDrawable); 304 } 305 mRowsFragment = (RowsFragment) getChildFragmentManager().findFragmentById( 306 R.id.details_rows_dock); 307 if (mRowsFragment == null) { 308 mRowsFragment = new RowsFragment(); 309 getChildFragmentManager().beginTransaction() 310 .replace(R.id.details_rows_dock, mRowsFragment).commit(); 311 } 312 installTitleView(inflater, mRootView, savedInstanceState); 313 mRowsFragment.setAdapter(mAdapter); 314 mRowsFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener); 315 mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener); 316 317 mSceneAfterEntranceTransition = TransitionHelper.createScene(mRootView, new Runnable() { 318 @Override 319 public void run() { 320 mRowsFragment.setEntranceTransitionState(true); 321 } 322 }); 323 324 setupDpadNavigation(); 325 326 if (Build.VERSION.SDK_INT >= 21) { 327 // Setup adapter listener to work with ParallaxTransition (>= API 21). 328 mRowsFragment.setExternalAdapterListener(new ItemBridgeAdapter.AdapterListener() { 329 @Override 330 public void onCreate(ItemBridgeAdapter.ViewHolder vh) { 331 if (mDetailsParallax != null && vh.getViewHolder() 332 instanceof FullWidthDetailsOverviewRowPresenter.ViewHolder) { 333 FullWidthDetailsOverviewRowPresenter.ViewHolder rowVh = 334 (FullWidthDetailsOverviewRowPresenter.ViewHolder) 335 vh.getViewHolder(); 336 rowVh.getOverviewView().setTag(R.id.lb_parallax_source, 337 mDetailsParallax); 338 } 339 } 340 }); 341 } 342 return mRootView; 343 } 344 345 /** 346 * @deprecated override {@link #onInflateTitleView(LayoutInflater,ViewGroup,Bundle)} instead. 347 */ 348 @Deprecated 349 protected View inflateTitle(LayoutInflater inflater, ViewGroup parent, 350 Bundle savedInstanceState) { 351 return super.onInflateTitleView(inflater, parent, savedInstanceState); 352 } 353 354 @Override 355 public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent, 356 Bundle savedInstanceState) { 357 return inflateTitle(inflater, parent, savedInstanceState); 358 } 359 360 void setVerticalGridViewLayout(VerticalGridView listview) { 361 // align the top edge of item to a fixed position 362 listview.setItemAlignmentOffset(-mContainerListAlignTop); 363 listview.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED); 364 listview.setWindowAlignmentOffset(0); 365 listview.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED); 366 listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE); 367 } 368 369 /** 370 * Called to setup each Presenter of Adapter passed in {@link #setAdapter(ObjectAdapter)}.Note 371 * that setup should only change the Presenter behavior that is meaningful in DetailsFragment. 372 * For example how a row is aligned in details Fragment. The default implementation invokes 373 * {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)} 374 * 375 */ 376 protected void setupPresenter(Presenter rowPresenter) { 377 if (rowPresenter instanceof FullWidthDetailsOverviewRowPresenter) { 378 setupDetailsOverviewRowPresenter((FullWidthDetailsOverviewRowPresenter) rowPresenter); 379 } 380 } 381 382 /** 383 * Called to setup {@link FullWidthDetailsOverviewRowPresenter}. The default implementation 384 * adds two alignment positions({@link ItemAlignmentFacet}) for ViewHolder of 385 * FullWidthDetailsOverviewRowPresenter to align in fragment. 386 */ 387 protected void setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter presenter) { 388 ItemAlignmentFacet facet = new ItemAlignmentFacet(); 389 // by default align details_frame to half window height 390 ItemAlignmentFacet.ItemAlignmentDef alignDef1 = new ItemAlignmentFacet.ItemAlignmentDef(); 391 alignDef1.setItemAlignmentViewId(R.id.details_frame); 392 alignDef1.setItemAlignmentOffset(- getResources() 393 .getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_actions)); 394 alignDef1.setItemAlignmentOffsetPercent(0); 395 // when description is selected, align details_frame to top edge 396 ItemAlignmentFacet.ItemAlignmentDef alignDef2 = new ItemAlignmentFacet.ItemAlignmentDef(); 397 alignDef2.setItemAlignmentViewId(R.id.details_frame); 398 alignDef2.setItemAlignmentFocusViewId(R.id.details_overview_description); 399 alignDef2.setItemAlignmentOffset(- getResources() 400 .getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_description)); 401 alignDef2.setItemAlignmentOffsetPercent(0); 402 ItemAlignmentFacet.ItemAlignmentDef[] defs = 403 new ItemAlignmentFacet.ItemAlignmentDef[] {alignDef1, alignDef2}; 404 facet.setAlignmentDefs(defs); 405 presenter.setFacet(ItemAlignmentFacet.class, facet); 406 } 407 408 VerticalGridView getVerticalGridView() { 409 return mRowsFragment == null ? null : mRowsFragment.getVerticalGridView(); 410 } 411 412 /** 413 * Gets embedded RowsFragment showing multiple rows for DetailsFragment. If view of 414 * DetailsFragment is not created, the method returns null. 415 * @return Embedded RowsFragment showing multiple rows for DetailsFragment. 416 */ 417 public RowsFragment getRowsFragment() { 418 return mRowsFragment; 419 } 420 421 /** 422 * Setup dimensions that are only meaningful when the child Fragments are inside 423 * DetailsFragment. 424 */ 425 private void setupChildFragmentLayout() { 426 setVerticalGridViewLayout(mRowsFragment.getVerticalGridView()); 427 } 428 429 /** 430 * Sets the selected row position with smooth animation. 431 */ 432 public void setSelectedPosition(int position) { 433 setSelectedPosition(position, true); 434 } 435 436 /** 437 * Sets the selected row position. 438 */ 439 public void setSelectedPosition(int position, boolean smooth) { 440 mSetSelectionRunnable.mPosition = position; 441 mSetSelectionRunnable.mSmooth = smooth; 442 if (getView() != null && getView().getHandler() != null) { 443 getView().getHandler().post(mSetSelectionRunnable); 444 } 445 } 446 447 /** 448 * This method asks DetailsFragmentBackgroundController to add a fragment for rendering video. 449 * In case the fragment is already there, it will return the existing one. The method must be 450 * called after calling super.onCreate(). App usually does not call this method directly. 451 * 452 * @return Fragment the added or restored fragment responsible for rendering video. 453 * @see DetailsFragmentBackgroundController#onCreateVideoFragment() 454 */ 455 final Fragment findOrCreateVideoFragment() { 456 Fragment fragment = getChildFragmentManager() 457 .findFragmentById(R.id.video_surface_container); 458 if (fragment == null && mDetailsBackgroundController != null) { 459 FragmentTransaction ft2 = getChildFragmentManager().beginTransaction(); 460 ft2.add(android.support.v17.leanback.R.id.video_surface_container, 461 fragment = mDetailsBackgroundController.onCreateVideoFragment()); 462 ft2.commit(); 463 } 464 mVideoFragment = fragment; 465 return mVideoFragment; 466 } 467 468 void onRowSelected(int selectedPosition, int selectedSubPosition) { 469 ObjectAdapter adapter = getAdapter(); 470 if (( mRowsFragment != null && mRowsFragment.getView() != null 471 && mRowsFragment.getView().hasFocus()) 472 && (adapter == null || adapter.size() == 0 473 || (getVerticalGridView().getSelectedPosition() == 0 474 && getVerticalGridView().getSelectedSubPosition() == 0))) { 475 showTitle(true); 476 } else { 477 showTitle(false); 478 } 479 if (adapter != null && adapter.size() > selectedPosition) { 480 final VerticalGridView gridView = getVerticalGridView(); 481 final int count = gridView.getChildCount(); 482 if (count > 0 && (mStartAndTransitionFlag & PF_ENTER_TRANSITION_PENDING) != 0) { 483 if (mWaitEnterTransitionTimeout == null) { 484 mWaitEnterTransitionTimeout = new WaitEnterTransitionTimeout(this); 485 } 486 } 487 for (int i = 0; i < count; i++) { 488 ItemBridgeAdapter.ViewHolder bridgeViewHolder = (ItemBridgeAdapter.ViewHolder) 489 gridView.getChildViewHolder(gridView.getChildAt(i)); 490 RowPresenter rowPresenter = (RowPresenter) bridgeViewHolder.getPresenter(); 491 onSetRowStatus(rowPresenter, 492 rowPresenter.getRowViewHolder(bridgeViewHolder.getViewHolder()), 493 bridgeViewHolder.getAdapterPosition(), 494 selectedPosition, selectedSubPosition); 495 } 496 } 497 } 498 499 void clearPendingEnterTransition() { 500 if ((mStartAndTransitionFlag & PF_ENTER_TRANSITION_PENDING) != 0) { 501 mStartAndTransitionFlag &= ~PF_ENTER_TRANSITION_PENDING; 502 dispatchOnStartAndTransitionFinished(); 503 } 504 } 505 506 void dispatchOnStartAndTransitionFinished() { 507 /** 508 * if onStart() was called and there is no pending enter transition or entrance transition. 509 */ 510 if ((mStartAndTransitionFlag & PF_PENDING_START) != 0 511 && (mStartAndTransitionFlag 512 & (PF_ENTER_TRANSITION_PENDING | PF_ENTRANCE_TRANSITION_PENDING)) == 0) { 513 mStartAndTransitionFlag &= ~PF_PENDING_START; 514 onSafeStart(); 515 } 516 } 517 518 /** 519 * Called when onStart and enter transition (postponed/none postponed) and entrance transition 520 * are all finished. 521 */ 522 @CallSuper 523 void onSafeStart() { 524 if (mDetailsBackgroundController != null) { 525 mDetailsBackgroundController.onStart(); 526 } 527 } 528 529 @CallSuper 530 void onReturnTransitionStart() { 531 if (mDetailsBackgroundController != null) { 532 // first disable parallax effect that auto-start PlaybackGlue. 533 boolean isVideoVisible = mDetailsBackgroundController.disableVideoParallax(); 534 // if video is not visible we can safely remove VideoFragment, 535 // otherwise let video playing during return transition. 536 if (!isVideoVisible && mVideoFragment != null) { 537 FragmentTransaction ft2 = getChildFragmentManager().beginTransaction(); 538 ft2.remove(mVideoFragment); 539 ft2.commit(); 540 mVideoFragment = null; 541 } 542 } 543 } 544 545 @Override 546 public void onStop() { 547 if (mDetailsBackgroundController != null) { 548 mDetailsBackgroundController.onStop(); 549 } 550 super.onStop(); 551 } 552 553 /** 554 * Called on every visible row to change view status when current selected row position 555 * or selected sub position changed. Subclass may override. The default 556 * implementation calls {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter, 557 * FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)} if presenter is 558 * instance of {@link FullWidthDetailsOverviewRowPresenter}. 559 * 560 * @param presenter The presenter used to create row ViewHolder. 561 * @param viewHolder The visible (attached) row ViewHolder, note that it may or may not 562 * be selected. 563 * @param adapterPosition The adapter position of viewHolder inside adapter. 564 * @param selectedPosition The adapter position of currently selected row. 565 * @param selectedSubPosition The sub position within currently selected row. This is used 566 * When a row has multiple alignment positions. 567 */ 568 protected void onSetRowStatus(RowPresenter presenter, RowPresenter.ViewHolder viewHolder, int 569 adapterPosition, int selectedPosition, int selectedSubPosition) { 570 if (presenter instanceof FullWidthDetailsOverviewRowPresenter) { 571 onSetDetailsOverviewRowStatus((FullWidthDetailsOverviewRowPresenter) presenter, 572 (FullWidthDetailsOverviewRowPresenter.ViewHolder) viewHolder, 573 adapterPosition, selectedPosition, selectedSubPosition); 574 } 575 } 576 577 /** 578 * Called to change DetailsOverviewRow view status when current selected row position 579 * or selected sub position changed. Subclass may override. The default 580 * implementation switches between three states based on the positions: 581 * {@link FullWidthDetailsOverviewRowPresenter#STATE_HALF}, 582 * {@link FullWidthDetailsOverviewRowPresenter#STATE_FULL} and 583 * {@link FullWidthDetailsOverviewRowPresenter#STATE_SMALL}. 584 * 585 * @param presenter The presenter used to create row ViewHolder. 586 * @param viewHolder The visible (attached) row ViewHolder, note that it may or may not 587 * be selected. 588 * @param adapterPosition The adapter position of viewHolder inside adapter. 589 * @param selectedPosition The adapter position of currently selected row. 590 * @param selectedSubPosition The sub position within currently selected row. This is used 591 * When a row has multiple alignment positions. 592 */ 593 protected void onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter presenter, 594 FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder, int adapterPosition, 595 int selectedPosition, int selectedSubPosition) { 596 if (selectedPosition > adapterPosition) { 597 presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF); 598 } else if (selectedPosition == adapterPosition && selectedSubPosition == 1) { 599 presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF); 600 } else if (selectedPosition == adapterPosition && selectedSubPosition == 0){ 601 presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_FULL); 602 } else { 603 presenter.setState(viewHolder, 604 FullWidthDetailsOverviewRowPresenter.STATE_SMALL); 605 } 606 } 607 608 @Override 609 public void onStart() { 610 super.onStart(); 611 612 mStartAndTransitionFlag |= PF_PENDING_START; 613 dispatchOnStartAndTransitionFinished(); 614 615 setupChildFragmentLayout(); 616 if (isEntranceTransitionEnabled()) { 617 mRowsFragment.setEntranceTransitionState(false); 618 } 619 if (mDetailsParallax != null) { 620 mDetailsParallax.setRecyclerView(mRowsFragment.getVerticalGridView()); 621 } 622 if (!getView().hasFocus()) { 623 mRowsFragment.getVerticalGridView().requestFocus(); 624 } 625 } 626 627 @Override 628 protected Object createEntranceTransition() { 629 return TransitionHelper.loadTransition(FragmentUtil.getContext(this), 630 R.transition.lb_details_enter_transition); 631 } 632 633 @Override 634 protected void runEntranceTransition(Object entranceTransition) { 635 TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition); 636 } 637 638 @Override 639 protected void onEntranceTransitionEnd() { 640 mStartAndTransitionFlag &= ~PF_ENTRANCE_TRANSITION_PENDING; 641 dispatchOnStartAndTransitionFinished(); 642 mRowsFragment.onTransitionEnd(); 643 } 644 645 @Override 646 protected void onEntranceTransitionPrepare() { 647 mStartAndTransitionFlag |= PF_ENTRANCE_TRANSITION_PENDING; 648 mRowsFragment.onTransitionPrepare(); 649 } 650 651 @Override 652 protected void onEntranceTransitionStart() { 653 mRowsFragment.onTransitionStart(); 654 } 655 656 /** 657 * Returns the {@link DetailsParallax} instance used by 658 * {@link DetailsFragmentBackgroundController} to configure parallax effect of background and 659 * control embedded video playback. App usually does not use this method directly. 660 * App may use this method for other custom parallax tasks. 661 * 662 * @return The DetailsParallax instance attached to the DetailsFragment. 663 */ 664 public DetailsParallax getParallax() { 665 if (mDetailsParallax == null) { 666 mDetailsParallax = new DetailsParallax(); 667 if (mRowsFragment != null && mRowsFragment.getView() != null) { 668 mDetailsParallax.setRecyclerView(mRowsFragment.getVerticalGridView()); 669 } 670 } 671 return mDetailsParallax; 672 } 673 674 /** 675 * Set background drawable shown below foreground rows UI and above 676 * {@link #findOrCreateVideoFragment()}. 677 * 678 * @see DetailsFragmentBackgroundController 679 */ 680 void setBackgroundDrawable(Drawable drawable) { 681 if (mBackgroundView != null) { 682 mBackgroundView.setBackground(drawable); 683 } 684 mBackgroundDrawable = drawable; 685 } 686 687 /** 688 * This method does the following 689 * <ul> 690 * <li>sets up focus search handling logic in the root view to enable transitioning between 691 * half screen/full screen/no video mode.</li> 692 * 693 * <li>Sets up the key listener in the root view to intercept events like UP/DOWN and 694 * transition to appropriate mode like half/full screen video.</li> 695 * </ul> 696 */ 697 void setupDpadNavigation() { 698 mRootView.setOnChildFocusListener(new BrowseFrameLayout.OnChildFocusListener() { 699 700 @Override 701 public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 702 return false; 703 } 704 705 @Override 706 public void onRequestChildFocus(View child, View focused) { 707 if (child != mRootView.getFocusedChild()) { 708 if (child.getId() == R.id.details_fragment_root) { 709 slideInGridView(); 710 showTitle(true); 711 } else if (child.getId() == R.id.video_surface_container) { 712 slideOutGridView(); 713 showTitle(false); 714 } else { 715 showTitle(true); 716 } 717 } 718 } 719 }); 720 mRootView.setOnFocusSearchListener(new BrowseFrameLayout.OnFocusSearchListener() { 721 @Override 722 public View onFocusSearch(View focused, int direction) { 723 if (mRowsFragment.getVerticalGridView() != null 724 && mRowsFragment.getVerticalGridView().hasFocus()) { 725 if (direction == View.FOCUS_UP) { 726 if (mVideoFragment != null && mVideoFragment.getView() != null) { 727 return mVideoFragment.getView(); 728 } else if (getTitleView() != null && getTitleView().hasFocusable()) { 729 return getTitleView(); 730 } 731 } 732 } else if (getTitleView() != null && getTitleView().hasFocus()) { 733 if (direction == View.FOCUS_DOWN) { 734 if (mRowsFragment.getVerticalGridView() != null) { 735 return mRowsFragment.getVerticalGridView(); 736 } 737 } 738 } 739 return focused; 740 } 741 }); 742 743 // If we press BACK on remote while in full screen video mode, we should 744 // transition back to half screen video playback mode. 745 mRootView.setOnDispatchKeyListener(new View.OnKeyListener() { 746 @Override 747 public boolean onKey(View v, int keyCode, KeyEvent event) { 748 // This is used to check if we are in full screen video mode. This is somewhat 749 // hacky and relies on the behavior of the video helper class to update the 750 // focusability of the video surface view. 751 if (mVideoFragment != null && mVideoFragment.getView() != null 752 && mVideoFragment.getView().hasFocus()) { 753 if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) { 754 getVerticalGridView().requestFocus(); 755 return true; 756 } 757 } 758 759 return false; 760 } 761 }); 762 } 763 764 /** 765 * Slides vertical grid view (displaying media item details) out of the screen from below. 766 */ 767 void slideOutGridView() { 768 if (getVerticalGridView() != null) { 769 getVerticalGridView().animateOut(); 770 } 771 } 772 773 void slideInGridView() { 774 if (getVerticalGridView() != null) { 775 getVerticalGridView().animateIn(); 776 } 777 } 778} 779