BrowseFragment.java revision e2679e4ccab0ce75f701629c22c179165df4f15e
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 */ 162 public void setBackgroundParams(BackgroundParams params) { 163 mRowsFragment.setBackgroundParams(params); 164 } 165 166 /** 167 * Returns browse parameters. 168 */ 169 public Params getBrowseParams() { 170 return mParams; 171 } 172 173 /** 174 * Returns the background parameters. 175 */ 176 public BackgroundParams getBackgroundParams() { 177 return mRowsFragment.getBackgroundParams(); 178 } 179 180 /** 181 * Sets the list of rows for the fragment. 182 */ 183 public void setAdapter(ObjectAdapter adapter) { 184 mRowsFragment.setAdapter(adapter); 185 mHeadersFragment.setAdapter(adapter); 186 } 187 188 /** 189 * Returns the list of rows. 190 */ 191 public ObjectAdapter getAdapter() { 192 return mRowsFragment.getAdapter(); 193 } 194 195 /** 196 * Sets an item selection listener. 197 */ 198 public void setOnItemSelectedListener(OnItemSelectedListener listener) { 199 mExternalOnItemSelectedListener = listener; 200 } 201 202 /** 203 * Sets an item Clicked listener. 204 */ 205 public void setOnItemClickedListener(OnItemClickedListener listener) { 206 mRowsFragment.setOnItemClickedListener(listener); 207 } 208 209 /** 210 * Returns the item Clicked listener. 211 */ 212 public OnItemClickedListener getOnItemClickedListener() { 213 return mRowsFragment.getOnItemClickedListener(); 214 } 215 216 /** 217 * Sets a click listener for the search "affordance". 218 * 219 * The presence of a listener will change the visibility of the search "affordance" in the 220 * title area. When set to non null the title area will contain a call to search action. 221 * 222 * The listener onClick method will be invoked when the user click on the search action. 223 * 224 * @param listener The listener. 225 */ 226 public void setOnSearchClickedListener(View.OnClickListener listener) { 227 mSearchOrbView.setOnOrbClickedListener(listener); 228 } 229 230 private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener = 231 new BrowseFrameLayout.OnFocusSearchListener() { 232 @Override 233 public View onFocusSearch(View focused, int direction) { 234 // If fastlane is disabled, just return null. 235 if (!mCanShowHeaders) return null; 236 237 if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction); 238 if (!mShowingHeaders && direction == View.FOCUS_LEFT) { 239 mTransitionHelper.runTransition(TransitionHelper.SCENE_WITH_HEADERS); 240 mShowingHeaders = true; 241 return mHeadersFragment.getVerticalGridView(); 242 243 } else if (mShowingHeaders && direction == View.FOCUS_RIGHT) { 244 mTransitionHelper.runTransition(TransitionHelper.SCENE_WITHOUT_HEADERS); 245 mShowingHeaders = false; 246 return mRowsFragment.getVerticalGridView(); 247 } else if (mSearchOrbView.getVisibility() == View.VISIBLE 248 && direction == View.FOCUS_DOWN) { 249 return mRowsFragment.getVerticalGridView(); 250 } else if (mSearchOrbView.getVisibility() == View.VISIBLE 251 && direction == View.FOCUS_UP) { 252 return mSearchOrbView; 253 } else { 254 return null; 255 } 256 } 257 }; 258 259 @Override 260 public void onCreate(Bundle savedInstanceState) { 261 super.onCreate(savedInstanceState); 262 263 mHeadersFragment.setOnHeaderClickListener(mHeaderClickListener); 264 265 mContainerListMarginLeft = (int) getResources().getDimension( 266 R.dimen.lb_browse_rows_margin_left); 267 mContainerListWidth = getResources().getDimensionPixelSize(R.dimen.lb_browse_rows_width); 268 mContainerListAlignTop = 269 getResources().getDimensionPixelSize(R.dimen.lb_browse_rows_align_top); 270 } 271 272 @Override 273 public View onCreateView(LayoutInflater inflater, ViewGroup container, 274 Bundle savedInstanceState) { 275 View root = inflater.inflate(R.layout.lb_browse_fragment, container, false); 276 277 mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame); 278 mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener); 279 280 mBrowseTitle = (ViewGroup) root.findViewById(R.id.browse_title_group); 281 mBadgeView = (ImageView) mBrowseTitle.findViewById(R.id.browse_badge); 282 mTitleView = (TextView) mBrowseTitle.findViewById(R.id.browse_title); 283 mSearchOrbView = (SearchOrbView) mBrowseTitle.findViewById(R.id.browse_orb); 284 285 readArguments(getArguments()); 286 if (mParams != null) { 287 setBadgeDrawable(mParams.mBadgeDrawable); 288 setTitle(mParams.mTitle); 289 setHeadersState(mParams.mHeadersState); 290 } 291 292 mTransitionHelper = new TransitionHelper(getActivity()); 293 mTransitionHelper.addSceneRunnable(TransitionHelper.SCENE_WITH_TITLE, mBrowseFrame, 294 new Runnable() { 295 @Override 296 public void run() { 297 showTitle(true); 298 } 299 }); 300 mTransitionHelper.addSceneRunnable(TransitionHelper.SCENE_WITHOUT_TITLE, mBrowseFrame, 301 new Runnable() { 302 @Override 303 public void run() { 304 showTitle(false); 305 } 306 }); 307 mTransitionHelper.addSceneRunnable(TransitionHelper.SCENE_WITH_HEADERS, mBrowseFrame, 308 new Runnable() { 309 @Override 310 public void run() { 311 showHeaders(true); 312 } 313 }); 314 mTransitionHelper.addSceneRunnable(TransitionHelper.SCENE_WITHOUT_HEADERS, mBrowseFrame, 315 new Runnable() { 316 @Override 317 public void run() { 318 showHeaders(false); 319 } 320 }); 321 322 return root; 323 } 324 325 private void showTitle(boolean show) { 326 mBrowseTitle.setVisibility(show ? View.VISIBLE : View.GONE); 327 } 328 329 private void showHeaders(boolean show) { 330 if (DEBUG) Log.v(TAG, "showHeaders " + show); 331 View headerList = mHeadersFragment.getView(); 332 View containerList = mRowsFragment.getView(); 333 MarginLayoutParams lp; 334 335 headerList.setVisibility(show ? View.VISIBLE : View.GONE); 336 lp = (MarginLayoutParams) containerList.getLayoutParams(); 337 lp.leftMargin = show ? mContainerListMarginLeft : 0; 338 containerList.setLayoutParams(lp); 339 340 mRowsFragment.setExpand(!show); 341 } 342 343 private HeaderPresenter.OnHeaderClickListener mHeaderClickListener = 344 new HeaderPresenter.OnHeaderClickListener() { 345 @Override 346 public void onHeaderClicked() { 347 if (!mCanShowHeaders || !mShowingHeaders) return; 348 349 mTransitionHelper.runTransition(TransitionHelper.SCENE_WITHOUT_HEADERS); 350 mShowingHeaders = false; 351 mRowsFragment.getVerticalGridView().requestFocus(); 352 } 353 }; 354 355 private OnItemSelectedListener mRowSelectedListener = new OnItemSelectedListener() { 356 @Override 357 public void onItemSelected(Object item, Row row) { 358 int position = mRowsFragment.getVerticalGridView().getSelectedPosition(); 359 if (DEBUG) Log.v(TAG, "row selected position " + position); 360 onRowSelected(position); 361 if (mExternalOnItemSelectedListener != null) { 362 mExternalOnItemSelectedListener.onItemSelected(item, row); 363 } 364 } 365 }; 366 367 private OnItemSelectedListener mHeaderSelectedListener = new OnItemSelectedListener() { 368 @Override 369 public void onItemSelected(Object item, Row row) { 370 int position = mHeadersFragment.getVerticalGridView().getSelectedPosition(); 371 if (DEBUG) Log.v(TAG, "header selected position " + position); 372 onRowSelected(position); 373 } 374 }; 375 376 private void onRowSelected(int position) { 377 if (position != mSelectedPosition) { 378 mSetSelectionRunnable.mPosition = position; 379 mBrowseFrame.getHandler().post(mSetSelectionRunnable); 380 381 if (position == 0) { 382 if (!mShowingTitle) { 383 mTransitionHelper.runTransition(TransitionHelper.SCENE_WITH_TITLE); 384 mShowingTitle = true; 385 } 386 } else if (mShowingTitle) { 387 mTransitionHelper.runTransition(TransitionHelper.SCENE_WITHOUT_TITLE); 388 mShowingTitle = false; 389 } 390 } 391 } 392 393 private class SetSelectionRunnable implements Runnable { 394 int mPosition; 395 @Override 396 public void run() { 397 setSelection(mPosition); 398 } 399 } 400 401 private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable(); 402 403 private void setSelection(int position) { 404 if (position != NO_POSITION) { 405 mRowsFragment.setSelectedPosition(position); 406 mHeadersFragment.setSelectedPosition(position); 407 } 408 mSelectedPosition = position; 409 } 410 411 @Override 412 public void onActivityCreated(Bundle savedInstanceState) { 413 super.onActivityCreated(savedInstanceState); 414 415 if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) { 416 getChildFragmentManager().beginTransaction() 417 .replace(R.id.browse_headers_dock, mHeadersFragment) 418 .replace(R.id.browse_container_dock, mRowsFragment).commit(); 419 mRowsFragment.setOnItemSelectedListener(mRowSelectedListener); 420 mHeadersFragment.setOnItemSelectedListener(mHeaderSelectedListener); 421 } 422 } 423 424 private void setVerticalVerticalGridViewLayout(VerticalGridView listview) { 425 // align the top edge of item to a fixed position 426 listview.setItemAlignmentOffset(0); 427 listview.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED); 428 listview.setWindowAlignmentOffset(mContainerListAlignTop); 429 listview.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED); 430 listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE); 431 } 432 433 /** 434 * Setup dimensions that are only meaningful when the child Fragments are inside 435 * BrowseFragment. 436 */ 437 private void setupChildFragmentsLayout() { 438 VerticalGridView headerList = mHeadersFragment.getVerticalGridView(); 439 VerticalGridView containerList = mRowsFragment.getVerticalGridView(); 440 441 // Both fragments list view has the same alignment 442 setVerticalVerticalGridViewLayout(headerList); 443 setVerticalVerticalGridViewLayout(containerList); 444 445 mRowsFragment.getVerticalGridView().getLayoutParams().width = mContainerListWidth; 446 mRowsFragment.getVerticalGridView().requestLayout(); 447 } 448 449 @Override 450 public void onStart() { 451 super.onStart(); 452 setupChildFragmentsLayout(); 453 if (mCanShowHeaders && mShowingHeaders && mHeadersFragment.getView() != null) { 454 mHeadersFragment.getView().requestFocus(); 455 } else if ((!mCanShowHeaders || !mShowingHeaders) 456 && mRowsFragment.getView() != null) { 457 mRowsFragment.getView().requestFocus(); 458 } 459 showHeaders(mCanShowHeaders && mShowingHeaders); 460 } 461 462 private void readArguments(Bundle args) { 463 if (args == null) { 464 return; 465 } 466 if (args.containsKey(ARG_TITLE)) { 467 setTitle(args.getString(ARG_TITLE)); 468 } 469 470 if (args.containsKey(ARG_BADGE_URI)) { 471 setBadgeUri(args.getString(ARG_BADGE_URI)); 472 } 473 474 if (args.containsKey(ARG_HEADERS_STATE)) { 475 setHeadersState(args.getInt(ARG_HEADERS_STATE)); 476 } 477 } 478 479 private void setBadgeUri(String badgeUri) { 480 // TODO - need a drawable downloader 481 } 482 483 private void setBadgeDrawable(Drawable drawable) { 484 if (mBadgeView == null) { 485 return; 486 } 487 mBadgeView.setVisibility(View.VISIBLE); 488 mBadgeView.setImageDrawable(drawable); 489 } 490 491 private void setTitle(String title) { 492 if (mTitleView != null) { 493 mTitleView.setText(title); 494 } 495 } 496 497 private void setHeadersState(int headersState) { 498 if (DEBUG) Log.v(TAG, "setHeadersState " + headersState); 499 switch (headersState) { 500 case HEADERS_ENABLED: 501 mCanShowHeaders = true; 502 mShowingHeaders = true; 503 break; 504 case HEADERS_HIDDEN: 505 mCanShowHeaders = true; 506 mShowingHeaders = false; 507 break; 508 case HEADERS_DISABLED: 509 mCanShowHeaders = false; 510 mShowingHeaders = false; 511 break; 512 default: 513 Log.w(TAG, "Unknown headers state: " + headersState); 514 break; 515 } 516 } 517} 518