DetailsFragment.java revision ac5fe7c617c66850fff75a9fce9979c6e5674b0f
1// CHECKSTYLE:OFF Generated code 2/* This file is auto-generated from DetailsSupportFragment.java. DO NOT MODIFY. */ 3 4// CHECKSTYLE:OFF Generated code 5/* This file is auto-generated from DetailsFragment.java. DO NOT MODIFY. */ 6 7/* 8 * Copyright (C) 2014 The Android Open Source Project 9 * 10 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 11 * in compliance with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, software distributed under the License 16 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 17 * or implied. See the License for the specific language governing permissions and limitations under 18 * the License. 19 */ 20package androidx.leanback.app; 21 22import android.app.Activity; 23import android.app.Fragment; 24import android.app.FragmentTransaction; 25import android.graphics.Rect; 26import android.graphics.drawable.Drawable; 27import android.os.Build; 28import android.os.Bundle; 29import androidx.annotation.CallSuper; 30import androidx.leanback.R; 31import androidx.leanback.transition.TransitionHelper; 32import androidx.leanback.transition.TransitionListener; 33import androidx.leanback.util.StateMachine.Event; 34import androidx.leanback.util.StateMachine.State; 35import androidx.leanback.widget.BaseOnItemViewClickedListener; 36import androidx.leanback.widget.BaseOnItemViewSelectedListener; 37import androidx.leanback.widget.BrowseFrameLayout; 38import androidx.leanback.widget.DetailsParallax; 39import androidx.leanback.widget.FullWidthDetailsOverviewRowPresenter; 40import androidx.leanback.widget.ItemAlignmentFacet; 41import androidx.leanback.widget.ItemBridgeAdapter; 42import androidx.leanback.widget.ObjectAdapter; 43import androidx.leanback.widget.Presenter; 44import androidx.leanback.widget.PresenterSelector; 45import androidx.leanback.widget.RowPresenter; 46import androidx.leanback.widget.VerticalGridView; 47import android.util.Log; 48import android.view.KeyEvent; 49import android.view.LayoutInflater; 50import android.view.View; 51import android.view.ViewGroup; 52import android.view.Window; 53 54import java.lang.ref.WeakReference; 55 56/** 57 * A fragment for creating Leanback details screens. 58 * 59 * <p> 60 * A DetailsFragment renders the elements of its {@link ObjectAdapter} as a set 61 * of rows in a vertical list.The Adapter's {@link PresenterSelector} must maintain subclasses 62 * of {@link RowPresenter}. 63 * </p> 64 * 65 * When {@link FullWidthDetailsOverviewRowPresenter} is found in adapter, DetailsFragment will 66 * setup default behavior of the DetailsOverviewRow: 67 * <li> 68 * The alignment of FullWidthDetailsOverviewRowPresenter is setup in 69 * {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)}. 70 * </li> 71 * <li> 72 * The view status switching of FullWidthDetailsOverviewRowPresenter is done in 73 * {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter, 74 * FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)}. 75 * </li> 76 * 77 * <p> 78 * The recommended activity themes to use with a DetailsFragment are 79 * <li> 80 * {@link androidx.leanback.R.style#Theme_Leanback_Details} with activity 81 * shared element transition for {@link FullWidthDetailsOverviewRowPresenter}. 82 * </li> 83 * <li> 84 * {@link androidx.leanback.R.style#Theme_Leanback_Details_NoSharedElementTransition} 85 * if shared element transition is not needed, for example if first row is not rendered by 86 * {@link FullWidthDetailsOverviewRowPresenter}. 87 * </li> 88 * </p> 89 * 90 * <p> 91 * DetailsFragment can use {@link DetailsFragmentBackgroundController} to add a parallax drawable 92 * background and embedded video playing fragment. 93 * </p> 94 * @deprecated use {@link DetailsSupportFragment} 95 */ 96@Deprecated 97public class DetailsFragment extends BaseFragment { 98 static final String TAG = "DetailsFragment"; 99 static final boolean DEBUG = false; 100 101 final State STATE_SET_ENTRANCE_START_STATE = new State("STATE_SET_ENTRANCE_START_STATE") { 102 @Override 103 public void run() { 104 mRowsFragment.setEntranceTransitionState(false); 105 } 106 }; 107 108 final State STATE_ENTER_TRANSITION_INIT = new State("STATE_ENTER_TRANSIITON_INIT"); 109 110 void switchToVideoBeforeVideoFragmentCreated() { 111 // if the video fragment is not ready: immediately fade out covering drawable, 112 // hide title and mark mPendingFocusOnVideo and set focus on it later. 113 mDetailsBackgroundController.switchToVideoBeforeCreate(); 114 showTitle(false); 115 mPendingFocusOnVideo = true; 116 slideOutGridView(); 117 } 118 119 final State STATE_SWITCH_TO_VIDEO_IN_ON_CREATE = new State("STATE_SWITCH_TO_VIDEO_IN_ON_CREATE", 120 false, false) { 121 @Override 122 public void run() { 123 switchToVideoBeforeVideoFragmentCreated(); 124 } 125 }; 126 127 final State STATE_ENTER_TRANSITION_CANCEL = new State("STATE_ENTER_TRANSITION_CANCEL", 128 false, false) { 129 @Override 130 public void run() { 131 if (mWaitEnterTransitionTimeout != null) { 132 mWaitEnterTransitionTimeout.mRef.clear(); 133 } 134 // clear the activity enter/sharedElement transition, return transitions are kept. 135 // keep the return transitions and clear enter transition 136 if (getActivity() != null) { 137 Window window = getActivity().getWindow(); 138 Object returnTransition = TransitionHelper.getReturnTransition(window); 139 Object sharedReturnTransition = TransitionHelper 140 .getSharedElementReturnTransition(window); 141 TransitionHelper.setEnterTransition(window, null); 142 TransitionHelper.setSharedElementEnterTransition(window, null); 143 TransitionHelper.setReturnTransition(window, returnTransition); 144 TransitionHelper.setSharedElementReturnTransition(window, sharedReturnTransition); 145 } 146 } 147 }; 148 149 final State STATE_ENTER_TRANSITION_COMPLETE = new State("STATE_ENTER_TRANSIITON_COMPLETE", 150 true, false); 151 152 final State STATE_ENTER_TRANSITION_ADDLISTENER = new State("STATE_ENTER_TRANSITION_PENDING") { 153 @Override 154 public void run() { 155 Object transition = TransitionHelper.getEnterTransition(getActivity().getWindow()); 156 TransitionHelper.addTransitionListener(transition, mEnterTransitionListener); 157 } 158 }; 159 160 final State STATE_ENTER_TRANSITION_PENDING = new State("STATE_ENTER_TRANSITION_PENDING") { 161 @Override 162 public void run() { 163 if (mWaitEnterTransitionTimeout == null) { 164 new WaitEnterTransitionTimeout(DetailsFragment.this); 165 } 166 } 167 }; 168 169 /** 170 * Start this task when first DetailsOverviewRow is created, if there is no entrance transition 171 * started, it will clear PF_ENTRANCE_TRANSITION_PENDING. 172 */ 173 static class WaitEnterTransitionTimeout implements Runnable { 174 static final long WAIT_ENTERTRANSITION_START = 200; 175 176 final WeakReference<DetailsFragment> mRef; 177 178 WaitEnterTransitionTimeout(DetailsFragment f) { 179 mRef = new WeakReference<>(f); 180 f.getView().postDelayed(this, WAIT_ENTERTRANSITION_START); 181 } 182 183 @Override 184 public void run() { 185 DetailsFragment f = mRef.get(); 186 if (f != null) { 187 f.mStateMachine.fireEvent(f.EVT_ENTER_TRANSIITON_DONE); 188 } 189 } 190 } 191 192 final State STATE_ON_SAFE_START = new State("STATE_ON_SAFE_START") { 193 @Override 194 public void run() { 195 onSafeStart(); 196 } 197 }; 198 199 final Event EVT_ONSTART = new Event("onStart"); 200 201 final Event EVT_NO_ENTER_TRANSITION = new Event("EVT_NO_ENTER_TRANSITION"); 202 203 final Event EVT_DETAILS_ROW_LOADED = new Event("onFirstRowLoaded"); 204 205 final Event EVT_ENTER_TRANSIITON_DONE = new Event("onEnterTransitionDone"); 206 207 final Event EVT_SWITCH_TO_VIDEO = new Event("switchToVideo"); 208 209 @Override 210 void createStateMachineStates() { 211 super.createStateMachineStates(); 212 mStateMachine.addState(STATE_SET_ENTRANCE_START_STATE); 213 mStateMachine.addState(STATE_ON_SAFE_START); 214 mStateMachine.addState(STATE_SWITCH_TO_VIDEO_IN_ON_CREATE); 215 mStateMachine.addState(STATE_ENTER_TRANSITION_INIT); 216 mStateMachine.addState(STATE_ENTER_TRANSITION_ADDLISTENER); 217 mStateMachine.addState(STATE_ENTER_TRANSITION_CANCEL); 218 mStateMachine.addState(STATE_ENTER_TRANSITION_PENDING); 219 mStateMachine.addState(STATE_ENTER_TRANSITION_COMPLETE); 220 } 221 222 @Override 223 void createStateMachineTransitions() { 224 super.createStateMachineTransitions(); 225 /** 226 * Part 1: Processing enter transitions after fragment.onCreate 227 */ 228 mStateMachine.addTransition(STATE_START, STATE_ENTER_TRANSITION_INIT, EVT_ON_CREATE); 229 // if transition is not supported, skip to complete 230 mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_COMPLETE, 231 COND_TRANSITION_NOT_SUPPORTED); 232 // if transition is not set on Activity, skip to complete 233 mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_COMPLETE, 234 EVT_NO_ENTER_TRANSITION); 235 // if switchToVideo is called before EVT_ON_CREATEVIEW, clear enter transition and skip to 236 // complete. 237 mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_CANCEL, 238 EVT_SWITCH_TO_VIDEO); 239 mStateMachine.addTransition(STATE_ENTER_TRANSITION_CANCEL, STATE_ENTER_TRANSITION_COMPLETE); 240 // once after onCreateView, we cannot skip the enter transition, add a listener and wait 241 // it to finish 242 mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_ADDLISTENER, 243 EVT_ON_CREATEVIEW); 244 // when enter transition finishes, go to complete, however this might never happen if 245 // the activity is not giving transition options in startActivity, there is no API to query 246 // if this activity is started in a enter transition mode. So we rely on a timer below: 247 mStateMachine.addTransition(STATE_ENTER_TRANSITION_ADDLISTENER, 248 STATE_ENTER_TRANSITION_COMPLETE, EVT_ENTER_TRANSIITON_DONE); 249 // we are expecting app to start delayed enter transition shortly after details row is 250 // loaded, so create a timer and wait for enter transition start. 251 mStateMachine.addTransition(STATE_ENTER_TRANSITION_ADDLISTENER, 252 STATE_ENTER_TRANSITION_PENDING, EVT_DETAILS_ROW_LOADED); 253 // if enter transition not started in the timer, skip to DONE, this can be also true when 254 // startActivity is not giving transition option. 255 mStateMachine.addTransition(STATE_ENTER_TRANSITION_PENDING, STATE_ENTER_TRANSITION_COMPLETE, 256 EVT_ENTER_TRANSIITON_DONE); 257 258 /** 259 * Part 2: modification to the entrance transition defined in BaseFragment 260 */ 261 // Must finish enter transition before perform entrance transition. 262 mStateMachine.addTransition(STATE_ENTER_TRANSITION_COMPLETE, STATE_ENTRANCE_PERFORM); 263 // Calling switch to video would hide immediately and skip entrance transition 264 mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_SWITCH_TO_VIDEO_IN_ON_CREATE, 265 EVT_SWITCH_TO_VIDEO); 266 mStateMachine.addTransition(STATE_SWITCH_TO_VIDEO_IN_ON_CREATE, STATE_ENTRANCE_COMPLETE); 267 // if the entrance transition is skipped to complete by COND_TRANSITION_NOT_SUPPORTED, we 268 // still need to do the switchToVideo. 269 mStateMachine.addTransition(STATE_ENTRANCE_COMPLETE, STATE_SWITCH_TO_VIDEO_IN_ON_CREATE, 270 EVT_SWITCH_TO_VIDEO); 271 272 // for once the view is created in onStart and prepareEntranceTransition was called, we 273 // could setEntranceStartState: 274 mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED, 275 STATE_SET_ENTRANCE_START_STATE, EVT_ONSTART); 276 277 /** 278 * Part 3: onSafeStart() 279 */ 280 // for onSafeStart: the condition is onStart called, entrance transition complete 281 mStateMachine.addTransition(STATE_START, STATE_ON_SAFE_START, EVT_ONSTART); 282 mStateMachine.addTransition(STATE_ENTRANCE_COMPLETE, STATE_ON_SAFE_START); 283 mStateMachine.addTransition(STATE_ENTER_TRANSITION_COMPLETE, STATE_ON_SAFE_START); 284 } 285 286 private class SetSelectionRunnable implements Runnable { 287 int mPosition; 288 boolean mSmooth = true; 289 290 SetSelectionRunnable() { 291 } 292 293 @Override 294 public void run() { 295 if (mRowsFragment == null) { 296 return; 297 } 298 mRowsFragment.setSelectedPosition(mPosition, mSmooth); 299 } 300 } 301 302 TransitionListener mEnterTransitionListener = new TransitionListener() { 303 @Override 304 public void onTransitionStart(Object transition) { 305 if (mWaitEnterTransitionTimeout != null) { 306 // cancel task of WaitEnterTransitionTimeout, we will clearPendingEnterTransition 307 // when transition finishes. 308 mWaitEnterTransitionTimeout.mRef.clear(); 309 } 310 } 311 312 @Override 313 public void onTransitionCancel(Object transition) { 314 mStateMachine.fireEvent(EVT_ENTER_TRANSIITON_DONE); 315 } 316 317 @Override 318 public void onTransitionEnd(Object transition) { 319 mStateMachine.fireEvent(EVT_ENTER_TRANSIITON_DONE); 320 } 321 }; 322 323 TransitionListener mReturnTransitionListener = new TransitionListener() { 324 @Override 325 public void onTransitionStart(Object transition) { 326 onReturnTransitionStart(); 327 } 328 }; 329 330 BrowseFrameLayout mRootView; 331 View mBackgroundView; 332 Drawable mBackgroundDrawable; 333 Fragment mVideoFragment; 334 DetailsParallax mDetailsParallax; 335 RowsFragment mRowsFragment; 336 ObjectAdapter mAdapter; 337 int mContainerListAlignTop; 338 BaseOnItemViewSelectedListener mExternalOnItemViewSelectedListener; 339 BaseOnItemViewClickedListener mOnItemViewClickedListener; 340 DetailsFragmentBackgroundController mDetailsBackgroundController; 341 342 // A temporarily flag when switchToVideo() is called in onCreate(), if mPendingFocusOnVideo is 343 // true, we will focus to VideoFragment immediately after video fragment's view is created. 344 boolean mPendingFocusOnVideo = false; 345 346 WaitEnterTransitionTimeout mWaitEnterTransitionTimeout; 347 348 Object mSceneAfterEntranceTransition; 349 350 final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable(); 351 352 final BaseOnItemViewSelectedListener<Object> mOnItemViewSelectedListener = 353 new BaseOnItemViewSelectedListener<Object>() { 354 @Override 355 public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, 356 RowPresenter.ViewHolder rowViewHolder, Object row) { 357 int position = mRowsFragment.getVerticalGridView().getSelectedPosition(); 358 int subposition = mRowsFragment.getVerticalGridView().getSelectedSubPosition(); 359 if (DEBUG) Log.v(TAG, "row selected position " + position 360 + " subposition " + subposition); 361 onRowSelected(position, subposition); 362 if (mExternalOnItemViewSelectedListener != null) { 363 mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item, 364 rowViewHolder, row); 365 } 366 } 367 }; 368 369 /** 370 * Sets the list of rows for the fragment. 371 */ 372 public void setAdapter(ObjectAdapter adapter) { 373 mAdapter = adapter; 374 Presenter[] presenters = adapter.getPresenterSelector().getPresenters(); 375 if (presenters != null) { 376 for (int i = 0; i < presenters.length; i++) { 377 setupPresenter(presenters[i]); 378 } 379 } else { 380 Log.e(TAG, "PresenterSelector.getPresenters() not implemented"); 381 } 382 if (mRowsFragment != null) { 383 mRowsFragment.setAdapter(adapter); 384 } 385 } 386 387 /** 388 * Returns the list of rows. 389 */ 390 public ObjectAdapter getAdapter() { 391 return mAdapter; 392 } 393 394 /** 395 * Sets an item selection listener. 396 */ 397 public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) { 398 mExternalOnItemViewSelectedListener = listener; 399 } 400 401 /** 402 * Sets an item clicked listener. 403 */ 404 public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) { 405 if (mOnItemViewClickedListener != listener) { 406 mOnItemViewClickedListener = listener; 407 if (mRowsFragment != null) { 408 mRowsFragment.setOnItemViewClickedListener(listener); 409 } 410 } 411 } 412 413 /** 414 * Returns the item clicked listener. 415 */ 416 public BaseOnItemViewClickedListener getOnItemViewClickedListener() { 417 return mOnItemViewClickedListener; 418 } 419 420 @Override 421 public void onCreate(Bundle savedInstanceState) { 422 super.onCreate(savedInstanceState); 423 mContainerListAlignTop = 424 getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top); 425 426 Activity activity = getActivity(); 427 if (activity != null) { 428 Object transition = TransitionHelper.getEnterTransition(activity.getWindow()); 429 if (transition == null) { 430 mStateMachine.fireEvent(EVT_NO_ENTER_TRANSITION); 431 } 432 transition = TransitionHelper.getReturnTransition(activity.getWindow()); 433 if (transition != null) { 434 TransitionHelper.addTransitionListener(transition, mReturnTransitionListener); 435 } 436 } else { 437 mStateMachine.fireEvent(EVT_NO_ENTER_TRANSITION); 438 } 439 } 440 441 @Override 442 public View onCreateView(LayoutInflater inflater, ViewGroup container, 443 Bundle savedInstanceState) { 444 mRootView = (BrowseFrameLayout) inflater.inflate( 445 R.layout.lb_details_fragment, container, false); 446 mBackgroundView = mRootView.findViewById(R.id.details_background_view); 447 if (mBackgroundView != null) { 448 mBackgroundView.setBackground(mBackgroundDrawable); 449 } 450 mRowsFragment = (RowsFragment) getChildFragmentManager().findFragmentById( 451 R.id.details_rows_dock); 452 if (mRowsFragment == null) { 453 mRowsFragment = new RowsFragment(); 454 getChildFragmentManager().beginTransaction() 455 .replace(R.id.details_rows_dock, mRowsFragment).commit(); 456 } 457 installTitleView(inflater, mRootView, savedInstanceState); 458 mRowsFragment.setAdapter(mAdapter); 459 mRowsFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener); 460 mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener); 461 462 mSceneAfterEntranceTransition = TransitionHelper.createScene(mRootView, new Runnable() { 463 @Override 464 public void run() { 465 mRowsFragment.setEntranceTransitionState(true); 466 } 467 }); 468 469 setupDpadNavigation(); 470 471 if (Build.VERSION.SDK_INT >= 21) { 472 // Setup adapter listener to work with ParallaxTransition (>= API 21). 473 mRowsFragment.setExternalAdapterListener(new ItemBridgeAdapter.AdapterListener() { 474 @Override 475 public void onCreate(ItemBridgeAdapter.ViewHolder vh) { 476 if (mDetailsParallax != null && vh.getViewHolder() 477 instanceof FullWidthDetailsOverviewRowPresenter.ViewHolder) { 478 FullWidthDetailsOverviewRowPresenter.ViewHolder rowVh = 479 (FullWidthDetailsOverviewRowPresenter.ViewHolder) 480 vh.getViewHolder(); 481 rowVh.getOverviewView().setTag(R.id.lb_parallax_source, 482 mDetailsParallax); 483 } 484 } 485 }); 486 } 487 return mRootView; 488 } 489 490 /** 491 * @deprecated override {@link #onInflateTitleView(LayoutInflater,ViewGroup,Bundle)} instead. 492 */ 493 @Deprecated 494 protected View inflateTitle(LayoutInflater inflater, ViewGroup parent, 495 Bundle savedInstanceState) { 496 return super.onInflateTitleView(inflater, parent, savedInstanceState); 497 } 498 499 @Override 500 public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent, 501 Bundle savedInstanceState) { 502 return inflateTitle(inflater, parent, savedInstanceState); 503 } 504 505 void setVerticalGridViewLayout(VerticalGridView listview) { 506 // align the top edge of item to a fixed position 507 listview.setItemAlignmentOffset(-mContainerListAlignTop); 508 listview.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED); 509 listview.setWindowAlignmentOffset(0); 510 listview.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED); 511 listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE); 512 } 513 514 /** 515 * Called to setup each Presenter of Adapter passed in {@link #setAdapter(ObjectAdapter)}.Note 516 * that setup should only change the Presenter behavior that is meaningful in DetailsFragment. 517 * For example how a row is aligned in details Fragment. The default implementation invokes 518 * {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)} 519 * 520 */ 521 protected void setupPresenter(Presenter rowPresenter) { 522 if (rowPresenter instanceof FullWidthDetailsOverviewRowPresenter) { 523 setupDetailsOverviewRowPresenter((FullWidthDetailsOverviewRowPresenter) rowPresenter); 524 } 525 } 526 527 /** 528 * Called to setup {@link FullWidthDetailsOverviewRowPresenter}. The default implementation 529 * adds two alignment positions({@link ItemAlignmentFacet}) for ViewHolder of 530 * FullWidthDetailsOverviewRowPresenter to align in fragment. 531 */ 532 protected void setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter presenter) { 533 ItemAlignmentFacet facet = new ItemAlignmentFacet(); 534 // by default align details_frame to half window height 535 ItemAlignmentFacet.ItemAlignmentDef alignDef1 = new ItemAlignmentFacet.ItemAlignmentDef(); 536 alignDef1.setItemAlignmentViewId(R.id.details_frame); 537 alignDef1.setItemAlignmentOffset(- getResources() 538 .getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_actions)); 539 alignDef1.setItemAlignmentOffsetPercent(0); 540 // when description is selected, align details_frame to top edge 541 ItemAlignmentFacet.ItemAlignmentDef alignDef2 = new ItemAlignmentFacet.ItemAlignmentDef(); 542 alignDef2.setItemAlignmentViewId(R.id.details_frame); 543 alignDef2.setItemAlignmentFocusViewId(R.id.details_overview_description); 544 alignDef2.setItemAlignmentOffset(- getResources() 545 .getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_description)); 546 alignDef2.setItemAlignmentOffsetPercent(0); 547 ItemAlignmentFacet.ItemAlignmentDef[] defs = 548 new ItemAlignmentFacet.ItemAlignmentDef[] {alignDef1, alignDef2}; 549 facet.setAlignmentDefs(defs); 550 presenter.setFacet(ItemAlignmentFacet.class, facet); 551 } 552 553 VerticalGridView getVerticalGridView() { 554 return mRowsFragment == null ? null : mRowsFragment.getVerticalGridView(); 555 } 556 557 /** 558 * Gets embedded RowsFragment showing multiple rows for DetailsFragment. If view of 559 * DetailsFragment is not created, the method returns null. 560 * @return Embedded RowsFragment showing multiple rows for DetailsFragment. 561 */ 562 public RowsFragment getRowsFragment() { 563 return mRowsFragment; 564 } 565 566 /** 567 * Setup dimensions that are only meaningful when the child Fragments are inside 568 * DetailsFragment. 569 */ 570 private void setupChildFragmentLayout() { 571 setVerticalGridViewLayout(mRowsFragment.getVerticalGridView()); 572 } 573 574 /** 575 * Sets the selected row position with smooth animation. 576 */ 577 public void setSelectedPosition(int position) { 578 setSelectedPosition(position, true); 579 } 580 581 /** 582 * Sets the selected row position. 583 */ 584 public void setSelectedPosition(int position, boolean smooth) { 585 mSetSelectionRunnable.mPosition = position; 586 mSetSelectionRunnable.mSmooth = smooth; 587 if (getView() != null && getView().getHandler() != null) { 588 getView().getHandler().post(mSetSelectionRunnable); 589 } 590 } 591 592 void switchToVideo() { 593 if (mVideoFragment != null && mVideoFragment.getView() != null) { 594 mVideoFragment.getView().requestFocus(); 595 } else { 596 mStateMachine.fireEvent(EVT_SWITCH_TO_VIDEO); 597 } 598 } 599 600 void switchToRows() { 601 mPendingFocusOnVideo = false; 602 VerticalGridView verticalGridView = getVerticalGridView(); 603 if (verticalGridView != null && verticalGridView.getChildCount() > 0) { 604 verticalGridView.requestFocus(); 605 } 606 } 607 608 /** 609 * This method asks DetailsFragmentBackgroundController to add a fragment for rendering video. 610 * In case the fragment is already there, it will return the existing one. The method must be 611 * called after calling super.onCreate(). App usually does not call this method directly. 612 * 613 * @return Fragment the added or restored fragment responsible for rendering video. 614 * @see DetailsFragmentBackgroundController#onCreateVideoFragment() 615 */ 616 final Fragment findOrCreateVideoFragment() { 617 if (mVideoFragment != null) { 618 return mVideoFragment; 619 } 620 Fragment fragment = getChildFragmentManager() 621 .findFragmentById(R.id.video_surface_container); 622 if (fragment == null && mDetailsBackgroundController != null) { 623 FragmentTransaction ft2 = getChildFragmentManager().beginTransaction(); 624 ft2.add(androidx.leanback.R.id.video_surface_container, 625 fragment = mDetailsBackgroundController.onCreateVideoFragment()); 626 ft2.commit(); 627 if (mPendingFocusOnVideo) { 628 // wait next cycle for Fragment view created so we can focus on it. 629 // This is a bit hack eventually we will do commitNow() which get view immediately. 630 getView().post(new Runnable() { 631 @Override 632 public void run() { 633 if (getView() != null) { 634 switchToVideo(); 635 } 636 mPendingFocusOnVideo = false; 637 } 638 }); 639 } 640 } 641 mVideoFragment = fragment; 642 return mVideoFragment; 643 } 644 645 void onRowSelected(int selectedPosition, int selectedSubPosition) { 646 ObjectAdapter adapter = getAdapter(); 647 if (( mRowsFragment != null && mRowsFragment.getView() != null 648 && mRowsFragment.getView().hasFocus() && !mPendingFocusOnVideo) 649 && (adapter == null || adapter.size() == 0 650 || (getVerticalGridView().getSelectedPosition() == 0 651 && getVerticalGridView().getSelectedSubPosition() == 0))) { 652 showTitle(true); 653 } else { 654 showTitle(false); 655 } 656 if (adapter != null && adapter.size() > selectedPosition) { 657 final VerticalGridView gridView = getVerticalGridView(); 658 final int count = gridView.getChildCount(); 659 if (count > 0) { 660 mStateMachine.fireEvent(EVT_DETAILS_ROW_LOADED); 661 } 662 for (int i = 0; i < count; i++) { 663 ItemBridgeAdapter.ViewHolder bridgeViewHolder = (ItemBridgeAdapter.ViewHolder) 664 gridView.getChildViewHolder(gridView.getChildAt(i)); 665 RowPresenter rowPresenter = (RowPresenter) bridgeViewHolder.getPresenter(); 666 onSetRowStatus(rowPresenter, 667 rowPresenter.getRowViewHolder(bridgeViewHolder.getViewHolder()), 668 bridgeViewHolder.getAdapterPosition(), 669 selectedPosition, selectedSubPosition); 670 } 671 } 672 } 673 674 /** 675 * Called when onStart and enter transition (postponed/none postponed) and entrance transition 676 * are all finished. 677 */ 678 @CallSuper 679 void onSafeStart() { 680 if (mDetailsBackgroundController != null) { 681 mDetailsBackgroundController.onStart(); 682 } 683 } 684 685 @CallSuper 686 void onReturnTransitionStart() { 687 if (mDetailsBackgroundController != null) { 688 // first disable parallax effect that auto-start PlaybackGlue. 689 boolean isVideoVisible = mDetailsBackgroundController.disableVideoParallax(); 690 // if video is not visible we can safely remove VideoFragment, 691 // otherwise let video playing during return transition. 692 if (!isVideoVisible && mVideoFragment != null) { 693 FragmentTransaction ft2 = getChildFragmentManager().beginTransaction(); 694 ft2.remove(mVideoFragment); 695 ft2.commit(); 696 mVideoFragment = null; 697 } 698 } 699 } 700 701 @Override 702 public void onStop() { 703 if (mDetailsBackgroundController != null) { 704 mDetailsBackgroundController.onStop(); 705 } 706 super.onStop(); 707 } 708 709 /** 710 * Called on every visible row to change view status when current selected row position 711 * or selected sub position changed. Subclass may override. The default 712 * implementation calls {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter, 713 * FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)} if presenter is 714 * instance of {@link FullWidthDetailsOverviewRowPresenter}. 715 * 716 * @param presenter The presenter used to create row ViewHolder. 717 * @param viewHolder The visible (attached) row ViewHolder, note that it may or may not 718 * be selected. 719 * @param adapterPosition The adapter position of viewHolder inside adapter. 720 * @param selectedPosition The adapter position of currently selected row. 721 * @param selectedSubPosition The sub position within currently selected row. This is used 722 * When a row has multiple alignment positions. 723 */ 724 protected void onSetRowStatus(RowPresenter presenter, RowPresenter.ViewHolder viewHolder, int 725 adapterPosition, int selectedPosition, int selectedSubPosition) { 726 if (presenter instanceof FullWidthDetailsOverviewRowPresenter) { 727 onSetDetailsOverviewRowStatus((FullWidthDetailsOverviewRowPresenter) presenter, 728 (FullWidthDetailsOverviewRowPresenter.ViewHolder) viewHolder, 729 adapterPosition, selectedPosition, selectedSubPosition); 730 } 731 } 732 733 /** 734 * Called to change DetailsOverviewRow view status when current selected row position 735 * or selected sub position changed. Subclass may override. The default 736 * implementation switches between three states based on the positions: 737 * {@link FullWidthDetailsOverviewRowPresenter#STATE_HALF}, 738 * {@link FullWidthDetailsOverviewRowPresenter#STATE_FULL} and 739 * {@link FullWidthDetailsOverviewRowPresenter#STATE_SMALL}. 740 * 741 * @param presenter The presenter used to create row ViewHolder. 742 * @param viewHolder The visible (attached) row ViewHolder, note that it may or may not 743 * be selected. 744 * @param adapterPosition The adapter position of viewHolder inside adapter. 745 * @param selectedPosition The adapter position of currently selected row. 746 * @param selectedSubPosition The sub position within currently selected row. This is used 747 * When a row has multiple alignment positions. 748 */ 749 protected void onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter presenter, 750 FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder, int adapterPosition, 751 int selectedPosition, int selectedSubPosition) { 752 if (selectedPosition > adapterPosition) { 753 presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF); 754 } else if (selectedPosition == adapterPosition && selectedSubPosition == 1) { 755 presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF); 756 } else if (selectedPosition == adapterPosition && selectedSubPosition == 0){ 757 presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_FULL); 758 } else { 759 presenter.setState(viewHolder, 760 FullWidthDetailsOverviewRowPresenter.STATE_SMALL); 761 } 762 } 763 764 @Override 765 public void onStart() { 766 super.onStart(); 767 768 setupChildFragmentLayout(); 769 mStateMachine.fireEvent(EVT_ONSTART); 770 if (mDetailsParallax != null) { 771 mDetailsParallax.setRecyclerView(mRowsFragment.getVerticalGridView()); 772 } 773 if (mPendingFocusOnVideo) { 774 slideOutGridView(); 775 } else if (!getView().hasFocus()) { 776 mRowsFragment.getVerticalGridView().requestFocus(); 777 } 778 } 779 780 @Override 781 protected Object createEntranceTransition() { 782 return TransitionHelper.loadTransition(FragmentUtil.getContext(DetailsFragment.this), 783 R.transition.lb_details_enter_transition); 784 } 785 786 @Override 787 protected void runEntranceTransition(Object entranceTransition) { 788 TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition); 789 } 790 791 @Override 792 protected void onEntranceTransitionEnd() { 793 mRowsFragment.onTransitionEnd(); 794 } 795 796 @Override 797 protected void onEntranceTransitionPrepare() { 798 mRowsFragment.onTransitionPrepare(); 799 } 800 801 @Override 802 protected void onEntranceTransitionStart() { 803 mRowsFragment.onTransitionStart(); 804 } 805 806 /** 807 * Returns the {@link DetailsParallax} instance used by 808 * {@link DetailsFragmentBackgroundController} to configure parallax effect of background and 809 * control embedded video playback. App usually does not use this method directly. 810 * App may use this method for other custom parallax tasks. 811 * 812 * @return The DetailsParallax instance attached to the DetailsFragment. 813 */ 814 public DetailsParallax getParallax() { 815 if (mDetailsParallax == null) { 816 mDetailsParallax = new DetailsParallax(); 817 if (mRowsFragment != null && mRowsFragment.getView() != null) { 818 mDetailsParallax.setRecyclerView(mRowsFragment.getVerticalGridView()); 819 } 820 } 821 return mDetailsParallax; 822 } 823 824 /** 825 * Set background drawable shown below foreground rows UI and above 826 * {@link #findOrCreateVideoFragment()}. 827 * 828 * @see DetailsFragmentBackgroundController 829 */ 830 void setBackgroundDrawable(Drawable drawable) { 831 if (mBackgroundView != null) { 832 mBackgroundView.setBackground(drawable); 833 } 834 mBackgroundDrawable = drawable; 835 } 836 837 /** 838 * This method does the following 839 * <ul> 840 * <li>sets up focus search handling logic in the root view to enable transitioning between 841 * half screen/full screen/no video mode.</li> 842 * 843 * <li>Sets up the key listener in the root view to intercept events like UP/DOWN and 844 * transition to appropriate mode like half/full screen video.</li> 845 * </ul> 846 */ 847 void setupDpadNavigation() { 848 mRootView.setOnChildFocusListener(new BrowseFrameLayout.OnChildFocusListener() { 849 850 @Override 851 public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 852 return false; 853 } 854 855 @Override 856 public void onRequestChildFocus(View child, View focused) { 857 if (child != mRootView.getFocusedChild()) { 858 if (child.getId() == R.id.details_fragment_root) { 859 if (!mPendingFocusOnVideo) { 860 slideInGridView(); 861 showTitle(true); 862 } 863 } else if (child.getId() == R.id.video_surface_container) { 864 slideOutGridView(); 865 showTitle(false); 866 } else { 867 showTitle(true); 868 } 869 } 870 } 871 }); 872 mRootView.setOnFocusSearchListener(new BrowseFrameLayout.OnFocusSearchListener() { 873 @Override 874 public View onFocusSearch(View focused, int direction) { 875 if (mRowsFragment.getVerticalGridView() != null 876 && mRowsFragment.getVerticalGridView().hasFocus()) { 877 if (direction == View.FOCUS_UP) { 878 if (mDetailsBackgroundController != null 879 && mDetailsBackgroundController.canNavigateToVideoFragment() 880 && mVideoFragment != null && mVideoFragment.getView() != null) { 881 return mVideoFragment.getView(); 882 } else if (getTitleView() != null && getTitleView().hasFocusable()) { 883 return getTitleView(); 884 } 885 } 886 } else if (getTitleView() != null && getTitleView().hasFocus()) { 887 if (direction == View.FOCUS_DOWN) { 888 if (mRowsFragment.getVerticalGridView() != null) { 889 return mRowsFragment.getVerticalGridView(); 890 } 891 } 892 } 893 return focused; 894 } 895 }); 896 897 // If we press BACK on remote while in full screen video mode, we should 898 // transition back to half screen video playback mode. 899 mRootView.setOnDispatchKeyListener(new View.OnKeyListener() { 900 @Override 901 public boolean onKey(View v, int keyCode, KeyEvent event) { 902 // This is used to check if we are in full screen video mode. This is somewhat 903 // hacky and relies on the behavior of the video helper class to update the 904 // focusability of the video surface view. 905 if (mVideoFragment != null && mVideoFragment.getView() != null 906 && mVideoFragment.getView().hasFocus()) { 907 if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) { 908 if (getVerticalGridView().getChildCount() > 0) { 909 getVerticalGridView().requestFocus(); 910 return true; 911 } 912 } 913 } 914 915 return false; 916 } 917 }); 918 } 919 920 /** 921 * Slides vertical grid view (displaying media item details) out of the screen from below. 922 */ 923 void slideOutGridView() { 924 if (getVerticalGridView() != null) { 925 getVerticalGridView().animateOut(); 926 } 927 } 928 929 void slideInGridView() { 930 if (getVerticalGridView() != null) { 931 getVerticalGridView().animateIn(); 932 } 933 } 934} 935