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