ListRowPresenter.java revision 892181367d658f347d00ea5e091aa31f086b2a20
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.widget;
15
16import java.util.ArrayList;
17
18import android.content.Context;
19import android.graphics.Canvas;
20import android.support.v17.leanback.R;
21import android.support.v17.leanback.graphics.ColorOverlayDimmer;
22import android.support.v17.leanback.widget.Presenter.ViewHolder;
23import android.support.v7.widget.RecyclerView;
24import android.util.AttributeSet;
25import android.view.View;
26import android.view.ViewGroup;
27import android.view.ViewGroup.LayoutParams;
28import android.widget.FrameLayout;
29
30/**
31 * ListRowPresenter renders {@link ListRow} using a
32 * {@link HorizontalGridView} hosted in a {@link BrowseRowView}.
33 *
34 * <h3>Hover card</h3>
35 * Optionally, {@link #setHoverCardPresenterSelector(PresenterSelector)} can be used to
36 * display a view for the currently focused list item below the rendered
37 * list. This view is known as a hover card.
38 *
39 * <h3>Selection animation</h3>
40 * ListRowPresenter disables {@link RowPresenter}'s default dimming effect and draw
41 * a dim overlay on top of each individual child items.  Subclass may override and disable
42 * {@link #isUsingDefaultListSelectEffect()} and write its own dim effect in
43 * {@link #onSelectLevelChanged(RowPresenter.ViewHolder)}.
44 */
45public class ListRowPresenter extends RowPresenter {
46
47    private static final String TAG = "ListRowPresenter";
48    private static final boolean DEBUG = false;
49
50    /**
51     * No zoom factor.
52     */
53    public static final int ZOOM_FACTOR_NONE = FocusHighlightHelper.BrowseItemFocusHighlight.ZOOM_FACTOR_NONE;
54
55    /**
56     * A small zoom factor, recommended for large item views.
57     */
58    public static final int ZOOM_FACTOR_SMALL = FocusHighlightHelper.BrowseItemFocusHighlight.ZOOM_FACTOR_SMALL;
59
60    /**
61     * A medium zoom factor, recommended for medium sized item views.
62     */
63    public static final int ZOOM_FACTOR_MEDIUM = FocusHighlightHelper.BrowseItemFocusHighlight.ZOOM_FACTOR_MEDIUM;
64
65    /**
66     * A large zoom factor, recommended for small item views.
67     */
68    public static final int ZOOM_FACTOR_LARGE = FocusHighlightHelper.BrowseItemFocusHighlight.ZOOM_FACTOR_LARGE;
69
70    public static class ViewHolder extends RowPresenter.ViewHolder {
71        final ListRowPresenter mListRowPresenter;
72        final HorizontalGridView mGridView;
73        final ItemBridgeAdapter mItemBridgeAdapter = new ItemBridgeAdapter();
74        final HorizontalHoverCardSwitcher mHoverCardViewSwitcher = new HorizontalHoverCardSwitcher();
75        final ColorOverlayDimmer mColorDimmer;
76
77        public ViewHolder(View rootView, HorizontalGridView gridView, ListRowPresenter p) {
78            super(rootView);
79            mGridView = gridView;
80            mListRowPresenter = p;
81            mColorDimmer = ColorOverlayDimmer.createDefault(rootView.getContext());
82        }
83
84        public final ListRowPresenter getListRowPresenter() {
85            return mListRowPresenter;
86        }
87
88        public final HorizontalGridView getGridView() {
89            return mGridView;
90        }
91    }
92
93    private PresenterSelector mHoverCardPresenterSelector;
94    private int mZoomFactor;
95    private boolean mShadowEnabled = true;
96
97    /**
98     * Constructs a ListRowPresenter with defaults.
99     * Uses {@link #ZOOM_FACTOR_MEDIUM} for focus zooming.
100     */
101    public ListRowPresenter() {
102        this(ZOOM_FACTOR_MEDIUM);
103    }
104
105    /**
106     * Constructs a ListRowPresenter with the given parameters.
107     *
108     * @param zoomFactor Controls the zoom factor used when an item view is focused. One of
109     *         {@link #ZOOM_FACTOR_NONE}, {@link #ZOOM_FACTOR_SMALL}, {@link #ZOOM_FACTOR_MEDIUM},
110     *         {@link #ZOOM_FACTOR_LARGE}
111     */
112    public ListRowPresenter(int zoomFactor) {
113        mZoomFactor = zoomFactor;
114    }
115
116    /**
117     * Returns the zoom factor used for focus highlighting.
118     */
119    public final int getZoomFactor() {
120        return mZoomFactor;
121    }
122
123    private ItemBridgeAdapter.Wrapper mCardWrapper = new ItemBridgeAdapter.Wrapper() {
124        @Override
125        public View createWrapper(View root) {
126            ListRowCardWrapper wrapper = new ListRowCardWrapper(root.getContext());
127            wrapper.setLayoutParams(
128                    new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
129            wrapper.initialize(needsDefaultShadow(), needsDefaultSelectEffect());
130            return wrapper;
131        }
132        @Override
133        public void wrap(View wrapper, View wrapped) {
134            ((ListRowCardWrapper) wrapper).wrap(wrapped);
135        }
136    };
137
138    @Override
139    protected void initializeRowViewHolder(RowPresenter.ViewHolder holder) {
140        super.initializeRowViewHolder(holder);
141        final ViewHolder rowViewHolder = (ViewHolder) holder;
142        if (needsDefaultSelectEffect() || needsDefaultShadow()) {
143            rowViewHolder.mItemBridgeAdapter.setWrapper(mCardWrapper);
144        }
145        if (needsDefaultShadow()) {
146            OpticalBoundsHelper.getInstance().setOpticalBounds(rowViewHolder.mGridView);
147            ((ViewGroup) rowViewHolder.view).setClipChildren(false);
148        }
149        FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter, mZoomFactor);
150        rowViewHolder.mGridView.setOnChildSelectedListener(
151                new OnChildSelectedListener() {
152            @Override
153            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
154                selectChildView(rowViewHolder, view);
155            }
156        });
157        rowViewHolder.mItemBridgeAdapter.setAdapterListener(
158                new ItemBridgeAdapter.AdapterListener() {
159            @Override
160            public void onCreate(final ItemBridgeAdapter.ViewHolder viewHolder) {
161                // Only when having an OnItemClickListner, we will attach the OnClickListener.
162                if (getOnItemClickedListener() != null) {
163                    viewHolder.mHolder.view.setOnClickListener(new View.OnClickListener() {
164                        @Override
165                        public void onClick(View v) {
166                            ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
167                                    rowViewHolder.mGridView.getChildViewHolder(viewHolder.itemView);
168                            if (getOnItemClickedListener() != null) {
169                                getOnItemClickedListener().onItemClicked(ibh.mItem,
170                                        (ListRow) rowViewHolder.mRow);
171                            }
172                        }
173                    });
174                }
175            }
176
177            @Override
178            public void onBind(ItemBridgeAdapter.ViewHolder viewHolder) {
179                super.onBind(viewHolder);
180                if (viewHolder.itemView instanceof ListRowCardWrapper) {
181                    int dimmedColor = rowViewHolder.mColorDimmer.getPaint().getColor();
182                    ((ListRowCardWrapper) viewHolder.itemView)
183                            .mColorDimOverlay.setBackgroundColor(dimmedColor);
184                }
185            }
186        });
187    }
188
189    final boolean needsDefaultSelectEffect() {
190        return isUsingDefaultListSelectEffect() && getSelectEffectEnabled();
191    }
192
193    /**
194     * Set {@link PresenterSelector} used for showing a select object in a hover card.
195     */
196    public final void setHoverCardPresenterSelector(PresenterSelector selector) {
197        mHoverCardPresenterSelector = selector;
198    }
199
200    /**
201     * Get {@link PresenterSelector} used for showing a select object in a hover card.
202     */
203    public final PresenterSelector getHoverCardPresenterSelector() {
204        return mHoverCardPresenterSelector;
205    }
206
207    /*
208     * Perform operations when a child of horizontal grid view is selected.
209     */
210    private void selectChildView(ViewHolder rowViewHolder, View view) {
211        ItemBridgeAdapter.ViewHolder ibh = null;
212        if (view != null) {
213            ibh = (ItemBridgeAdapter.ViewHolder)
214                    rowViewHolder.mGridView.getChildViewHolder(view);
215        }
216        if (view == null) {
217            if (mHoverCardPresenterSelector != null) {
218                rowViewHolder.mHoverCardViewSwitcher.unselect();
219            }
220            if (getOnItemSelectedListener() != null) {
221                getOnItemSelectedListener().onItemSelected(null, rowViewHolder.mRow);
222            }
223        } else if (rowViewHolder.mExpanded && rowViewHolder.mSelected) {
224            if (mHoverCardPresenterSelector != null) {
225                rowViewHolder.mHoverCardViewSwitcher.select(rowViewHolder.mGridView, view,
226                        ibh.mItem);
227            }
228            if (getOnItemSelectedListener() != null) {
229                getOnItemSelectedListener().onItemSelected(ibh.mItem, rowViewHolder.mRow);
230            }
231        }
232    }
233
234    @Override
235    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
236        BrowseRowView rowView = new BrowseRowView(parent.getContext());
237        return new ViewHolder(rowView, rowView.getGridView(), this);
238    }
239
240    @Override
241    protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) {
242        updateFooterViewSwitcher((ViewHolder) holder);
243        updateInitialChildSelection((ViewHolder) holder);
244    }
245
246    /*
247     * Show or hide hover card when row selection or expanded state is changed.
248     */
249    private void updateFooterViewSwitcher(ViewHolder vh) {
250        if (vh.mExpanded && vh.mSelected) {
251            if (mHoverCardPresenterSelector != null) {
252                vh.mHoverCardViewSwitcher.init((ViewGroup) vh.view,
253                        mHoverCardPresenterSelector);
254            }
255        } else {
256            if (mHoverCardPresenterSelector != null) {
257                vh.mHoverCardViewSwitcher.clear();
258            }
259        }
260    }
261
262    /*
263     * Make initial child selection when row selection state is changed.
264     */
265    private void updateInitialChildSelection(ViewHolder vh) {
266        if (vh.mExpanded && vh.mSelected) {
267            ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
268                    vh.mGridView.findViewHolderForPosition(
269                            vh.mGridView.getSelectedPosition());
270            selectChildView(vh, ibh == null ? null : ibh.itemView);
271        } else {
272            selectChildView(vh, null);
273        }
274    }
275
276    @Override
277    protected void onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded) {
278        super.onRowViewExpanded(holder, expanded);
279        ViewHolder vh = (ViewHolder) holder;
280        vh.mGridView.setClipToPadding(!expanded);
281        vh.mGridView.invalidate();
282        updateFooterViewSwitcher(vh);
283    }
284
285    @Override
286    public void onBindViewHolder(Presenter.ViewHolder holder, Object item) {
287        super.onBindViewHolder(holder, item);
288        ViewHolder vh = (ViewHolder)holder;
289        ListRow rowItem = (ListRow) item;
290        vh.mItemBridgeAdapter.clear();
291        vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter());
292        vh.mGridView.setAdapter(vh.mItemBridgeAdapter);
293    }
294
295    @Override
296    public void onUnbindViewHolder(Presenter.ViewHolder holder) {
297        ViewHolder vh = (ViewHolder)holder;
298        vh.mGridView.setAdapter(null);
299        super.onUnbindViewHolder(holder);
300    }
301
302    /**
303     * ListRowPresenter overrides the default select effect of {@link RowPresenter}
304     * and return false.
305     */
306    @Override
307    public final boolean isUsingDefaultSelectEffect() {
308        return false;
309    }
310
311    /**
312     * Returns true so that default select effect is applied to each individual
313     * child of {@link HorizontalGridView}.  Subclass may return false to disable
314     * the default implementation.
315     * @see #onSelectLevelChanged(RowPresenter.ViewHolder)
316     */
317    public boolean isUsingDefaultListSelectEffect() {
318        return true;
319    }
320
321    /**
322     * Returns true if opticalBounds is supported (SDK >= 18) so that default shadow
323     * is applied to each individual child of {@link HorizontalGridView}.
324     * Subclass may return false to disable.
325     */
326    public boolean isUsingDefaultShadow() {
327        return OpticalBoundsHelper.systemSupportsOpticalBounds();
328    }
329
330    /**
331     * Enable or disable child shadow.
332     * This is not only for enable/disable default shadow implementation but also subclass must
333     * respect this flag.
334     */
335    public final void setShadowEnabled(boolean enabled) {
336        mShadowEnabled = enabled;
337    }
338
339    /**
340     * Returns true if child shadow is enabled.
341     * This is not only for enable/disable default shadow implementation but also subclass must
342     * respect this flag.
343     */
344    public final boolean getShadowEnabled() {
345        return mShadowEnabled;
346    }
347
348    final boolean needsDefaultShadow() {
349        return isUsingDefaultShadow() && getShadowEnabled();
350    }
351
352    @Override
353    public boolean canDrawOutOfBounds() {
354        return needsDefaultShadow();
355    }
356
357    /**
358     * Applies select level to header and draw a default color dim over each child
359     * of {@link HorizontalGridView}.
360     * <p>
361     * Subclass may override this method.  A subclass
362     * needs to call super.onSelectLevelChanged() for applying header select level
363     * and optionally applying a default select level to each child view of
364     * {@link HorizontalGridView} if {@link #isUsingDefaultListSelectEffect()}
365     * is true.  Subclass may override {@link #isUsingDefaultListSelectEffect()} to return
366     * false and deal with the individual item select level by itself.
367     * </p>
368     */
369    @Override
370    protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) {
371        super.onSelectLevelChanged(holder);
372        if (needsDefaultSelectEffect()) {
373            ViewHolder vh = (ViewHolder) holder;
374            vh.mColorDimmer.setActiveLevel(holder.mSelectLevel);
375            int dimmedColor = vh.mColorDimmer.getPaint().getColor();
376            for (int i = 0, count = vh.mGridView.getChildCount(); i < count; i++) {
377                ListRowCardWrapper wrapper = (ListRowCardWrapper) vh.mGridView.getChildAt(i);
378                wrapper.mColorDimOverlay.setBackgroundColor(dimmedColor);
379            }
380        }
381    }
382
383}
384