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