ListRowPresenter.java revision 267ee02d485e13699840b3f8a6f480e0bb4abf0b
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 *
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(), needsDefaultSelectEffect());
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 (needsDefaultSelectEffect() || needsDefaultShadow()) {
130            rowViewHolder.mItemBridgeAdapter.setWrapper(mCardWrapper);
131        }
132        if (needsDefaultShadow()) {
133            ShadowOverlayContainer.prepareParentForShadow(rowViewHolder.mGridView);
134            ((ViewGroup) rowViewHolder.view).setClipChildren(false);
135        }
136        FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter, mZoomFactor);
137        rowViewHolder.mGridView.setOnChildSelectedListener(
138                new OnChildSelectedListener() {
139            @Override
140            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
141                selectChildView(rowViewHolder, view);
142            }
143        });
144        rowViewHolder.mItemBridgeAdapter.setAdapterListener(
145                new ItemBridgeAdapter.AdapterListener() {
146            @Override
147            public void onCreate(final ItemBridgeAdapter.ViewHolder viewHolder) {
148                // Only when having an OnItemClickListner, we will attach the OnClickListener.
149                if (getOnItemClickedListener() != null) {
150                    viewHolder.mHolder.view.setOnClickListener(new View.OnClickListener() {
151                        @Override
152                        public void onClick(View v) {
153                            ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
154                                    rowViewHolder.mGridView.getChildViewHolder(viewHolder.itemView);
155                            if (getOnItemClickedListener() != null) {
156                                getOnItemClickedListener().onItemClicked(ibh.mItem,
157                                        (ListRow) rowViewHolder.mRow);
158                            }
159                        }
160                    });
161                }
162            }
163
164            @Override
165            public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
166                if (viewHolder.itemView instanceof ShadowOverlayContainer) {
167                    int dimmedColor = rowViewHolder.mColorDimmer.getPaint().getColor();
168                    ((ShadowOverlayContainer) viewHolder.itemView).setOverlayColor(dimmedColor);
169                }
170            }
171        });
172    }
173
174    final boolean needsDefaultSelectEffect() {
175        return isUsingDefaultListSelectEffect() && getSelectEffectEnabled();
176    }
177
178    /**
179     * Set {@link PresenterSelector} used for showing a select object in a hover card.
180     */
181    public final void setHoverCardPresenterSelector(PresenterSelector selector) {
182        mHoverCardPresenterSelector = selector;
183    }
184
185    /**
186     * Get {@link PresenterSelector} used for showing a select object in a hover card.
187     */
188    public final PresenterSelector getHoverCardPresenterSelector() {
189        return mHoverCardPresenterSelector;
190    }
191
192    /*
193     * Perform operations when a child of horizontal grid view is selected.
194     */
195    private void selectChildView(ViewHolder rowViewHolder, View view) {
196        ItemBridgeAdapter.ViewHolder ibh = null;
197        if (view != null) {
198            ibh = (ItemBridgeAdapter.ViewHolder)
199                    rowViewHolder.mGridView.getChildViewHolder(view);
200        }
201        if (view == null) {
202            if (mHoverCardPresenterSelector != null) {
203                rowViewHolder.mHoverCardViewSwitcher.unselect();
204            }
205            if (getOnItemSelectedListener() != null) {
206                getOnItemSelectedListener().onItemSelected(null, rowViewHolder.mRow);
207            }
208        } else if (rowViewHolder.mExpanded && rowViewHolder.mSelected) {
209            if (mHoverCardPresenterSelector != null) {
210                rowViewHolder.mHoverCardViewSwitcher.select(rowViewHolder.mGridView, view,
211                        ibh.mItem);
212            }
213            if (getOnItemSelectedListener() != null) {
214                getOnItemSelectedListener().onItemSelected(ibh.mItem, rowViewHolder.mRow);
215            }
216        }
217    }
218
219    @Override
220    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
221        BrowseRowView rowView = new BrowseRowView(parent.getContext());
222        return new ViewHolder(rowView, rowView.getGridView(), this);
223    }
224
225    @Override
226    protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) {
227        updateFooterViewSwitcher((ViewHolder) holder);
228        updateInitialChildSelection((ViewHolder) holder);
229    }
230
231    /*
232     * Show or hide hover card when row selection or expanded state is changed.
233     */
234    private void updateFooterViewSwitcher(ViewHolder vh) {
235        if (vh.mExpanded && vh.mSelected) {
236            if (mHoverCardPresenterSelector != null) {
237                vh.mHoverCardViewSwitcher.init((ViewGroup) vh.view,
238                        mHoverCardPresenterSelector);
239            }
240        } else {
241            if (mHoverCardPresenterSelector != null) {
242                vh.mHoverCardViewSwitcher.clear();
243            }
244        }
245    }
246
247    /*
248     * Make initial child selection when row selection state is changed.
249     */
250    private void updateInitialChildSelection(ViewHolder vh) {
251        if (vh.mExpanded && vh.mSelected) {
252            ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
253                    vh.mGridView.findViewHolderForPosition(
254                            vh.mGridView.getSelectedPosition());
255            selectChildView(vh, ibh == null ? null : ibh.itemView);
256        } else {
257            selectChildView(vh, null);
258        }
259    }
260
261    @Override
262    protected void onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded) {
263        super.onRowViewExpanded(holder, expanded);
264        ViewHolder vh = (ViewHolder) holder;
265        vh.mGridView.setClipToPadding(!expanded);
266        vh.mGridView.invalidate();
267        updateFooterViewSwitcher(vh);
268    }
269
270    @Override
271    public void onBindViewHolder(Presenter.ViewHolder holder, Object item) {
272        super.onBindViewHolder(holder, item);
273        ViewHolder vh = (ViewHolder)holder;
274        ListRow rowItem = (ListRow) item;
275        vh.mItemBridgeAdapter.clear();
276        vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter());
277        vh.mGridView.setAdapter(vh.mItemBridgeAdapter);
278    }
279
280    @Override
281    public void onUnbindViewHolder(Presenter.ViewHolder holder) {
282        ViewHolder vh = (ViewHolder)holder;
283        vh.mGridView.setAdapter(null);
284        super.onUnbindViewHolder(holder);
285    }
286
287    /**
288     * ListRowPresenter overrides the default select effect of {@link RowPresenter}
289     * and return false.
290     */
291    @Override
292    public final boolean isUsingDefaultSelectEffect() {
293        return false;
294    }
295
296    /**
297     * Returns true so that default select effect is applied to each individual
298     * child of {@link HorizontalGridView}.  Subclass may return false to disable
299     * the default implementation.
300     * @see #onSelectLevelChanged(RowPresenter.ViewHolder)
301     */
302    public boolean isUsingDefaultListSelectEffect() {
303        return true;
304    }
305
306    /**
307     * Returns true if SDK >= 18, where default shadow
308     * is applied to each individual child of {@link HorizontalGridView}.
309     * Subclass may return false to disable.
310     */
311    public boolean isUsingDefaultShadow() {
312        return ShadowOverlayContainer.supportsShadow();
313    }
314
315    /**
316     * Enable or disable child shadow.
317     * This is not only for enable/disable default shadow implementation but also subclass must
318     * respect this flag.
319     */
320    public final void setShadowEnabled(boolean enabled) {
321        mShadowEnabled = enabled;
322    }
323
324    /**
325     * Returns true if child shadow is enabled.
326     * This is not only for enable/disable default shadow implementation but also subclass must
327     * respect this flag.
328     */
329    public final boolean getShadowEnabled() {
330        return mShadowEnabled;
331    }
332
333    final boolean needsDefaultShadow() {
334        return isUsingDefaultShadow() && getShadowEnabled();
335    }
336
337    @Override
338    public boolean canDrawOutOfBounds() {
339        return needsDefaultShadow();
340    }
341
342    /**
343     * Applies select level to header and draw a default color dim over each child
344     * of {@link HorizontalGridView}.
345     * <p>
346     * Subclass may override this method.  A subclass
347     * needs to call super.onSelectLevelChanged() for applying header select level
348     * and optionally applying a default select level to each child view of
349     * {@link HorizontalGridView} if {@link #isUsingDefaultListSelectEffect()}
350     * is true.  Subclass may override {@link #isUsingDefaultListSelectEffect()} to return
351     * false and deal with the individual item select level by itself.
352     * </p>
353     */
354    @Override
355    protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) {
356        super.onSelectLevelChanged(holder);
357        if (needsDefaultSelectEffect()) {
358            ViewHolder vh = (ViewHolder) holder;
359            vh.mColorDimmer.setActiveLevel(holder.mSelectLevel);
360            int dimmedColor = vh.mColorDimmer.getPaint().getColor();
361            for (int i = 0, count = vh.mGridView.getChildCount(); i < count; i++) {
362                ShadowOverlayContainer wrapper = (ShadowOverlayContainer) vh.mGridView.getChildAt(i);
363                wrapper.setOverlayColor(dimmedColor);
364            }
365        }
366    }
367
368}
369