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