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.transition.TransitionHelper; 18import android.support.v17.leanback.widget.BrowseFrameLayout; 19import android.support.v17.leanback.widget.OnItemViewClickedListener; 20import android.support.v17.leanback.widget.OnItemViewSelectedListener; 21import android.support.v17.leanback.widget.Presenter; 22import android.support.v17.leanback.widget.Row; 23import android.support.v17.leanback.widget.RowPresenter; 24import android.support.v17.leanback.widget.TitleView; 25import android.support.v17.leanback.widget.VerticalGridPresenter; 26import android.support.v17.leanback.widget.ObjectAdapter; 27import android.support.v17.leanback.widget.OnItemClickedListener; 28import android.support.v17.leanback.widget.OnItemSelectedListener; 29import android.support.v17.leanback.widget.SearchOrbView; 30import android.support.v4.view.ViewCompat; 31import android.app.Fragment; 32import android.content.Context; 33import android.graphics.drawable.Drawable; 34import android.os.Bundle; 35import android.util.Log; 36import android.view.LayoutInflater; 37import android.view.View; 38import android.view.ViewGroup; 39import android.view.ViewGroup.MarginLayoutParams; 40import android.widget.ImageView; 41import android.widget.TextView; 42 43/** 44 * A fragment for creating leanback vertical grids. 45 * 46 * <p>Renders a vertical grid of objects given a {@link VerticalGridPresenter} and 47 * an {@link ObjectAdapter}. 48 */ 49public class VerticalGridFragment extends Fragment { 50 private static final String TAG = "VerticalGridFragment"; 51 private static boolean DEBUG = false; 52 53 private BrowseFrameLayout mBrowseFrame; 54 private String mTitle; 55 private Drawable mBadgeDrawable; 56 private ObjectAdapter mAdapter; 57 private VerticalGridPresenter mGridPresenter; 58 private VerticalGridPresenter.ViewHolder mGridViewHolder; 59 private OnItemSelectedListener mOnItemSelectedListener; 60 private OnItemClickedListener mOnItemClickedListener; 61 private OnItemViewSelectedListener mOnItemViewSelectedListener; 62 private OnItemViewClickedListener mOnItemViewClickedListener; 63 private View.OnClickListener mExternalOnSearchClickedListener; 64 private int mSelectedPosition = -1; 65 66 private TitleView mTitleView; 67 private SearchOrbView.Colors mSearchAffordanceColors; 68 private boolean mSearchAffordanceColorSet; 69 private boolean mShowingTitle = true; 70 71 // transition related 72 private static TransitionHelper sTransitionHelper = TransitionHelper.getInstance(); 73 private Object mTitleUpTransition; 74 private Object mTitleDownTransition; 75 private Object mSceneWithTitle; 76 private Object mSceneWithoutTitle; 77 78 /** 79 * Sets the badge drawable displayed in the title area. 80 */ 81 public void setBadgeDrawable(Drawable drawable) { 82 if (drawable != mBadgeDrawable) { 83 mBadgeDrawable = drawable; 84 if (mTitleView != null) { 85 mTitleView.setBadgeDrawable(drawable); 86 } 87 } 88 } 89 90 /** 91 * Returns the badge drawable. 92 */ 93 public Drawable getBadgeDrawable() { 94 return mBadgeDrawable; 95 } 96 97 /** 98 * Sets a title for the fragment. 99 */ 100 public void setTitle(String title) { 101 mTitle = title; 102 if (mTitleView != null) { 103 mTitleView.setTitle(mTitle); 104 } 105 } 106 107 /** 108 * Returns the title for the fragment. 109 */ 110 public String getTitle() { 111 return mTitle; 112 } 113 114 /** 115 * Sets the grid presenter. 116 */ 117 public void setGridPresenter(VerticalGridPresenter gridPresenter) { 118 if (gridPresenter == null) { 119 throw new IllegalArgumentException("Grid presenter may not be null"); 120 } 121 mGridPresenter = gridPresenter; 122 mGridPresenter.setOnItemViewSelectedListener(mRowSelectedListener); 123 if (mOnItemViewClickedListener != null) { 124 mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener); 125 } 126 if (mOnItemClickedListener != null) { 127 mGridPresenter.setOnItemClickedListener(mOnItemClickedListener); 128 } 129 } 130 131 /** 132 * Returns the grid presenter. 133 */ 134 public VerticalGridPresenter getGridPresenter() { 135 return mGridPresenter; 136 } 137 138 /** 139 * Sets the object adapter for the fragment. 140 */ 141 public void setAdapter(ObjectAdapter adapter) { 142 mAdapter = adapter; 143 updateAdapter(); 144 } 145 146 /** 147 * Returns the object adapter. 148 */ 149 public ObjectAdapter getAdapter() { 150 return mAdapter; 151 } 152 153 final private OnItemViewSelectedListener mRowSelectedListener = 154 new OnItemViewSelectedListener() { 155 @Override 156 public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, 157 RowPresenter.ViewHolder rowViewHolder, Row row) { 158 int position = mGridViewHolder.getGridView().getSelectedPosition(); 159 if (DEBUG) Log.v(TAG, "row selected position " + position); 160 onRowSelected(position); 161 if (mOnItemSelectedListener != null) { 162 mOnItemSelectedListener.onItemSelected(item, row); 163 } 164 if (mOnItemViewSelectedListener != null) { 165 mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item, 166 rowViewHolder, row); 167 } 168 } 169 }; 170 171 /** 172 * Sets an item selection listener. 173 * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)} 174 */ 175 public void setOnItemSelectedListener(OnItemSelectedListener listener) { 176 mOnItemSelectedListener = listener; 177 } 178 179 /** 180 * Sets an item selection listener. 181 */ 182 public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) { 183 mOnItemViewSelectedListener = listener; 184 } 185 186 private void onRowSelected(int position) { 187 if (position != mSelectedPosition) { 188 if (!mGridViewHolder.getGridView().hasPreviousViewInSameRow(position)) { 189 // if has no sibling in front of it, show title 190 if (!mShowingTitle) { 191 sTransitionHelper.runTransition(mSceneWithTitle, mTitleDownTransition); 192 mShowingTitle = true; 193 } 194 } else if (mShowingTitle) { 195 sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleUpTransition); 196 mShowingTitle = false; 197 } 198 mSelectedPosition = position; 199 } 200 } 201 202 /** 203 * Sets an item clicked listener. 204 * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)} 205 */ 206 public void setOnItemClickedListener(OnItemClickedListener listener) { 207 mOnItemClickedListener = listener; 208 if (mGridPresenter != null) { 209 mGridPresenter.setOnItemClickedListener(mOnItemClickedListener); 210 } 211 } 212 213 /** 214 * Returns the item clicked listener. 215 * @deprecated Use {@link #getOnItemViewClickedListener()} 216 */ 217 public OnItemClickedListener getOnItemClickedListener() { 218 return mOnItemClickedListener; 219 } 220 221 /** 222 * Sets an item clicked listener. 223 */ 224 public void setOnItemViewClickedListener(OnItemViewClickedListener listener) { 225 mOnItemViewClickedListener = listener; 226 if (mGridPresenter != null) { 227 mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener); 228 } 229 } 230 231 /** 232 * Returns the item clicked listener. 233 */ 234 public OnItemViewClickedListener getOnItemViewClickedListener() { 235 return mOnItemViewClickedListener; 236 } 237 238 /** 239 * Sets a click listener for the search affordance. 240 * 241 * <p>The presence of a listener will change the visibility of the search 242 * affordance in the title area. When set to non-null, the title area will 243 * contain a call to search action. 244 * 245 * <p>The listener's onClick method will be invoked when the user clicks on 246 * the search action. 247 * 248 * @param listener The listener to invoke when the search affordance is 249 * clicked, or null to hide the search affordance. 250 */ 251 public void setOnSearchClickedListener(View.OnClickListener listener) { 252 mExternalOnSearchClickedListener = listener; 253 if (mTitleView != null) { 254 mTitleView.setOnSearchClickedListener(listener); 255 } 256 } 257 258 /** 259 * Sets the {@link SearchOrbView.Colors} used to draw the search affordance. 260 */ 261 public void setSearchAffordanceColors(SearchOrbView.Colors colors) { 262 mSearchAffordanceColors = colors; 263 mSearchAffordanceColorSet = true; 264 if (mTitleView != null) { 265 mTitleView.setSearchAffordanceColors(mSearchAffordanceColors); 266 } 267 } 268 269 /** 270 * Returns the {@link SearchOrbView.Colors} used to draw the search affordance. 271 */ 272 public SearchOrbView.Colors getSearchAffordanceColors() { 273 if (mSearchAffordanceColorSet) { 274 return mSearchAffordanceColors; 275 } 276 if (mTitleView == null) { 277 throw new IllegalStateException("Fragment views not yet created"); 278 } 279 return mTitleView.getSearchAffordanceColors(); 280 } 281 282 /** 283 * Sets the color used to draw the search affordance. 284 * A default brighter color will be set by the framework. 285 * 286 * @param color The color to use for the search affordance. 287 */ 288 public void setSearchAffordanceColor(int color) { 289 setSearchAffordanceColors(new SearchOrbView.Colors(color)); 290 } 291 292 /** 293 * Returns the color used to draw the search affordance. 294 */ 295 public int getSearchAffordanceColor() { 296 return getSearchAffordanceColors().color; 297 } 298 299 private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener = 300 new BrowseFrameLayout.OnFocusSearchListener() { 301 @Override 302 public View onFocusSearch(View focused, int direction) { 303 if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction); 304 305 final View searchOrbView = mTitleView.getSearchAffordanceView(); 306 final boolean isRtl = ViewCompat.getLayoutDirection(focused) == 307 View.LAYOUT_DIRECTION_RTL; 308 final int forward = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT; 309 if (focused == searchOrbView && ( 310 direction == View.FOCUS_DOWN || direction == forward)) { 311 return mGridViewHolder.view; 312 313 } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE 314 && direction == View.FOCUS_UP) { 315 return searchOrbView; 316 317 } else { 318 return null; 319 } 320 } 321 }; 322 323 @Override 324 public View onCreateView(LayoutInflater inflater, ViewGroup container, 325 Bundle savedInstanceState) { 326 ViewGroup root = (ViewGroup) inflater.inflate(R.layout.lb_vertical_grid_fragment, 327 container, false); 328 329 mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame); 330 mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener); 331 332 mTitleView = (TitleView) root.findViewById(R.id.browse_title_group); 333 mTitleView.setBadgeDrawable(mBadgeDrawable); 334 mTitleView.setTitle(mTitle); 335 if (mSearchAffordanceColorSet) { 336 mTitleView.setSearchAffordanceColors(mSearchAffordanceColors); 337 } 338 if (mExternalOnSearchClickedListener != null) { 339 mTitleView.setOnSearchClickedListener(mExternalOnSearchClickedListener); 340 } 341 342 mSceneWithTitle = sTransitionHelper.createScene(root, new Runnable() { 343 @Override 344 public void run() { 345 mTitleView.setVisibility(View.VISIBLE); 346 } 347 }); 348 mSceneWithoutTitle = sTransitionHelper.createScene(root, new Runnable() { 349 @Override 350 public void run() { 351 mTitleView.setVisibility(View.INVISIBLE); 352 } 353 }); 354 Context context = getActivity(); 355 mTitleUpTransition = sTransitionHelper.loadTransition(context, R.transition.lb_title_out); 356 mTitleDownTransition = sTransitionHelper.loadTransition(context, R.transition.lb_title_in); 357 358 return root; 359 } 360 361 @Override 362 public void onViewCreated(View view, Bundle savedInstanceState) { 363 ViewGroup gridDock = (ViewGroup) view.findViewById(R.id.browse_grid_dock); 364 mGridViewHolder = mGridPresenter.onCreateViewHolder(gridDock); 365 gridDock.addView(mGridViewHolder.view); 366 367 updateAdapter(); 368 } 369 370 @Override 371 public void onStart() { 372 super.onStart(); 373 mGridViewHolder.getGridView().requestFocus(); 374 } 375 376 @Override 377 public void onPause() { 378 mTitleView.enableAnimation(false); 379 super.onPause(); 380 } 381 382 @Override 383 public void onResume() { 384 super.onResume(); 385 mTitleView.enableAnimation(true); 386 } 387 388 @Override 389 public void onDestroyView() { 390 super.onDestroyView(); 391 mGridViewHolder = null; 392 } 393 394 /** 395 * Sets the selected item position. 396 */ 397 public void setSelectedPosition(int position) { 398 mSelectedPosition = position; 399 if(mGridViewHolder != null && mGridViewHolder.getGridView().getAdapter() != null) { 400 mGridViewHolder.getGridView().setSelectedPositionSmooth(position); 401 } 402 } 403 404 private void updateAdapter() { 405 if (mGridViewHolder != null) { 406 mGridPresenter.onBindViewHolder(mGridViewHolder, mAdapter); 407 if (mSelectedPosition != -1) { 408 mGridViewHolder.getGridView().setSelectedPosition(mSelectedPosition); 409 } 410 } 411 } 412} 413