RowsSupportFragment.java revision 7560f1efed0774bc6fe0c5cf7030feba51010645
1/* This file is auto-generated from RowsFragment.java.  DO NOT MODIFY. */
2
3/*
4 * Copyright (C) 2014 The Android Open Source Project
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7 * in compliance with the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software distributed under the License
12 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 * or implied. See the License for the specific language governing permissions and limitations under
14 * the License.
15 */
16package android.support.v17.leanback.app;
17
18import android.animation.TimeAnimator;
19import android.animation.TimeAnimator.TimeListener;
20import android.os.Bundle;
21import android.support.v17.leanback.R;
22import android.support.v17.leanback.widget.HorizontalGridView;
23import android.support.v17.leanback.widget.ItemBridgeAdapter;
24import android.support.v17.leanback.widget.ListRowPresenter;
25import android.support.v17.leanback.widget.ObjectAdapter;
26import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
27import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
28import android.support.v17.leanback.widget.OnItemViewClickedListener;
29import android.support.v17.leanback.widget.OnItemViewSelectedListener;
30import android.support.v17.leanback.widget.Presenter;
31import android.support.v17.leanback.widget.PresenterSelector;
32import android.support.v17.leanback.widget.RowPresenter;
33import android.support.v17.leanback.widget.VerticalGridView;
34import android.support.v17.leanback.widget.ViewHolderTask;
35import android.support.v7.widget.RecyclerView;
36import android.util.Log;
37import android.view.LayoutInflater;
38import android.view.View;
39import android.view.ViewGroup;
40import android.view.animation.DecelerateInterpolator;
41import android.view.animation.Interpolator;
42
43import java.util.ArrayList;
44
45/**
46 * An ordered set of rows of leanback widgets.
47 * <p>
48 * A RowsSupportFragment renders the elements of its
49 * {@link android.support.v17.leanback.widget.ObjectAdapter} as a set
50 * of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses
51 * of {@link RowPresenter}.
52 * </p>
53 */
54public class RowsSupportFragment extends BaseRowSupportFragment implements Adaptable {
55
56    private MainFragmentAdapter mMainFragmentAdapter;
57    private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
58
59    @Override
60    public Object getAdapter(Class clazz) {
61        if (clazz == BrowseSupportFragment.MainFragmentAdapter.class) {
62            if (mMainFragmentAdapter == null) {
63                mMainFragmentAdapter = new MainFragmentAdapter(this);
64            }
65            return mMainFragmentAdapter;
66        } else if (clazz == BrowseSupportFragment.MainFragmentRowsAdapter.class) {
67            if (mMainFragmentRowsAdapter == null) {
68                mMainFragmentRowsAdapter = new MainFragmentRowsAdapter(this);
69            }
70            return mMainFragmentRowsAdapter;
71        }
72        return null;
73    }
74
75    /**
76     * Internal helper class that manages row select animation and apply a default
77     * dim to each row.
78     */
79    final class RowViewHolderExtra implements TimeListener {
80        final RowPresenter mRowPresenter;
81        final Presenter.ViewHolder mRowViewHolder;
82
83        final TimeAnimator mSelectAnimator = new TimeAnimator();
84
85        int mSelectAnimatorDurationInUse;
86        Interpolator mSelectAnimatorInterpolatorInUse;
87        float mSelectLevelAnimStart;
88        float mSelectLevelAnimDelta;
89
90        RowViewHolderExtra(ItemBridgeAdapter.ViewHolder ibvh) {
91            mRowPresenter = (RowPresenter) ibvh.getPresenter();
92            mRowViewHolder = ibvh.getViewHolder();
93            mSelectAnimator.setTimeListener(this);
94        }
95
96        @Override
97        public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
98            if (mSelectAnimator.isRunning()) {
99                updateSelect(totalTime, deltaTime);
100            }
101        }
102
103        void updateSelect(long totalTime, long deltaTime) {
104            float fraction;
105            if (totalTime >= mSelectAnimatorDurationInUse) {
106                fraction = 1;
107                mSelectAnimator.end();
108            } else {
109                fraction = (float) (totalTime / (double) mSelectAnimatorDurationInUse);
110            }
111            if (mSelectAnimatorInterpolatorInUse != null) {
112                fraction = mSelectAnimatorInterpolatorInUse.getInterpolation(fraction);
113            }
114            float level = mSelectLevelAnimStart + fraction * mSelectLevelAnimDelta;
115            mRowPresenter.setSelectLevel(mRowViewHolder, level);
116        }
117
118        void animateSelect(boolean select, boolean immediate) {
119            mSelectAnimator.end();
120            final float end = select ? 1 : 0;
121            if (immediate) {
122                mRowPresenter.setSelectLevel(mRowViewHolder, end);
123            } else if (mRowPresenter.getSelectLevel(mRowViewHolder) != end) {
124                mSelectAnimatorDurationInUse = mSelectAnimatorDuration;
125                mSelectAnimatorInterpolatorInUse = mSelectAnimatorInterpolator;
126                mSelectLevelAnimStart = mRowPresenter.getSelectLevel(mRowViewHolder);
127                mSelectLevelAnimDelta = end - mSelectLevelAnimStart;
128                mSelectAnimator.start();
129            }
130        }
131
132    }
133
134    private static final String TAG = "RowsSupportFragment";
135    private static final boolean DEBUG = false;
136
137    private ItemBridgeAdapter.ViewHolder mSelectedViewHolder;
138    private int mSubPosition;
139    private boolean mExpand = true;
140    private boolean mViewsCreated;
141    private int mAlignedTop;
142    private boolean mAfterEntranceTransition = true;
143
144    private BaseOnItemViewSelectedListener mOnItemViewSelectedListener;
145    private BaseOnItemViewClickedListener mOnItemViewClickedListener;
146
147    // Select animation and interpolator are not intended to be
148    // exposed at this moment. They might be synced with vertical scroll
149    // animation later.
150    int mSelectAnimatorDuration;
151    Interpolator mSelectAnimatorInterpolator = new DecelerateInterpolator(2);
152
153    private RecyclerView.RecycledViewPool mRecycledViewPool;
154    private ArrayList<Presenter> mPresenterMapper;
155
156    private ItemBridgeAdapter.AdapterListener mExternalAdapterListener;
157
158    @Override
159    protected VerticalGridView findGridViewFromRoot(View view) {
160        return (VerticalGridView) view.findViewById(R.id.container_list);
161    }
162
163    /**
164     * Sets an item clicked listener on the fragment.
165     * OnItemViewClickedListener will override {@link View.OnClickListener} that
166     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
167     * So in general, developer should choose one of the listeners but not both.
168     */
169    public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
170        mOnItemViewClickedListener = listener;
171        if (mViewsCreated) {
172            throw new IllegalStateException(
173                    "Item clicked listener must be set before views are created");
174        }
175    }
176
177    /**
178     * Returns the item clicked listener.
179     */
180    public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
181        return mOnItemViewClickedListener;
182    }
183
184    /**
185     * @deprecated use {@link BrowseSupportFragment#enableRowScaling(boolean)} instead.
186     *
187     * @param enable true to enable row scaling
188     */
189    public void enableRowScaling(boolean enable) {
190    }
191
192    /**
193     * Set the visibility of titles/hovercard of browse rows.
194     */
195    public void setExpand(boolean expand) {
196        mExpand = expand;
197        VerticalGridView listView = getVerticalGridView();
198        if (listView != null) {
199            final int count = listView.getChildCount();
200            if (DEBUG) Log.v(TAG, "setExpand " + expand + " count " + count);
201            for (int i = 0; i < count; i++) {
202                View view = listView.getChildAt(i);
203                ItemBridgeAdapter.ViewHolder vh
204                        = (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
205                setRowViewExpanded(vh, mExpand);
206            }
207        }
208    }
209
210    /**
211     * Sets an item selection listener.
212     */
213    public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
214        mOnItemViewSelectedListener = listener;
215        VerticalGridView listView = getVerticalGridView();
216        if (listView != null) {
217            final int count = listView.getChildCount();
218            for (int i = 0; i < count; i++) {
219                View view = listView.getChildAt(i);
220                ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
221                        listView.getChildViewHolder(view);
222                getRowViewHolder(ibvh).setOnItemViewSelectedListener(mOnItemViewSelectedListener);
223            }
224        }
225    }
226
227    /**
228     * Returns an item selection listener.
229     */
230    public BaseOnItemViewSelectedListener getOnItemViewSelectedListener() {
231        return mOnItemViewSelectedListener;
232    }
233
234    @Override
235    void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder viewHolder,
236            int position, int subposition) {
237        if (mSelectedViewHolder != viewHolder || mSubPosition != subposition) {
238            if (DEBUG) Log.v(TAG, "new row selected position " + position + " subposition "
239                    + subposition + " view " + viewHolder.itemView);
240            mSubPosition = subposition;
241            if (mSelectedViewHolder != null) {
242                setRowViewSelected(mSelectedViewHolder, false, false);
243            }
244            mSelectedViewHolder = (ItemBridgeAdapter.ViewHolder) viewHolder;
245            if (mSelectedViewHolder != null) {
246                setRowViewSelected(mSelectedViewHolder, true, false);
247            }
248        }
249    }
250
251    /**
252     * Get row ViewHolder at adapter position.  Returns null if the row object is not in adapter or
253     * the row object has not been bound to a row view.
254     *
255     * @param position Position of row in adapter.
256     * @return Row ViewHolder at a given adapter position.
257     */
258    public RowPresenter.ViewHolder getRowViewHolder(int position) {
259        VerticalGridView verticalView = getVerticalGridView();
260        if (verticalView == null) {
261            return null;
262        }
263        return getRowViewHolder((ItemBridgeAdapter.ViewHolder)
264                verticalView.findViewHolderForAdapterPosition(position));
265    }
266
267    @Override
268    int getLayoutResourceId() {
269        return R.layout.lb_rows_fragment;
270    }
271
272    @Override
273    public void onCreate(Bundle savedInstanceState) {
274        super.onCreate(savedInstanceState);
275        mSelectAnimatorDuration = getResources().getInteger(
276                R.integer.lb_browse_rows_anim_duration);
277    }
278
279    @Override
280    public void onViewCreated(View view, Bundle savedInstanceState) {
281        if (DEBUG) Log.v(TAG, "onViewCreated");
282        super.onViewCreated(view, savedInstanceState);
283        // Align the top edge of child with id row_content.
284        // Need set this for directly using RowsSupportFragment.
285        getVerticalGridView().setItemAlignmentViewId(R.id.row_content);
286        getVerticalGridView().setSaveChildrenPolicy(VerticalGridView.SAVE_LIMITED_CHILD);
287
288        setAlignment(mAlignedTop);
289
290        mRecycledViewPool = null;
291        mPresenterMapper = null;
292        if (mMainFragmentAdapter != null) {
293            mMainFragmentAdapter.getFragmentHost().notifyViewCreated(mMainFragmentAdapter);
294        }
295    }
296
297    @Override
298    public void onDestroyView() {
299        mViewsCreated = false;
300        super.onDestroyView();
301    }
302
303    void setExternalAdapterListener(ItemBridgeAdapter.AdapterListener listener) {
304        mExternalAdapterListener = listener;
305    }
306
307    private static void setRowViewExpanded(ItemBridgeAdapter.ViewHolder vh, boolean expanded) {
308        ((RowPresenter) vh.getPresenter()).setRowViewExpanded(vh.getViewHolder(), expanded);
309    }
310
311    private static void setRowViewSelected(ItemBridgeAdapter.ViewHolder vh, boolean selected,
312            boolean immediate) {
313        RowViewHolderExtra extra = (RowViewHolderExtra) vh.getExtraObject();
314        extra.animateSelect(selected, immediate);
315        ((RowPresenter) vh.getPresenter()).setRowViewSelected(vh.getViewHolder(), selected);
316    }
317
318    private final ItemBridgeAdapter.AdapterListener mBridgeAdapterListener =
319            new ItemBridgeAdapter.AdapterListener() {
320        @Override
321        public void onAddPresenter(Presenter presenter, int type) {
322            if (mExternalAdapterListener != null) {
323                mExternalAdapterListener.onAddPresenter(presenter, type);
324            }
325        }
326
327        @Override
328        public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
329            VerticalGridView listView = getVerticalGridView();
330            if (listView != null) {
331                // set clip children false for slide animation
332                listView.setClipChildren(false);
333            }
334            setupSharedViewPool(vh);
335            mViewsCreated = true;
336            vh.setExtraObject(new RowViewHolderExtra(vh));
337            // selected state is initialized to false, then driven by grid view onChildSelected
338            // events.  When there is rebind, grid view fires onChildSelected event properly.
339            // So we don't need do anything special later in onBind or onAttachedToWindow.
340            setRowViewSelected(vh, false, true);
341            if (mExternalAdapterListener != null) {
342                mExternalAdapterListener.onCreate(vh);
343            }
344        }
345
346        @Override
347        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
348            if (DEBUG) Log.v(TAG, "onAttachToWindow");
349            // All views share the same mExpand value.  When we attach a view to grid view,
350            // we should make sure it pick up the latest mExpand value we set early on other
351            // attached views.  For no-structure-change update,  the view is rebound to new data,
352            // but again it should use the unchanged mExpand value,  so we don't need do any
353            // thing in onBind.
354            setRowViewExpanded(vh, mExpand);
355            RowPresenter rowPresenter = (RowPresenter) vh.getPresenter();
356            RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(vh.getViewHolder());
357            rowVh.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
358            rowVh.setOnItemViewClickedListener(mOnItemViewClickedListener);
359            rowPresenter.setEntranceTransitionState(rowVh, mAfterEntranceTransition);
360            if (mExternalAdapterListener != null) {
361                mExternalAdapterListener.onAttachedToWindow(vh);
362            }
363        }
364
365        @Override
366        public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
367            if (mSelectedViewHolder == vh) {
368                setRowViewSelected(mSelectedViewHolder, false, true);
369                mSelectedViewHolder = null;
370            }
371            if (mExternalAdapterListener != null) {
372                mExternalAdapterListener.onDetachedFromWindow(vh);
373            }
374        }
375
376        @Override
377        public void onBind(ItemBridgeAdapter.ViewHolder vh) {
378            if (mExternalAdapterListener != null) {
379                mExternalAdapterListener.onBind(vh);
380            }
381        }
382
383        @Override
384        public void onUnbind(ItemBridgeAdapter.ViewHolder vh) {
385            setRowViewSelected(vh, false, true);
386            if (mExternalAdapterListener != null) {
387                mExternalAdapterListener.onUnbind(vh);
388            }
389        }
390    };
391
392    private void setupSharedViewPool(ItemBridgeAdapter.ViewHolder bridgeVh) {
393        RowPresenter rowPresenter = (RowPresenter) bridgeVh.getPresenter();
394        RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(bridgeVh.getViewHolder());
395
396        if (rowVh instanceof ListRowPresenter.ViewHolder) {
397            HorizontalGridView view = ((ListRowPresenter.ViewHolder) rowVh).getGridView();
398            // Recycled view pool is shared between all list rows
399            if (mRecycledViewPool == null) {
400                mRecycledViewPool = view.getRecycledViewPool();
401            } else {
402                view.setRecycledViewPool(mRecycledViewPool);
403            }
404
405            ItemBridgeAdapter bridgeAdapter =
406                    ((ListRowPresenter.ViewHolder) rowVh).getBridgeAdapter();
407            if (mPresenterMapper == null) {
408                mPresenterMapper = bridgeAdapter.getPresenterMapper();
409            } else {
410                bridgeAdapter.setPresenterMapper(mPresenterMapper);
411            }
412        }
413    }
414
415    @Override
416    void updateAdapter() {
417        super.updateAdapter();
418        mSelectedViewHolder = null;
419        mViewsCreated = false;
420
421        ItemBridgeAdapter adapter = getBridgeAdapter();
422        if (adapter != null) {
423            adapter.setAdapterListener(mBridgeAdapterListener);
424        }
425    }
426
427    @Override
428    public boolean onTransitionPrepare() {
429        boolean prepared = super.onTransitionPrepare();
430        if (prepared) {
431            freezeRows(true);
432        }
433        return prepared;
434    }
435
436    @Override
437    public void onTransitionEnd() {
438        super.onTransitionEnd();
439        freezeRows(false);
440    }
441
442    private void freezeRows(boolean freeze) {
443        VerticalGridView verticalView = getVerticalGridView();
444        if (verticalView != null) {
445            final int count = verticalView.getChildCount();
446            for (int i = 0; i < count; i++) {
447                ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
448                        verticalView.getChildViewHolder(verticalView.getChildAt(i));
449                RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
450                RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
451                rowPresenter.freeze(vh, freeze);
452            }
453        }
454    }
455
456    /**
457     * For rows that willing to participate entrance transition,  this function
458     * hide views if afterTransition is true,  show views if afterTransition is false.
459     */
460    public void setEntranceTransitionState(boolean afterTransition) {
461        mAfterEntranceTransition = afterTransition;
462        VerticalGridView verticalView = getVerticalGridView();
463        if (verticalView != null) {
464            final int count = verticalView.getChildCount();
465            for (int i = 0; i < count; i++) {
466                ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
467                        verticalView.getChildViewHolder(verticalView.getChildAt(i));
468                RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
469                RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
470                rowPresenter.setEntranceTransitionState(vh, mAfterEntranceTransition);
471            }
472        }
473    }
474
475    /**
476     * Selects a Row and perform an optional task on the Row. For example
477     * <code>setSelectedPosition(10, true, new ListRowPresenterSelectItemViewHolderTask(5))</code>
478     * Scroll to 11th row and selects 6th item on that row.  The method will be ignored if
479     * RowsSupportFragment has not been created (i.e. before {@link #onCreateView(LayoutInflater,
480     * ViewGroup, Bundle)}).
481     *
482     * @param rowPosition Which row to select.
483     * @param smooth True to scroll to the row, false for no animation.
484     * @param rowHolderTask Task to perform on the Row.
485     */
486    public void setSelectedPosition(int rowPosition, boolean smooth,
487            final Presenter.ViewHolderTask rowHolderTask) {
488        VerticalGridView verticalView = getVerticalGridView();
489        if (verticalView == null) {
490            return;
491        }
492        ViewHolderTask task = null;
493        if (rowHolderTask != null) {
494            task = new ViewHolderTask() {
495                @Override
496                public void run(RecyclerView.ViewHolder rvh) {
497                    rowHolderTask.run(getRowViewHolder((ItemBridgeAdapter.ViewHolder) rvh));
498                }
499            };
500        }
501        if (smooth) {
502            verticalView.setSelectedPositionSmooth(rowPosition, task);
503        } else {
504            verticalView.setSelectedPosition(rowPosition, task);
505        }
506    }
507
508    static RowPresenter.ViewHolder getRowViewHolder(ItemBridgeAdapter.ViewHolder ibvh) {
509        if (ibvh == null) {
510            return null;
511        }
512        RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
513        return rowPresenter.getRowViewHolder(ibvh.getViewHolder());
514    }
515
516    public boolean isScrolling() {
517        if (getVerticalGridView() == null) {
518            return false;
519        }
520        return getVerticalGridView().getScrollState() != HorizontalGridView.SCROLL_STATE_IDLE;
521    }
522
523    @Override
524    public void setAlignment(int windowAlignOffsetFromTop) {
525        mAlignedTop = windowAlignOffsetFromTop;
526        final VerticalGridView gridView = getVerticalGridView();
527
528        if (gridView != null) {
529            gridView.setItemAlignmentOffset(0);
530            gridView.setItemAlignmentOffsetPercent(
531                    VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
532            gridView.setItemAlignmentOffsetWithPadding(true);
533            gridView.setWindowAlignmentOffset(mAlignedTop);
534            // align to a fixed position from top
535            gridView.setWindowAlignmentOffsetPercent(
536                    VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
537            gridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
538        }
539    }
540
541    public static class MainFragmentAdapter extends BrowseSupportFragment.MainFragmentAdapter<RowsSupportFragment> {
542
543        public MainFragmentAdapter(RowsSupportFragment fragment) {
544            super(fragment);
545            setScalingEnabled(true);
546        }
547
548        @Override
549        public boolean isScrolling() {
550            return getFragment().isScrolling();
551        }
552
553        @Override
554        public void setExpand(boolean expand) {
555            getFragment().setExpand(expand);
556        }
557
558        @Override
559        public void setEntranceTransitionState(boolean state) {
560            getFragment().setEntranceTransitionState(state);
561        }
562
563        @Override
564        public void setAlignment(int windowAlignOffsetFromTop) {
565            getFragment().setAlignment(windowAlignOffsetFromTop);
566        }
567
568        @Override
569        public boolean onTransitionPrepare() {
570            return getFragment().onTransitionPrepare();
571        }
572
573        @Override
574        public void onTransitionStart() {
575            getFragment().onTransitionStart();
576        }
577
578        @Override
579        public void onTransitionEnd() {
580            getFragment().onTransitionEnd();
581        }
582
583    }
584
585    public static class MainFragmentRowsAdapter
586            extends BrowseSupportFragment.MainFragmentRowsAdapter<RowsSupportFragment> {
587
588        public MainFragmentRowsAdapter(RowsSupportFragment fragment) {
589            super(fragment);
590        }
591
592        @Override
593        public void setAdapter(ObjectAdapter adapter) {
594            getFragment().setAdapter(adapter);
595        }
596
597        /**
598         * Sets an item clicked listener on the fragment.
599         */
600        @Override
601        public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
602            getFragment().setOnItemViewClickedListener(listener);
603        }
604
605        @Override
606        public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
607            getFragment().setOnItemViewSelectedListener(listener);
608        }
609
610        @Override
611        public void setSelectedPosition(int rowPosition,
612                                        boolean smooth,
613                                        final Presenter.ViewHolderTask rowHolderTask) {
614            getFragment().setSelectedPosition(rowPosition, smooth, rowHolderTask);
615        }
616
617        @Override
618        public void setSelectedPosition(int rowPosition, boolean smooth) {
619            getFragment().setSelectedPosition(rowPosition, smooth);
620        }
621
622        @Override
623        public int getSelectedPosition() {
624            return getFragment().getSelectedPosition();
625        }
626    }
627}
628