BrowseFragment.java revision 731066a59e10ddc7bb6c95d0b91b3e0e11e10396
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.support.v17.leanback.R; 17import android.support.v17.leanback.widget.HorizontalGridView; 18import android.support.v17.leanback.widget.Presenter; 19import android.support.v17.leanback.widget.PresenterSelector; 20import android.support.v17.leanback.widget.TitleView; 21import android.support.v17.leanback.widget.VerticalGridView; 22import android.support.v17.leanback.widget.Row; 23import android.support.v17.leanback.widget.ObjectAdapter; 24import android.support.v17.leanback.widget.OnItemSelectedListener; 25import android.support.v17.leanback.widget.OnItemClickedListener; 26import android.support.v17.leanback.widget.SearchOrbView; 27import android.util.Log; 28import android.app.Activity; 29import android.app.Fragment; 30import android.app.FragmentManager; 31import android.app.FragmentManager.BackStackEntry; 32import android.content.res.TypedArray; 33import android.os.Bundle; 34import android.view.LayoutInflater; 35import android.view.View; 36import android.view.View.OnClickListener; 37import android.view.ViewGroup; 38import android.view.ViewGroup.MarginLayoutParams; 39import android.graphics.Color; 40import android.graphics.drawable.Drawable; 41 42import static android.support.v7.widget.RecyclerView.NO_POSITION; 43 44/** 45 * Wrapper fragment for leanback browse screens. Composed of a 46 * RowsFragment and a HeadersFragment. 47 * <p> 48 * The fragment comes with default back key support to show headers. 49 * For app customized {@link Activity#onBackPressed()}, app must disable 50 * BrowseFragment's default back key support by calling 51 * {@link #setHeadersTransitionOnBackEnabled(boolean)} with false and use 52 * {@link BrowseFragment.BrowseTransitionListener} and {@link #startHeadersTransition(boolean)}. 53 */ 54public class BrowseFragment extends Fragment { 55 56 @Deprecated 57 public static class Params { 58 private String mTitle; 59 private Drawable mBadgeDrawable; 60 private int mHeadersState = HEADERS_ENABLED; 61 62 /** 63 * Sets the badge image. 64 */ 65 public void setBadgeImage(Drawable drawable) { 66 mBadgeDrawable = drawable; 67 } 68 69 /** 70 * Returns the badge image. 71 */ 72 public Drawable getBadgeImage() { 73 return mBadgeDrawable; 74 } 75 76 /** 77 * Sets a title for the browse fragment. 78 */ 79 public void setTitle(String title) { 80 mTitle = title; 81 } 82 83 /** 84 * Returns the title for the browse fragment. 85 */ 86 public String getTitle() { 87 return mTitle; 88 } 89 90 /** 91 * Sets the state for the headers column in the browse fragment. 92 */ 93 public void setHeadersState(int headersState) { 94 if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) { 95 Log.e(TAG, "Invalid headers state: " + headersState 96 + ", default to enabled and shown."); 97 mHeadersState = HEADERS_ENABLED; 98 } else { 99 mHeadersState = headersState; 100 } 101 } 102 103 /** 104 * Returns the state for the headers column in the browse fragment. 105 */ 106 public int getHeadersState() { 107 return mHeadersState; 108 } 109 } 110 111 final class BackStackListener implements FragmentManager.OnBackStackChangedListener { 112 int mLastEntryCount; 113 int mIndexOfHeadersBackStack; 114 115 BackStackListener() { 116 reset(); 117 } 118 119 void reset() { 120 mLastEntryCount = getFragmentManager().getBackStackEntryCount(); 121 mIndexOfHeadersBackStack = -1; 122 } 123 124 @Override 125 public void onBackStackChanged() { 126 int count = getFragmentManager().getBackStackEntryCount(); 127 // if backstack is growing and last pushed entry is "headers" backstack, 128 // remember the index of the entry. 129 if (count > mLastEntryCount) { 130 BackStackEntry entry = getFragmentManager().getBackStackEntryAt(count - 1); 131 if (mWithHeadersBackStackName.equals(entry.getName())) { 132 mIndexOfHeadersBackStack = count - 1; 133 } 134 } else if (count < mLastEntryCount) { 135 // if popped "headers" backstack, initiate the show header transition if needed 136 if (mIndexOfHeadersBackStack >= count) { 137 if (!mShowingHeaders) { 138 startHeadersTransitionInternal(true); 139 } 140 } 141 } 142 mLastEntryCount = count; 143 } 144 } 145 146 /** 147 * Listener for browse transitions. 148 */ 149 public static class BrowseTransitionListener { 150 /** 151 * Callback when headers transition starts. 152 */ 153 public void onHeadersTransitionStart(boolean withHeaders) { 154 } 155 /** 156 * Callback when headers transition stops. 157 */ 158 public void onHeadersTransitionStop(boolean withHeaders) { 159 } 160 } 161 162 private static final String TAG = "BrowseFragment"; 163 164 private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_"; 165 166 private static boolean DEBUG = false; 167 168 /** The headers fragment is enabled and shown by default. */ 169 public static final int HEADERS_ENABLED = 1; 170 171 /** The headers fragment is enabled and hidden by default. */ 172 public static final int HEADERS_HIDDEN = 2; 173 174 /** The headers fragment is disabled and will never be shown. */ 175 public static final int HEADERS_DISABLED = 3; 176 177 private static final float SLIDE_DISTANCE_FACTOR = 2; 178 179 private RowsFragment mRowsFragment; 180 private HeadersFragment mHeadersFragment; 181 182 private ObjectAdapter mAdapter; 183 184 // TODO: remove Params 185 private Params mParams; 186 187 private String mTitle; 188 private Drawable mBadgeDrawable; 189 private int mHeadersState = HEADERS_ENABLED; 190 private int mBrandColor = Color.TRANSPARENT; 191 private boolean mBrandColorSet; 192 193 private BrowseFrameLayout mBrowseFrame; 194 private TitleView mTitleView; 195 private boolean mShowingTitle = true; 196 private boolean mHeadersBackStackEnabled = true; 197 private String mWithHeadersBackStackName; 198 private boolean mShowingHeaders = true; 199 private boolean mCanShowHeaders = true; 200 private int mContainerListMarginLeft; 201 private int mContainerListAlignTop; 202 private int mSearchAffordanceColor; 203 private boolean mSearchAffordanceColorSet; 204 private OnItemSelectedListener mExternalOnItemSelectedListener; 205 private OnClickListener mExternalOnSearchClickedListener; 206 private OnItemClickedListener mOnItemClickedListener; 207 private int mSelectedPosition = -1; 208 209 private PresenterSelector mHeaderPresenterSelector; 210 211 // transition related: 212 private static TransitionHelper sTransitionHelper = TransitionHelper.getInstance(); 213 private int mReparentHeaderId = View.generateViewId(); 214 private Object mSceneWithTitle; 215 private Object mSceneWithoutTitle; 216 private Object mSceneWithHeaders; 217 private Object mSceneWithoutHeaders; 218 private Object mTitleUpTransition; 219 private Object mTitleDownTransition; 220 private Object mHeadersTransition; 221 private int mHeadersTransitionStartDelay; 222 private int mHeadersTransitionDuration; 223 private BackStackListener mBackStackChangedListener; 224 private BrowseTransitionListener mBrowseTransitionListener; 225 226 private static final String ARG_TITLE = BrowseFragment.class.getCanonicalName() + ".title"; 227 private static final String ARG_BADGE_URI = BrowseFragment.class.getCanonicalName() + ".badge"; 228 private static final String ARG_HEADERS_STATE = 229 BrowseFragment.class.getCanonicalName() + ".headersState"; 230 231 /** 232 * Create arguments for a browse fragment. 233 * @deprecated Use {@link #createArgs(Bundle args, String title, int headersState)}. 234 */ 235 @Deprecated 236 public static Bundle createArgs(Bundle args, String title, String badgeUri) { 237 return createArgs(args, title, HEADERS_ENABLED); 238 } 239 240 /** 241 * Create arguments for a browse fragment. 242 * @deprecated Use {@link #createArgs(Bundle args, String title, int headersState)}. 243 */ 244 @Deprecated 245 public static Bundle createArgs(Bundle args, String title, String badgeUri, int headersState) { 246 return createArgs(args, title, headersState); 247 } 248 249 /** 250 * Create arguments for a browse fragment. 251 */ 252 public static Bundle createArgs(Bundle args, String title, int headersState) { 253 if (args == null) { 254 args = new Bundle(); 255 } 256 args.putString(ARG_TITLE, title); 257 args.putInt(ARG_HEADERS_STATE, headersState); 258 return args; 259 } 260 261 /** 262 * Set browse parameters. 263 * @deprecated Call methods on the fragment directly. 264 */ 265 @Deprecated 266 public void setBrowseParams(Params params) { 267 mParams = params; 268 setBadgeDrawable(params.mBadgeDrawable); 269 setTitle(params.mTitle); 270 setHeadersState(params.mHeadersState); 271 } 272 273 /** 274 * Returns browse parameters. 275 * @deprecated Call methods on the fragment directly. 276 */ 277 @Deprecated 278 public Params getBrowseParams() { 279 return mParams; 280 } 281 282 /** 283 * Sets the brand color for the browse fragment. 284 */ 285 public void setBrandColor(int color) { 286 mBrandColor = color; 287 mBrandColorSet = true; 288 289 if (mHeadersFragment != null) { 290 mHeadersFragment.setBackgroundColor(mBrandColor); 291 } 292 } 293 294 /** 295 * Returns the brand color for the browse fragment. 296 * The default is transparent. 297 */ 298 public int getBrandColor() { 299 return mBrandColor; 300 } 301 302 /** 303 * Sets the list of rows for the fragment. 304 */ 305 public void setAdapter(ObjectAdapter adapter) { 306 mAdapter = adapter; 307 if (mRowsFragment != null) { 308 mRowsFragment.setAdapter(adapter); 309 mHeadersFragment.setAdapter(adapter); 310 } 311 } 312 313 /** 314 * Returns the list of rows. 315 */ 316 public ObjectAdapter getAdapter() { 317 return mAdapter; 318 } 319 320 /** 321 * Sets an item selection listener. 322 */ 323 public void setOnItemSelectedListener(OnItemSelectedListener listener) { 324 mExternalOnItemSelectedListener = listener; 325 } 326 327 /** 328 * Sets an item clicked listener on the fragment. 329 * OnItemClickedListener will override {@link View.OnClickListener} that 330 * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}. 331 * So in general, developer should choose one of the listeners but not both. 332 */ 333 public void setOnItemClickedListener(OnItemClickedListener listener) { 334 mOnItemClickedListener = listener; 335 if (mRowsFragment != null) { 336 mRowsFragment.setOnItemClickedListener(listener); 337 } 338 } 339 340 /** 341 * Returns the item Clicked listener. 342 */ 343 public OnItemClickedListener getOnItemClickedListener() { 344 return mOnItemClickedListener; 345 } 346 347 /** 348 * Sets a click listener for the search affordance. 349 * 350 * The presence of a listener will change the visibility of the search affordance in the 351 * title area. When set to non-null the title area will contain a call to search action. 352 * 353 * The listener onClick method will be invoked when the user click on the search action. 354 * 355 * @param listener The listener. 356 */ 357 public void setOnSearchClickedListener(View.OnClickListener listener) { 358 mExternalOnSearchClickedListener = listener; 359 if (mTitleView != null) { 360 mTitleView.setOnSearchClickedListener(listener); 361 } 362 } 363 364 /** 365 * Sets the color used to draw the search affordance. 366 */ 367 public void setSearchAffordanceColor(int color) { 368 mSearchAffordanceColor = color; 369 mSearchAffordanceColorSet = true; 370 if (mTitleView != null) { 371 mTitleView.setSearchAffordanceColor(mSearchAffordanceColor); 372 } 373 } 374 375 /** 376 * Returns the color used to draw the search affordance. 377 * Can be called only after an activity has been attached. 378 */ 379 public int getSearchAffordanceColor() { 380 if (mSearchAffordanceColorSet) { 381 return mSearchAffordanceColor; 382 } 383 if (mTitleView == null) { 384 throw new IllegalStateException("Fragment views not yet created"); 385 } 386 return mTitleView.getSearchAffordanceColor(); 387 } 388 389 /** 390 * Start headers transition. 391 */ 392 public void startHeadersTransition(boolean withHeaders) { 393 if (!mCanShowHeaders) { 394 throw new IllegalStateException("Cannot start headers transition"); 395 } 396 if (isInHeadersTransition() || mShowingHeaders == withHeaders) { 397 return; 398 } 399 startHeadersTransitionInternal(withHeaders); 400 } 401 402 /** 403 * Returns true if headers transition is currently running. 404 */ 405 public boolean isInHeadersTransition() { 406 return mHeadersTransition != null; 407 } 408 409 /** 410 * Returns true if headers is showing. 411 */ 412 public boolean isShowingHeaders() { 413 return mShowingHeaders; 414 } 415 416 /** 417 * Set listener for browse fragment transitions. 418 */ 419 public void setBrowseTransitionListener(BrowseTransitionListener listener) { 420 mBrowseTransitionListener = listener; 421 } 422 423 private void startHeadersTransitionInternal(boolean withHeaders) { 424 mShowingHeaders = withHeaders; 425 mRowsFragment.onTransitionStart(); 426 mHeadersFragment.onTransitionStart(); 427 createHeadersTransition(); 428 if (mBrowseTransitionListener != null) { 429 mBrowseTransitionListener.onHeadersTransitionStart(withHeaders); 430 } 431 sTransitionHelper.runTransition(withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders, 432 mHeadersTransition); 433 if (mHeadersBackStackEnabled) { 434 if (!withHeaders) { 435 getFragmentManager().beginTransaction() 436 .addToBackStack(mWithHeadersBackStackName).commit(); 437 } else { 438 int count = getFragmentManager().getBackStackEntryCount(); 439 if (count > 0) { 440 BackStackEntry entry = getFragmentManager().getBackStackEntryAt(count - 1); 441 if (mWithHeadersBackStackName.equals(entry.getName())) { 442 getFragmentManager().popBackStack(); 443 } 444 } 445 } 446 } 447 } 448 449 private boolean isVerticalScrolling() { 450 // don't run transition 451 return mHeadersFragment.getVerticalGridView().getScrollState() 452 != HorizontalGridView.SCROLL_STATE_IDLE 453 || mRowsFragment.getVerticalGridView().getScrollState() 454 != HorizontalGridView.SCROLL_STATE_IDLE; 455 } 456 457 private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener = 458 new BrowseFrameLayout.OnFocusSearchListener() { 459 @Override 460 public View onFocusSearch(View focused, int direction) { 461 // If headers fragment is disabled, just return null. 462 if (!mCanShowHeaders) return null; 463 464 final View searchOrbView = mTitleView.getSearchAffordanceView(); 465 // if headers is running transition, focus stays 466 if (isInHeadersTransition()) return focused; 467 if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction); 468 if (direction == View.FOCUS_LEFT) { 469 if (isVerticalScrolling() || mShowingHeaders) { 470 return focused; 471 } 472 return mHeadersFragment.getVerticalGridView(); 473 } else if (direction == View.FOCUS_RIGHT) { 474 if (isVerticalScrolling() || !mShowingHeaders) { 475 return focused; 476 } 477 return mRowsFragment.getVerticalGridView(); 478 } else if (focused == searchOrbView && direction == View.FOCUS_DOWN) { 479 return mShowingHeaders ? mHeadersFragment.getVerticalGridView() : 480 mRowsFragment.getVerticalGridView(); 481 482 } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE 483 && direction == View.FOCUS_UP) { 484 return searchOrbView; 485 486 } else { 487 return null; 488 } 489 } 490 }; 491 492 private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener = 493 new BrowseFrameLayout.OnChildFocusListener() { 494 @Override 495 public void onRequestChildFocus(View child, View focused) { 496 int childId = child.getId(); 497 if (!mCanShowHeaders || isInHeadersTransition()) return; 498 if (childId == R.id.browse_container_dock && mShowingHeaders) { 499 startHeadersTransitionInternal(false); 500 } else if (childId == R.id.browse_headers_dock && !mShowingHeaders) { 501 startHeadersTransitionInternal(true); 502 } 503 } 504 }; 505 506 @Override 507 public void onCreate(Bundle savedInstanceState) { 508 super.onCreate(savedInstanceState); 509 TypedArray ta = getActivity().obtainStyledAttributes(R.styleable.LeanbackTheme); 510 mContainerListMarginLeft = (int) ta.getDimension( 511 R.styleable.LeanbackTheme_browseRowsMarginStart, 0); 512 mContainerListAlignTop = (int) ta.getDimension( 513 R.styleable.LeanbackTheme_browseRowsMarginTop, 0); 514 ta.recycle(); 515 516 mHeadersTransitionStartDelay = getResources() 517 .getInteger(R.integer.lb_browse_headers_transition_delay); 518 mHeadersTransitionDuration = getResources() 519 .getInteger(R.integer.lb_browse_headers_transition_duration); 520 521 readArguments(getArguments()); 522 523 if (mCanShowHeaders && mHeadersBackStackEnabled) { 524 mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this; 525 mBackStackChangedListener = new BackStackListener(); 526 getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener); 527 if (!mShowingHeaders) { 528 getFragmentManager().beginTransaction() 529 .addToBackStack(mWithHeadersBackStackName).commit(); 530 } 531 } 532 533 } 534 535 @Override 536 public View onCreateView(LayoutInflater inflater, ViewGroup container, 537 Bundle savedInstanceState) { 538 if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) { 539 mRowsFragment = new RowsFragment(); 540 mHeadersFragment = new HeadersFragment(); 541 getChildFragmentManager().beginTransaction() 542 .replace(R.id.browse_headers_dock, mHeadersFragment) 543 .replace(R.id.browse_container_dock, mRowsFragment).commit(); 544 } else { 545 mHeadersFragment = (HeadersFragment) getChildFragmentManager() 546 .findFragmentById(R.id.browse_headers_dock); 547 mRowsFragment = (RowsFragment) getChildFragmentManager() 548 .findFragmentById(R.id.browse_container_dock); 549 } 550 551 mHeadersFragment.setHeadersGone(!mCanShowHeaders); 552 553 mRowsFragment.setAdapter(mAdapter); 554 if (mHeaderPresenterSelector != null) { 555 mHeadersFragment.setPresenterSelector(mHeaderPresenterSelector); 556 } 557 mHeadersFragment.setAdapter(mAdapter); 558 559 mRowsFragment.setOnItemSelectedListener(mRowSelectedListener); 560 mHeadersFragment.setOnItemSelectedListener(mHeaderSelectedListener); 561 mHeadersFragment.setOnHeaderClickedListener(mHeaderClickedListener); 562 mRowsFragment.setOnItemClickedListener(mOnItemClickedListener); 563 564 View root = inflater.inflate(R.layout.lb_browse_fragment, container, false); 565 566 mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame); 567 mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener); 568 mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener); 569 570 mTitleView = (TitleView) root.findViewById(R.id.browse_title_group); 571 mTitleView.setTitle(mTitle); 572 mTitleView.setBadgeDrawable(mBadgeDrawable); 573 if (mSearchAffordanceColorSet) { 574 mTitleView.setSearchAffordanceColor(mSearchAffordanceColor); 575 } 576 if (mExternalOnSearchClickedListener != null) { 577 mTitleView.setOnSearchClickedListener(mExternalOnSearchClickedListener); 578 } 579 580 if (mBrandColorSet) { 581 mHeadersFragment.setBackgroundColor(mBrandColor); 582 } 583 584 mSceneWithTitle = sTransitionHelper.createScene(mBrowseFrame, new Runnable() { 585 @Override 586 public void run() { 587 TitleTransitionHelper.showTitle(mTitleView, true); 588 } 589 }); 590 mSceneWithoutTitle = sTransitionHelper.createScene(mBrowseFrame, new Runnable() { 591 @Override 592 public void run() { 593 TitleTransitionHelper.showTitle(mTitleView, false); 594 } 595 }); 596 mSceneWithHeaders = sTransitionHelper.createScene(mBrowseFrame, new Runnable() { 597 @Override 598 public void run() { 599 showHeaders(true); 600 } 601 }); 602 mSceneWithoutHeaders = sTransitionHelper.createScene(mBrowseFrame, new Runnable() { 603 @Override 604 public void run() { 605 showHeaders(false); 606 } 607 }); 608 mTitleUpTransition = TitleTransitionHelper.createTransitionTitleUp(sTransitionHelper); 609 mTitleDownTransition = TitleTransitionHelper.createTransitionTitleDown(sTransitionHelper); 610 611 sTransitionHelper.excludeChildren(mTitleUpTransition, R.id.browse_headers, true); 612 sTransitionHelper.excludeChildren(mTitleDownTransition, R.id.browse_headers, true); 613 sTransitionHelper.excludeChildren(mTitleUpTransition, R.id.container_list, true); 614 sTransitionHelper.excludeChildren(mTitleDownTransition, R.id.container_list, true); 615 616 return root; 617 } 618 619 private void createHeadersTransition() { 620 mHeadersTransition = sTransitionHelper.createTransitionSet(false); 621 sTransitionHelper.excludeChildren(mHeadersTransition, R.id.browse_title_group, true); 622 Object changeBounds = sTransitionHelper.createChangeBounds(false); 623 Object fadeIn = sTransitionHelper.createFadeTransition(TransitionHelper.FADE_IN); 624 Object fadeOut = sTransitionHelper.createFadeTransition(TransitionHelper.FADE_OUT); 625 626 sTransitionHelper.setDuration(fadeOut, mHeadersTransitionDuration); 627 sTransitionHelper.addTransition(mHeadersTransition, fadeOut); 628 if (mShowingHeaders) { 629 sTransitionHelper.setStartDelay(changeBounds, mHeadersTransitionStartDelay); 630 } 631 sTransitionHelper.setDuration(changeBounds, mHeadersTransitionDuration); 632 sTransitionHelper.addTransition(mHeadersTransition, changeBounds); 633 sTransitionHelper.setDuration(fadeIn, mHeadersTransitionDuration); 634 sTransitionHelper.setStartDelay(fadeIn, mHeadersTransitionStartDelay); 635 sTransitionHelper.addTransition(mHeadersTransition, fadeIn); 636 637 sTransitionHelper.setTransitionListener(mHeadersTransition, new TransitionListener() { 638 @Override 639 public void onTransitionStart(Object transition) { 640 } 641 @Override 642 public void onTransitionEnd(Object transition) { 643 mHeadersTransition = null; 644 mRowsFragment.onTransitionEnd(); 645 mHeadersFragment.onTransitionEnd(); 646 if (mShowingHeaders) { 647 VerticalGridView headerGridView = mHeadersFragment.getVerticalGridView(); 648 if (headerGridView != null && !headerGridView.hasFocus()) { 649 headerGridView.requestFocus(); 650 } 651 } else { 652 VerticalGridView rowsGridView = mRowsFragment.getVerticalGridView(); 653 if (rowsGridView != null && !rowsGridView.hasFocus()) { 654 rowsGridView.requestFocus(); 655 } 656 } 657 if (mBrowseTransitionListener != null) { 658 mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders); 659 } 660 } 661 }); 662 } 663 664 public void setHeaderPresenterSelector(PresenterSelector headerPresenterSelector) { 665 mHeaderPresenterSelector = headerPresenterSelector; 666 if (mHeadersFragment != null) { 667 mHeadersFragment.setPresenterSelector(mHeaderPresenterSelector); 668 } 669 } 670 671 private void showHeaders(boolean show) { 672 if (DEBUG) Log.v(TAG, "showHeaders " + show); 673 mHeadersFragment.setHeadersEnabled(show); 674 MarginLayoutParams lp; 675 View containerList; 676 677 containerList = mRowsFragment.getView(); 678 lp = (MarginLayoutParams) containerList.getLayoutParams(); 679 lp.leftMargin = show ? mContainerListMarginLeft : 0; 680 containerList.setLayoutParams(lp); 681 682 containerList = mHeadersFragment.getView(); 683 lp = (MarginLayoutParams) containerList.getLayoutParams(); 684 lp.leftMargin = show ? 0 : -mContainerListMarginLeft; 685 containerList.setLayoutParams(lp); 686 687 mRowsFragment.setExpand(!show); 688 } 689 690 private HeadersFragment.OnHeaderClickedListener mHeaderClickedListener = 691 new HeadersFragment.OnHeaderClickedListener() { 692 @Override 693 public void onHeaderClicked() { 694 if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) { 695 return; 696 } 697 startHeadersTransitionInternal(false); 698 mRowsFragment.getVerticalGridView().requestFocus(); 699 } 700 }; 701 702 private OnItemSelectedListener mRowSelectedListener = new OnItemSelectedListener() { 703 @Override 704 public void onItemSelected(Object item, Row row) { 705 int position = mRowsFragment.getVerticalGridView().getSelectedPosition(); 706 if (DEBUG) Log.v(TAG, "row selected position " + position); 707 onRowSelected(position); 708 if (mExternalOnItemSelectedListener != null) { 709 mExternalOnItemSelectedListener.onItemSelected(item, row); 710 } 711 } 712 }; 713 714 private OnItemSelectedListener mHeaderSelectedListener = new OnItemSelectedListener() { 715 @Override 716 public void onItemSelected(Object item, Row row) { 717 int position = mHeadersFragment.getVerticalGridView().getSelectedPosition(); 718 if (DEBUG) Log.v(TAG, "header selected position " + position); 719 onRowSelected(position); 720 } 721 }; 722 723 private void onRowSelected(int position) { 724 if (position != mSelectedPosition) { 725 mSetSelectionRunnable.mPosition = position; 726 mBrowseFrame.getHandler().post(mSetSelectionRunnable); 727 728 if (getAdapter() == null || getAdapter().size() == 0 || position == 0) { 729 if (!mShowingTitle) { 730 sTransitionHelper.runTransition(mSceneWithTitle, mTitleDownTransition); 731 mShowingTitle = true; 732 } 733 } else if (mShowingTitle) { 734 sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleUpTransition); 735 mShowingTitle = false; 736 } 737 } 738 } 739 740 private class SetSelectionRunnable implements Runnable { 741 int mPosition; 742 @Override 743 public void run() { 744 setSelection(mPosition); 745 } 746 } 747 748 private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable(); 749 750 private void setSelection(int position) { 751 if (position != NO_POSITION) { 752 mRowsFragment.setSelectedPosition(position); 753 mHeadersFragment.setSelectedPosition(position); 754 } 755 mSelectedPosition = position; 756 } 757 758 @Override 759 public void onStart() { 760 super.onStart(); 761 mHeadersFragment.setWindowAlignmentFromTop(mContainerListAlignTop); 762 mHeadersFragment.setItemAlignment(); 763 mRowsFragment.setWindowAlignmentFromTop(mContainerListAlignTop); 764 mRowsFragment.setItemAlignment(); 765 766 if (mCanShowHeaders && mShowingHeaders && mHeadersFragment.getView() != null) { 767 mHeadersFragment.getView().requestFocus(); 768 } else if ((!mCanShowHeaders || !mShowingHeaders) 769 && mRowsFragment.getView() != null) { 770 mRowsFragment.getView().requestFocus(); 771 } 772 if (mCanShowHeaders) { 773 showHeaders(mShowingHeaders); 774 } 775 } 776 777 /** 778 * Enable/disable headers transition on back key support. This is enabled by default. 779 * BrowseFragment will add a back stack entry when headers are showing. 780 * Headers transition on back key only works for {@link #HEADERS_ENABLED} 781 * or {@link #HEADERS_HIDDEN}. 782 * <p> 783 * NOTE: If app has its own onBackPressed() handling, 784 * app must disable this feature, app may use {@link #startHeadersTransition(boolean)} 785 * and {@link BrowseTransitionListener} in its own back stack handling. 786 */ 787 public final void setHeadersTransitionOnBackEnabled(boolean headersBackStackEnabled) { 788 mHeadersBackStackEnabled = headersBackStackEnabled; 789 } 790 791 /** 792 * Returns true if headers transition on back key support is enabled. 793 */ 794 public final boolean isHeadersTransitionOnBackEnabled() { 795 return mHeadersBackStackEnabled; 796 } 797 798 private void readArguments(Bundle args) { 799 if (args == null) { 800 return; 801 } 802 if (args.containsKey(ARG_TITLE)) { 803 setTitle(args.getString(ARG_TITLE)); 804 } 805 if (args.containsKey(ARG_HEADERS_STATE)) { 806 setHeadersState(args.getInt(ARG_HEADERS_STATE)); 807 } 808 } 809 810 /** 811 * Sets the drawable displayed in the fragment title area. 812 * @param drawable 813 */ 814 public void setBadgeDrawable(Drawable drawable) { 815 if (mBadgeDrawable != drawable) { 816 mBadgeDrawable = drawable; 817 if (mTitleView != null) { 818 mTitleView.setBadgeDrawable(drawable); 819 } 820 } 821 } 822 823 /** 824 * Returns the badge drawable. 825 */ 826 public Drawable getBadgeDrawable() { 827 return mBadgeDrawable; 828 } 829 830 /** 831 * Sets a title for the browse fragment. 832 */ 833 public void setTitle(String title) { 834 mTitle = title; 835 if (mTitleView != null) { 836 mTitleView.setTitle(title); 837 } 838 } 839 840 /** 841 * Returns the title for the browse fragment. 842 */ 843 public String getTitle() { 844 return mTitle; 845 } 846 847 /** 848 * Sets the state for the headers column in the browse fragment. 849 */ 850 public void setHeadersState(int headersState) { 851 if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) { 852 throw new IllegalArgumentException("Invalid headers state: " + headersState); 853 } 854 if (DEBUG) Log.v(TAG, "setHeadersState " + headersState); 855 856 if (headersState != mHeadersState) { 857 mHeadersState = headersState; 858 switch (headersState) { 859 case HEADERS_ENABLED: 860 mCanShowHeaders = true; 861 mShowingHeaders = true; 862 break; 863 case HEADERS_HIDDEN: 864 mCanShowHeaders = true; 865 mShowingHeaders = false; 866 break; 867 case HEADERS_DISABLED: 868 mCanShowHeaders = false; 869 mShowingHeaders = false; 870 break; 871 default: 872 Log.w(TAG, "Unknown headers state: " + headersState); 873 break; 874 } 875 if (mHeadersFragment != null) { 876 mHeadersFragment.setHeadersGone(!mCanShowHeaders); 877 } 878 } 879 } 880 881 /** 882 * Returns the state for the headers column in the browse fragment. 883 */ 884 public int getHeadersState() { 885 return mHeadersState; 886 } 887} 888