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