BrowseFragment.java revision 1c33346ba79177e64fe33da70ee73547d7bb15f7
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.VerticalGridView; 18import android.support.v17.leanback.widget.Row; 19import android.support.v17.leanback.widget.ObjectAdapter; 20import android.support.v17.leanback.widget.OnItemSelectedListener; 21import android.support.v17.leanback.widget.OnItemClickedListener; 22import android.support.v17.leanback.widget.SearchOrbView; 23import android.util.Log; 24import android.app.Fragment; 25import android.os.Bundle; 26import android.view.LayoutInflater; 27import android.view.View; 28import android.view.ViewGroup; 29import android.view.ViewGroup.MarginLayoutParams; 30import android.widget.ImageView; 31import android.widget.TextView; 32import android.graphics.drawable.Drawable; 33 34import static android.support.v7.widget.RecyclerView.NO_POSITION; 35 36/** 37 * Wrapper fragment for leanback browse screens. Composed of a 38 * RowsFragment and a HeadersFragment. 39 * 40 */ 41public class BrowseFragment extends Fragment { 42 private static final String TAG = "BrowseFragment"; 43 private static boolean DEBUG = false; 44 45 /** The fastlane navigation panel is enabled and shown by default. */ 46 public static final int HEADERS_ENABLED = 1; 47 48 /** The fastlane navigation panel is enabled and hidden by default. */ 49 public static final int HEADERS_HIDDEN = 2; 50 51 /** The fastlane navigation panel is disabled and will never be shown. */ 52 public static final int HEADERS_DISABLED = 3; 53 54 private final RowsFragment mRowsFragment = new RowsFragment(); 55 private final HeadersFragment mHeadersFragment = new HeadersFragment(); 56 57 private Params mParams; 58 private BrowseFrameLayout mBrowseFrame; 59 private ImageView mBadgeView; 60 private TextView mTitleView; 61 private ViewGroup mBrowseTitle; 62 private SearchOrbView mSearchOrbView; 63 private boolean mShowingTitle = true; 64 private boolean mShowingHeaders = true; 65 private boolean mCanShowHeaders = true; 66 private int mContainerListMarginLeft; 67 private int mContainerListWidth; 68 private int mContainerListAlignTop; 69 private TransitionHelper mTransitionHelper; 70 private OnItemSelectedListener mExternalOnItemSelectedListener; 71 private int mSelectedPosition = -1; 72 73 private static final String ARG_TITLE = BrowseFragment.class.getCanonicalName() + ".title"; 74 private static final String ARG_BADGE_URI = BrowseFragment.class.getCanonicalName() + ".badge"; 75 private static final String ARG_HEADERS_STATE = 76 BrowseFragment.class.getCanonicalName() + ".headersState"; 77 78 /** 79 * @param args Bundle to use for the arguments, if null a new Bundle will be created. 80 */ 81 public static Bundle createArgs(Bundle args, String title, String badgeUri) { 82 return createArgs(args, title, badgeUri, HEADERS_ENABLED); 83 } 84 85 public static Bundle createArgs(Bundle args, String title, String badgeUri, int headersState) { 86 if (args == null) { 87 args = new Bundle(); 88 } 89 args.putString(ARG_TITLE, title); 90 args.putString(ARG_BADGE_URI, badgeUri); 91 args.putInt(ARG_HEADERS_STATE, headersState); 92 return args; 93 } 94 95 public static class Params { 96 private String mTitle; 97 private Drawable mBadgeDrawable; 98 private int mHeadersState; 99 100 /** 101 * Sets the badge image. 102 */ 103 public void setBadgeImage(Drawable drawable) { 104 mBadgeDrawable = drawable; 105 } 106 107 /** 108 * Returns the badge image. 109 */ 110 public Drawable getBadgeImage() { 111 return mBadgeDrawable; 112 } 113 114 /** 115 * Sets a title for the browse fragment. 116 */ 117 public void setTitle(String title) { 118 mTitle = title; 119 } 120 121 /** 122 * Returns the title for the browse fragment. 123 */ 124 public String getTitle() { 125 return mTitle; 126 } 127 128 /** 129 * Sets the state for the headers column in the browse fragment. 130 */ 131 public void setHeadersState(int headersState) { 132 if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) { 133 Log.e(TAG, "Invalid headers state: " + headersState 134 + ", default to enabled and shown."); 135 mHeadersState = HEADERS_ENABLED; 136 } else { 137 mHeadersState = headersState; 138 } 139 } 140 141 /** 142 * Returns the state for the headers column in the browse fragment. 143 */ 144 public int getHeadersState() { 145 return mHeadersState; 146 } 147 } 148 149 /** 150 * Set browse parameters. 151 */ 152 public void setBrowseParams(Params params) { 153 mParams = params; 154 setBadgeDrawable(mParams.mBadgeDrawable); 155 setTitle(mParams.mTitle); 156 setHeadersState(mParams.mHeadersState); 157 } 158 159 /** 160 * Set background parameters. 161 * @deprecated Use BackgroundManager instead 162 */ 163 @Deprecated 164 public void setBackgroundParams(BackgroundParams params) { 165 } 166 167 /** 168 * Returns browse parameters. 169 */ 170 public Params getBrowseParams() { 171 return mParams; 172 } 173 174 /** 175 * Returns the background parameters. 176 * @deprecated Use BackgroundManager instead 177 */ 178 @Deprecated 179 public BackgroundParams getBackgroundParams() { 180 return new BackgroundParams(); 181 } 182 183 /** 184 * Sets the list of rows for the fragment. 185 */ 186 public void setAdapter(ObjectAdapter adapter) { 187 mRowsFragment.setAdapter(adapter); 188 mHeadersFragment.setAdapter(adapter); 189 } 190 191 /** 192 * Returns the list of rows. 193 */ 194 public ObjectAdapter getAdapter() { 195 return mRowsFragment.getAdapter(); 196 } 197 198 /** 199 * Sets an item selection listener. 200 */ 201 public void setOnItemSelectedListener(OnItemSelectedListener listener) { 202 mExternalOnItemSelectedListener = listener; 203 } 204 205 /** 206 * Sets an item Clicked listener. 207 */ 208 public void setOnItemClickedListener(OnItemClickedListener listener) { 209 mRowsFragment.setOnItemClickedListener(listener); 210 } 211 212 /** 213 * Returns the item Clicked listener. 214 */ 215 public OnItemClickedListener getOnItemClickedListener() { 216 return mRowsFragment.getOnItemClickedListener(); 217 } 218 219 /** 220 * Sets a click listener for the search "affordance". 221 * 222 * The presence of a listener will change the visibility of the search "affordance" in the 223 * title area. When set to non null the title area will contain a call to search action. 224 * 225 * The listener onClick method will be invoked when the user click on the search action. 226 * 227 * @param listener The listener. 228 */ 229 public void setOnSearchClickedListener(View.OnClickListener listener) { 230 mSearchOrbView.setOnOrbClickedListener(listener); 231 } 232 233 private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener = 234 new BrowseFrameLayout.OnFocusSearchListener() { 235 @Override 236 public View onFocusSearch(View focused, int direction) { 237 // If fastlane is disabled, just return null. 238 if (!mCanShowHeaders) return null; 239 240 if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction); 241 if (!mShowingHeaders && direction == View.FOCUS_LEFT) { 242 mTransitionHelper.runTransition(TransitionHelper.SCENE_WITH_HEADERS); 243 mShowingHeaders = true; 244 return mHeadersFragment.getVerticalGridView(); 245 246 } else if (mShowingHeaders && direction == View.FOCUS_RIGHT) { 247 mTransitionHelper.runTransition(TransitionHelper.SCENE_WITHOUT_HEADERS); 248 mShowingHeaders = false; 249 return mRowsFragment.getVerticalGridView(); 250 } else if (mSearchOrbView.getVisibility() == View.VISIBLE 251 && direction == View.FOCUS_DOWN) { 252 return mRowsFragment.getVerticalGridView(); 253 } else if (mSearchOrbView.getVisibility() == View.VISIBLE 254 && direction == View.FOCUS_UP) { 255 return mSearchOrbView; 256 } else { 257 return null; 258 } 259 } 260 }; 261 262 @Override 263 public void onCreate(Bundle savedInstanceState) { 264 super.onCreate(savedInstanceState); 265 266 mHeadersFragment.setOnHeaderClickListener(mHeaderClickListener); 267 268 mContainerListMarginLeft = (int) getResources().getDimension( 269 R.dimen.lb_browse_rows_margin_left); 270 mContainerListWidth = getResources().getDimensionPixelSize(R.dimen.lb_browse_rows_width); 271 mContainerListAlignTop = 272 getResources().getDimensionPixelSize(R.dimen.lb_browse_rows_align_top); 273 } 274 275 @Override 276 public View onCreateView(LayoutInflater inflater, ViewGroup container, 277 Bundle savedInstanceState) { 278 View root = inflater.inflate(R.layout.lb_browse_fragment, container, false); 279 280 mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame); 281 mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener); 282 283 mBrowseTitle = (ViewGroup) root.findViewById(R.id.browse_title_group); 284 mBadgeView = (ImageView) mBrowseTitle.findViewById(R.id.browse_badge); 285 mTitleView = (TextView) mBrowseTitle.findViewById(R.id.browse_title); 286 mSearchOrbView = (SearchOrbView) mBrowseTitle.findViewById(R.id.browse_orb); 287 288 readArguments(getArguments()); 289 if (mParams != null) { 290 setBadgeDrawable(mParams.mBadgeDrawable); 291 setTitle(mParams.mTitle); 292 setHeadersState(mParams.mHeadersState); 293 } 294 295 mTransitionHelper = new TransitionHelper(getActivity()); 296 mTransitionHelper.addSceneRunnable(TransitionHelper.SCENE_WITH_TITLE, mBrowseFrame, 297 new Runnable() { 298 @Override 299 public void run() { 300 showTitle(true); 301 } 302 }); 303 mTransitionHelper.addSceneRunnable(TransitionHelper.SCENE_WITHOUT_TITLE, mBrowseFrame, 304 new Runnable() { 305 @Override 306 public void run() { 307 showTitle(false); 308 } 309 }); 310 mTransitionHelper.addSceneRunnable(TransitionHelper.SCENE_WITH_HEADERS, mBrowseFrame, 311 new Runnable() { 312 @Override 313 public void run() { 314 showHeaders(true); 315 } 316 }); 317 mTransitionHelper.addSceneRunnable(TransitionHelper.SCENE_WITHOUT_HEADERS, mBrowseFrame, 318 new Runnable() { 319 @Override 320 public void run() { 321 showHeaders(false); 322 } 323 }); 324 325 return root; 326 } 327 328 private void showTitle(boolean show) { 329 mBrowseTitle.setVisibility(show ? View.VISIBLE : View.GONE); 330 } 331 332 private void showHeaders(boolean show) { 333 if (DEBUG) Log.v(TAG, "showHeaders " + show); 334 View headerList = mHeadersFragment.getView(); 335 View containerList = mRowsFragment.getView(); 336 MarginLayoutParams lp; 337 338 headerList.setVisibility(show ? View.VISIBLE : View.GONE); 339 lp = (MarginLayoutParams) containerList.getLayoutParams(); 340 lp.leftMargin = show ? mContainerListMarginLeft : 0; 341 containerList.setLayoutParams(lp); 342 343 mRowsFragment.setExpand(!show); 344 } 345 346 private HeaderPresenter.OnHeaderClickListener mHeaderClickListener = 347 new HeaderPresenter.OnHeaderClickListener() { 348 @Override 349 public void onHeaderClicked() { 350 if (!mCanShowHeaders || !mShowingHeaders) return; 351 352 mTransitionHelper.runTransition(TransitionHelper.SCENE_WITHOUT_HEADERS); 353 mShowingHeaders = false; 354 mRowsFragment.getVerticalGridView().requestFocus(); 355 } 356 }; 357 358 private OnItemSelectedListener mRowSelectedListener = new OnItemSelectedListener() { 359 @Override 360 public void onItemSelected(Object item, Row row) { 361 int position = mRowsFragment.getVerticalGridView().getSelectedPosition(); 362 if (DEBUG) Log.v(TAG, "row selected position " + position); 363 onRowSelected(position); 364 if (mExternalOnItemSelectedListener != null) { 365 mExternalOnItemSelectedListener.onItemSelected(item, row); 366 } 367 } 368 }; 369 370 private OnItemSelectedListener mHeaderSelectedListener = new OnItemSelectedListener() { 371 @Override 372 public void onItemSelected(Object item, Row row) { 373 int position = mHeadersFragment.getVerticalGridView().getSelectedPosition(); 374 if (DEBUG) Log.v(TAG, "header selected position " + position); 375 onRowSelected(position); 376 } 377 }; 378 379 private void onRowSelected(int position) { 380 if (position != mSelectedPosition) { 381 mSetSelectionRunnable.mPosition = position; 382 mBrowseFrame.getHandler().post(mSetSelectionRunnable); 383 384 if (position == 0) { 385 if (!mShowingTitle) { 386 mTransitionHelper.runTransition(TransitionHelper.SCENE_WITH_TITLE); 387 mShowingTitle = true; 388 } 389 } else if (mShowingTitle) { 390 mTransitionHelper.runTransition(TransitionHelper.SCENE_WITHOUT_TITLE); 391 mShowingTitle = false; 392 } 393 } 394 } 395 396 private class SetSelectionRunnable implements Runnable { 397 int mPosition; 398 @Override 399 public void run() { 400 setSelection(mPosition); 401 } 402 } 403 404 private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable(); 405 406 private void setSelection(int position) { 407 if (position != NO_POSITION) { 408 mRowsFragment.setSelectedPosition(position); 409 mHeadersFragment.setSelectedPosition(position); 410 } 411 mSelectedPosition = position; 412 } 413 414 @Override 415 public void onActivityCreated(Bundle savedInstanceState) { 416 super.onActivityCreated(savedInstanceState); 417 418 if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) { 419 getChildFragmentManager().beginTransaction() 420 .replace(R.id.browse_headers_dock, mHeadersFragment) 421 .replace(R.id.browse_container_dock, mRowsFragment).commit(); 422 mRowsFragment.setOnItemSelectedListener(mRowSelectedListener); 423 mHeadersFragment.setOnItemSelectedListener(mHeaderSelectedListener); 424 } 425 } 426 427 private void setVerticalVerticalGridViewLayout(VerticalGridView listview) { 428 // align the top edge of item to a fixed position 429 listview.setItemAlignmentOffset(0); 430 listview.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED); 431 listview.setWindowAlignmentOffset(mContainerListAlignTop); 432 listview.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED); 433 listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE); 434 } 435 436 /** 437 * Setup dimensions that are only meaningful when the child Fragments are inside 438 * BrowseFragment. 439 */ 440 private void setupChildFragmentsLayout() { 441 VerticalGridView headerList = mHeadersFragment.getVerticalGridView(); 442 VerticalGridView containerList = mRowsFragment.getVerticalGridView(); 443 444 // Both fragments list view has the same alignment 445 setVerticalVerticalGridViewLayout(headerList); 446 setVerticalVerticalGridViewLayout(containerList); 447 448 mRowsFragment.getVerticalGridView().getLayoutParams().width = mContainerListWidth; 449 mRowsFragment.getVerticalGridView().requestLayout(); 450 } 451 452 @Override 453 public void onStart() { 454 super.onStart(); 455 setupChildFragmentsLayout(); 456 if (mCanShowHeaders && mShowingHeaders && mHeadersFragment.getView() != null) { 457 mHeadersFragment.getView().requestFocus(); 458 } else if ((!mCanShowHeaders || !mShowingHeaders) 459 && mRowsFragment.getView() != null) { 460 mRowsFragment.getView().requestFocus(); 461 } 462 showHeaders(mCanShowHeaders && mShowingHeaders); 463 } 464 465 private void readArguments(Bundle args) { 466 if (args == null) { 467 return; 468 } 469 if (args.containsKey(ARG_TITLE)) { 470 setTitle(args.getString(ARG_TITLE)); 471 } 472 473 if (args.containsKey(ARG_BADGE_URI)) { 474 setBadgeUri(args.getString(ARG_BADGE_URI)); 475 } 476 477 if (args.containsKey(ARG_HEADERS_STATE)) { 478 setHeadersState(args.getInt(ARG_HEADERS_STATE)); 479 } 480 } 481 482 private void setBadgeUri(String badgeUri) { 483 // TODO - need a drawable downloader 484 } 485 486 private void setBadgeDrawable(Drawable drawable) { 487 if (mBadgeView == null) { 488 return; 489 } 490 mBadgeView.setImageDrawable(drawable); 491 if (drawable != null) { 492 mBadgeView.setVisibility(View.VISIBLE); 493 } else { 494 mBadgeView.setVisibility(View.GONE); 495 } 496 } 497 498 private void setTitle(String title) { 499 if (mTitleView != null) { 500 mTitleView.setText(title); 501 } 502 } 503 504 private void setHeadersState(int headersState) { 505 if (DEBUG) Log.v(TAG, "setHeadersState " + headersState); 506 switch (headersState) { 507 case HEADERS_ENABLED: 508 mCanShowHeaders = true; 509 mShowingHeaders = true; 510 break; 511 case HEADERS_HIDDEN: 512 mCanShowHeaders = true; 513 mShowingHeaders = false; 514 break; 515 case HEADERS_DISABLED: 516 mCanShowHeaders = false; 517 mShowingHeaders = false; 518 break; 519 default: 520 Log.w(TAG, "Unknown headers state: " + headersState); 521 break; 522 } 523 } 524} 525