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