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