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