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