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