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