RowsFragment.java revision b60082430ab9c45352b42764d6f94e54514084aa
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 java.util.ArrayList; 17 18import android.animation.TimeAnimator; 19import android.animation.TimeAnimator.TimeListener; 20import android.os.Bundle; 21import android.support.v17.leanback.R; 22import android.support.v17.leanback.widget.ItemBridgeAdapter; 23import android.support.v17.leanback.widget.OnItemViewClickedListener; 24import android.support.v17.leanback.widget.OnItemViewSelectedListener; 25import android.support.v17.leanback.widget.RowPresenter.ViewHolder; 26import android.support.v17.leanback.widget.VerticalGridView; 27import android.support.v17.leanback.widget.HorizontalGridView; 28import android.support.v17.leanback.widget.OnItemSelectedListener; 29import android.support.v17.leanback.widget.OnItemClickedListener; 30import android.support.v17.leanback.widget.RowPresenter; 31import android.support.v17.leanback.widget.ListRowPresenter; 32import android.support.v17.leanback.widget.Presenter; 33import android.support.v7.widget.RecyclerView; 34import android.util.Log; 35import android.view.View; 36import android.view.ViewGroup; 37import android.view.ViewTreeObserver; 38import android.view.animation.DecelerateInterpolator; 39import android.view.animation.Interpolator; 40 41/** 42 * An ordered set of rows of leanback widgets. 43 */ 44public class RowsFragment extends BaseRowFragment { 45 46 /** 47 * Internal helper class that manages row select animation and apply a default 48 * dim to each row. 49 */ 50 final class RowViewHolderExtra implements TimeListener { 51 final RowPresenter mRowPresenter; 52 final Presenter.ViewHolder mRowViewHolder; 53 54 final TimeAnimator mSelectAnimator = new TimeAnimator(); 55 56 int mSelectAnimatorDurationInUse; 57 Interpolator mSelectAnimatorInterpolatorInUse; 58 float mSelectLevelAnimStart; 59 float mSelectLevelAnimDelta; 60 61 RowViewHolderExtra(ItemBridgeAdapter.ViewHolder ibvh) { 62 mRowPresenter = (RowPresenter) ibvh.getPresenter(); 63 mRowViewHolder = ibvh.getViewHolder(); 64 mSelectAnimator.setTimeListener(this); 65 } 66 67 @Override 68 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { 69 if (mSelectAnimator.isRunning()) { 70 updateSelect(totalTime, deltaTime); 71 } 72 } 73 74 void updateSelect(long totalTime, long deltaTime) { 75 float fraction; 76 if (totalTime >= mSelectAnimatorDurationInUse) { 77 fraction = 1; 78 mSelectAnimator.end(); 79 } else { 80 fraction = (float) (totalTime / (double) mSelectAnimatorDurationInUse); 81 } 82 if (mSelectAnimatorInterpolatorInUse != null) { 83 fraction = mSelectAnimatorInterpolatorInUse.getInterpolation(fraction); 84 } 85 float level = mSelectLevelAnimStart + fraction * mSelectLevelAnimDelta; 86 mRowPresenter.setSelectLevel(mRowViewHolder, level); 87 } 88 89 void animateSelect(boolean select, boolean immediate) { 90 endSelectAnimation(); 91 final float end = select ? 1 : 0; 92 if (immediate) { 93 mRowPresenter.setSelectLevel(mRowViewHolder, end); 94 } else if (mRowPresenter.getSelectLevel(mRowViewHolder) != end) { 95 mSelectAnimatorDurationInUse = mSelectAnimatorDuration; 96 mSelectAnimatorInterpolatorInUse = mSelectAnimatorInterpolator; 97 mSelectLevelAnimStart = mRowPresenter.getSelectLevel(mRowViewHolder); 98 mSelectLevelAnimDelta = end - mSelectLevelAnimStart; 99 mSelectAnimator.start(); 100 } 101 } 102 103 void endAnimations() { 104 endSelectAnimation(); 105 } 106 107 void endSelectAnimation() { 108 mSelectAnimator.end(); 109 } 110 111 } 112 113 private static final String TAG = "RowsFragment"; 114 private static final boolean DEBUG = false; 115 116 private ItemBridgeAdapter.ViewHolder mSelectedViewHolder; 117 private boolean mExpand = true; 118 private boolean mViewsCreated; 119 120 private OnItemSelectedListener mOnItemSelectedListener; 121 private OnItemViewSelectedListener mOnItemViewSelectedListener; 122 private OnItemClickedListener mOnItemClickedListener; 123 private OnItemViewClickedListener mOnItemViewClickedListener; 124 125 // Select animation and interpolator are not intended to be 126 // exposed at this moment. They might be synced with vertical scroll 127 // animation later. 128 int mSelectAnimatorDuration; 129 Interpolator mSelectAnimatorInterpolator = new DecelerateInterpolator(2); 130 131 private RecyclerView.RecycledViewPool mRecycledViewPool; 132 private ArrayList<Presenter> mPresenterMapper; 133 134 private ItemBridgeAdapter.AdapterListener mExternalAdapterListener; 135 136 /** 137 * Sets an item clicked listener on the fragment. 138 * OnItemClickedListener will override {@link View.OnClickListener} that 139 * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}. 140 * So in general, developer should choose one of the listeners but not both. 141 * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)} 142 */ 143 public void setOnItemClickedListener(OnItemClickedListener listener) { 144 mOnItemClickedListener = listener; 145 if (mViewsCreated) { 146 throw new IllegalStateException( 147 "Item clicked listener must be set before views are created"); 148 } 149 } 150 151 /** 152 * Returns the item clicked listener. 153 * @deprecated Use {@link #getOnItemClickedListener()} 154 */ 155 public OnItemClickedListener getOnItemClickedListener() { 156 return mOnItemClickedListener; 157 } 158 159 /** 160 * Sets an item clicked listener on the fragment. 161 * OnItemViewClickedListener will override {@link View.OnClickListener} that 162 * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}. 163 * So in general, developer should choose one of the listeners but not both. 164 */ 165 public void setOnItemViewClickedListener(OnItemViewClickedListener listener) { 166 mOnItemViewClickedListener = listener; 167 if (mViewsCreated) { 168 throw new IllegalStateException( 169 "Item clicked listener must be set before views are created"); 170 } 171 } 172 173 /** 174 * Returns the item clicked listener. 175 */ 176 public OnItemViewClickedListener getOnItemViewClickedListener() { 177 return mOnItemViewClickedListener; 178 } 179 180 /** 181 * Set the visibility of titles/hovercard of browse rows. 182 */ 183 public void setExpand(boolean expand) { 184 mExpand = expand; 185 VerticalGridView listView = getVerticalGridView(); 186 if (listView != null) { 187 final int count = listView.getChildCount(); 188 if (DEBUG) Log.v(TAG, "setExpand " + expand + " count " + count); 189 for (int i = 0; i < count; i++) { 190 View view = listView.getChildAt(i); 191 ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view); 192 setRowViewExpanded(vh, mExpand); 193 } 194 } 195 } 196 197 /** 198 * Sets an item selection listener. 199 * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)} 200 */ 201 public void setOnItemSelectedListener(OnItemSelectedListener listener) { 202 mOnItemSelectedListener = listener; 203 VerticalGridView listView = getVerticalGridView(); 204 if (listView != null) { 205 final int count = listView.getChildCount(); 206 for (int i = 0; i < count; i++) { 207 View view = listView.getChildAt(i); 208 ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) 209 listView.getChildViewHolder(view); 210 setOnItemSelectedListener(vh, mOnItemSelectedListener); 211 } 212 } 213 } 214 215 /** 216 * Sets an item selection listener. 217 */ 218 public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) { 219 mOnItemViewSelectedListener = listener; 220 VerticalGridView listView = getVerticalGridView(); 221 if (listView != null) { 222 final int count = listView.getChildCount(); 223 for (int i = 0; i < count; i++) { 224 View view = listView.getChildAt(i); 225 ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) 226 listView.getChildViewHolder(view); 227 setOnItemViewSelectedListener(vh, mOnItemViewSelectedListener); 228 } 229 } 230 } 231 232 /** 233 * Returns an item selection listener. 234 */ 235 public OnItemViewSelectedListener getOnItemViewSelectedListener() { 236 return mOnItemViewSelectedListener; 237 } 238 239 @Override 240 protected void onRowSelected(ViewGroup parent, View view, int position, long id) { 241 VerticalGridView listView = getVerticalGridView(); 242 if (listView == null) { 243 return; 244 } 245 ItemBridgeAdapter.ViewHolder vh = (view == null) ? null : 246 (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view); 247 248 if (mSelectedViewHolder != vh) { 249 if (DEBUG) Log.v(TAG, "new row selected position " + position + " view " + view); 250 251 if (mSelectedViewHolder != null) { 252 setRowViewSelected(mSelectedViewHolder, false, false); 253 } 254 mSelectedViewHolder = vh; 255 if (mSelectedViewHolder != null) { 256 setRowViewSelected(mSelectedViewHolder, true, false); 257 } 258 } 259 } 260 261 @Override 262 protected int getLayoutResourceId() { 263 return R.layout.lb_rows_fragment; 264 } 265 266 @Override 267 public void onCreate(Bundle savedInstanceState) { 268 super.onCreate(savedInstanceState); 269 mSelectAnimatorDuration = getResources().getInteger(R.integer.lb_browse_rows_anim_duration); 270 } 271 272 @Override 273 public void onViewCreated(View view, Bundle savedInstanceState) { 274 if (DEBUG) Log.v(TAG, "onViewCreated"); 275 super.onViewCreated(view, savedInstanceState); 276 // Align the top edge of child with id row_content. 277 // Need set this for directly using RowsFragment. 278 getVerticalGridView().setItemAlignmentViewId(R.id.row_content); 279 getVerticalGridView().setSaveChildrenPolicy(VerticalGridView.SAVE_LIMITED_CHILD); 280 281 mRecycledViewPool = null; 282 mPresenterMapper = null; 283 } 284 285 @Override 286 void setItemAlignment() { 287 super.setItemAlignment(); 288 if (getVerticalGridView() != null) { 289 getVerticalGridView().setItemAlignmentOffsetWithPadding(true); 290 } 291 } 292 293 void setExternalAdapterListener(ItemBridgeAdapter.AdapterListener listener) { 294 mExternalAdapterListener = listener; 295 } 296 297 private static void setRowViewExpanded(ItemBridgeAdapter.ViewHolder vh, boolean expanded) { 298 ((RowPresenter) vh.getPresenter()).setRowViewExpanded(vh.getViewHolder(), expanded); 299 } 300 301 private static void setRowViewSelected(ItemBridgeAdapter.ViewHolder vh, boolean selected, 302 boolean immediate) { 303 RowViewHolderExtra extra = (RowViewHolderExtra) vh.getExtraObject(); 304 extra.animateSelect(selected, immediate); 305 ((RowPresenter) vh.getPresenter()).setRowViewSelected(vh.getViewHolder(), selected); 306 } 307 308 private static void setOnItemSelectedListener(ItemBridgeAdapter.ViewHolder vh, 309 OnItemSelectedListener listener) { 310 ((RowPresenter) vh.getPresenter()).setOnItemSelectedListener(listener); 311 } 312 313 private static void setOnItemViewSelectedListener(ItemBridgeAdapter.ViewHolder vh, 314 OnItemViewSelectedListener listener) { 315 ((RowPresenter) vh.getPresenter()).setOnItemViewSelectedListener(listener); 316 } 317 318 private final ItemBridgeAdapter.AdapterListener mBridgeAdapterListener = 319 new ItemBridgeAdapter.AdapterListener() { 320 @Override 321 public void onAddPresenter(Presenter presenter, int type) { 322 ((RowPresenter) presenter).setOnItemClickedListener(mOnItemClickedListener); 323 ((RowPresenter) presenter).setOnItemViewClickedListener(mOnItemViewClickedListener); 324 if (mExternalAdapterListener != null) { 325 mExternalAdapterListener.onAddPresenter(presenter, type); 326 } 327 } 328 @Override 329 public void onCreate(ItemBridgeAdapter.ViewHolder vh) { 330 VerticalGridView listView = getVerticalGridView(); 331 if (listView != null && ((RowPresenter) vh.getPresenter()).canDrawOutOfBounds()) { 332 listView.setClipChildren(false); 333 } 334 setupSharedViewPool(vh); 335 mViewsCreated = true; 336 vh.setExtraObject(new RowViewHolderExtra(vh)); 337 // selected state is initialized to false, then driven by grid view onChildSelected 338 // events. When there is rebind, grid view fires onChildSelected event properly. 339 // So we don't need do anything special later in onBind or onAttachedToWindow. 340 setRowViewSelected(vh, false, true); 341 if (mExternalAdapterListener != null) { 342 mExternalAdapterListener.onCreate(vh); 343 } 344 } 345 @Override 346 public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) { 347 if (DEBUG) Log.v(TAG, "onAttachToWindow"); 348 // All views share the same mExpand value. When we attach a view to grid view, 349 // we should make sure it pick up the latest mExpand value we set early on other 350 // attached views. For no-structure-change update, the view is rebound to new data, 351 // but again it should use the unchanged mExpand value, so we don't need do any 352 // thing in onBind. 353 setRowViewExpanded(vh, mExpand); 354 setOnItemSelectedListener(vh, mOnItemSelectedListener); 355 setOnItemViewSelectedListener(vh, mOnItemViewSelectedListener); 356 if (mExternalAdapterListener != null) { 357 mExternalAdapterListener.onAttachedToWindow(vh); 358 } 359 } 360 @Override 361 public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) { 362 if (mSelectedViewHolder == vh) { 363 setRowViewSelected(mSelectedViewHolder, false, true); 364 mSelectedViewHolder = null; 365 } 366 if (mExternalAdapterListener != null) { 367 mExternalAdapterListener.onDetachedFromWindow(vh); 368 } 369 } 370 @Override 371 public void onBind(ItemBridgeAdapter.ViewHolder vh) { 372 if (mExternalAdapterListener != null) { 373 mExternalAdapterListener.onBind(vh); 374 } 375 } 376 @Override 377 public void onUnbind(ItemBridgeAdapter.ViewHolder vh) { 378 RowViewHolderExtra extra = (RowViewHolderExtra) vh.getExtraObject(); 379 extra.endAnimations(); 380 if (mExternalAdapterListener != null) { 381 mExternalAdapterListener.onUnbind(vh); 382 } 383 } 384 }; 385 386 private void setupSharedViewPool(ItemBridgeAdapter.ViewHolder bridgeVh) { 387 RowPresenter rowPresenter = (RowPresenter) bridgeVh.getPresenter(); 388 RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(bridgeVh.getViewHolder()); 389 390 if (rowVh instanceof ListRowPresenter.ViewHolder) { 391 HorizontalGridView view = ((ListRowPresenter.ViewHolder) rowVh).getGridView(); 392 // Recycled view pool is shared between all list rows 393 if (mRecycledViewPool == null) { 394 mRecycledViewPool = view.getRecycledViewPool(); 395 } else { 396 view.setRecycledViewPool(mRecycledViewPool); 397 } 398 399 ItemBridgeAdapter bridgeAdapter = 400 ((ListRowPresenter.ViewHolder) rowVh).getBridgeAdapter(); 401 if (mPresenterMapper == null) { 402 mPresenterMapper = bridgeAdapter.getPresenterMapper(); 403 } else { 404 bridgeAdapter.setPresenterMapper(mPresenterMapper); 405 } 406 } 407 } 408 409 @Override 410 protected void updateAdapter() { 411 super.updateAdapter(); 412 mSelectedViewHolder = null; 413 mViewsCreated = false; 414 415 ItemBridgeAdapter adapter = getBridgeAdapter(); 416 if (adapter != null) { 417 adapter.setAdapterListener(mBridgeAdapterListener); 418 } 419 } 420 421 @Override 422 void onTransitionStart() { 423 super.onTransitionStart(); 424 freezeRows(true); 425 } 426 427 class ExpandPreLayout implements ViewTreeObserver.OnPreDrawListener { 428 429 final View mVerticalView; 430 final Runnable mCallback; 431 int mState; 432 433 final static int STATE_INIT = 0; 434 final static int STATE_FIRST_DRAW = 1; 435 final static int STATE_SECOND_DRAW = 2; 436 437 ExpandPreLayout(Runnable callback) { 438 mVerticalView = getVerticalGridView(); 439 mCallback = callback; 440 } 441 442 void execute() { 443 mVerticalView.getViewTreeObserver().addOnPreDrawListener(this); 444 setExpand(false); 445 mState = STATE_INIT; 446 } 447 448 @Override 449 public boolean onPreDraw() { 450 if (mState == STATE_INIT) { 451 setExpand(true); 452 mState = STATE_FIRST_DRAW; 453 } else if (mState == STATE_FIRST_DRAW) { 454 mCallback.run(); 455 mVerticalView.getViewTreeObserver().removeOnPreDrawListener(this); 456 mState = STATE_SECOND_DRAW; 457 } 458 return false; 459 } 460 } 461 462 void onExpandTransitionStart(boolean expand, final Runnable callback) { 463 onTransitionStart(); 464 if (expand) { 465 callback.run(); 466 return; 467 } 468 // Run a "pre" layout when we go non-expand, in order to get the initial 469 // positions of added rows. 470 new ExpandPreLayout(callback).execute(); 471 } 472 473 @Override 474 void onTransitionEnd() { 475 super.onTransitionEnd(); 476 freezeRows(false); 477 } 478 479 private void freezeRows(boolean freeze) { 480 VerticalGridView verticalView = getVerticalGridView(); 481 final int count = verticalView.getChildCount(); 482 for (int i = 0; i < count; i++) { 483 ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder) 484 verticalView.getChildViewHolder(verticalView.getChildAt(i)); 485 RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter(); 486 RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder()); 487 rowPresenter.freeze(vh, freeze); 488 } 489 } 490} 491