VerticalGridPresenter.java revision 85833087b2288e0f002de6b4ebcbc0564839a217
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 android.content.Context;
17import android.support.v17.leanback.R;
18import android.support.v17.leanback.system.Settings;
19import android.view.LayoutInflater;
20import android.view.View;
21import android.view.ViewGroup;
22import android.view.ViewGroup.LayoutParams;
23import android.util.Log;
24
25/**
26 * A presenter that renders objects in a {@link VerticalGridView}.
27 */
28public class VerticalGridPresenter extends Presenter {
29    private static final String TAG = "GridPresenter";
30    private static final boolean DEBUG = false;
31
32    class VerticalGridItemBridgeAdapter extends ItemBridgeAdapter {
33        @Override
34        protected void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
35            if (mShadowOverlayHelper != null) {
36                mShadowOverlayHelper.onViewCreated(viewHolder.itemView);
37            }
38        }
39
40        @Override
41        public void onBind(final ItemBridgeAdapter.ViewHolder itemViewHolder) {
42            // Only when having an OnItemClickListner, we attach the OnClickListener.
43            if (getOnItemViewClickedListener() != null) {
44                final View itemView = itemViewHolder.mHolder.view;
45                itemView.setOnClickListener(new View.OnClickListener() {
46                    @Override
47                    public void onClick(View view) {
48                        if (getOnItemViewClickedListener() != null) {
49                            // Row is always null
50                            getOnItemViewClickedListener().onItemClicked(
51                                    itemViewHolder.mHolder, itemViewHolder.mItem, null, null);
52                        }
53                    }
54                });
55            }
56        }
57
58        @Override
59        public void onUnbind(ItemBridgeAdapter.ViewHolder viewHolder) {
60            if (getOnItemViewClickedListener() != null) {
61                viewHolder.mHolder.view.setOnClickListener(null);
62            }
63        }
64
65        @Override
66        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
67            viewHolder.itemView.setActivated(true);
68        }
69    }
70
71    /**
72     * ViewHolder for the VerticalGridPresenter.
73     */
74    public static class ViewHolder extends Presenter.ViewHolder {
75        ItemBridgeAdapter mItemBridgeAdapter;
76        final VerticalGridView mGridView;
77        boolean mInitialized;
78
79        public ViewHolder(VerticalGridView view) {
80            super(view);
81            mGridView = view;
82        }
83
84        public VerticalGridView getGridView() {
85            return mGridView;
86        }
87    }
88
89    private int mNumColumns = -1;
90    private int mFocusZoomFactor;
91    private boolean mUseFocusDimmer;
92    private boolean mShadowEnabled = true;
93    private boolean mKeepChildForeground = true;
94    private OnItemViewSelectedListener mOnItemViewSelectedListener;
95    private OnItemViewClickedListener mOnItemViewClickedListener;
96    private boolean mRoundedCornersEnabled = true;
97    private ShadowOverlayHelper mShadowOverlayHelper;
98    private ItemBridgeAdapter.Wrapper mShadowOverlayWrapper;
99
100    /**
101     * Constructs a VerticalGridPresenter with defaults.
102     * Uses {@link FocusHighlight#ZOOM_FACTOR_MEDIUM} for focus zooming and
103     * enabled dimming on focus.
104     */
105    public VerticalGridPresenter() {
106        this(FocusHighlight.ZOOM_FACTOR_LARGE);
107    }
108
109    /**
110     * Constructs a VerticalGridPresenter with the given parameters.
111     *
112     * @param focusZoomFactor Controls the zoom factor used when an item view is focused. One of
113     *         {@link FocusHighlight#ZOOM_FACTOR_NONE},
114     *         {@link FocusHighlight#ZOOM_FACTOR_SMALL},
115     *         {@link FocusHighlight#ZOOM_FACTOR_XSMALL},
116     *         {@link FocusHighlight#ZOOM_FACTOR_MEDIUM},
117     *         {@link FocusHighlight#ZOOM_FACTOR_LARGE}
118     * enabled dimming on focus.
119     */
120    public VerticalGridPresenter(int focusZoomFactor) {
121        this(focusZoomFactor, true);
122    }
123
124    /**
125     * Constructs a VerticalGridPresenter with the given parameters.
126     *
127     * @param focusZoomFactor Controls the zoom factor used when an item view is focused. One of
128     *         {@link FocusHighlight#ZOOM_FACTOR_NONE},
129     *         {@link FocusHighlight#ZOOM_FACTOR_SMALL},
130     *         {@link FocusHighlight#ZOOM_FACTOR_XSMALL},
131     *         {@link FocusHighlight#ZOOM_FACTOR_MEDIUM},
132     *         {@link FocusHighlight#ZOOM_FACTOR_LARGE}
133     * @param useFocusDimmer determines if the FocusHighlighter will use the dimmer
134     */
135    public VerticalGridPresenter(int focusZoomFactor, boolean useFocusDimmer) {
136        mFocusZoomFactor = focusZoomFactor;
137        mUseFocusDimmer = useFocusDimmer;
138    }
139
140    /**
141     * Sets the number of columns in the vertical grid.
142     */
143    public void setNumberOfColumns(int numColumns) {
144        if (numColumns < 0) {
145            throw new IllegalArgumentException("Invalid number of columns");
146        }
147        if (mNumColumns != numColumns) {
148            mNumColumns = numColumns;
149        }
150    }
151
152    /**
153     * Returns the number of columns in the vertical grid.
154     */
155    public int getNumberOfColumns() {
156        return mNumColumns;
157    }
158
159    /**
160     * Enable or disable child shadow.
161     * This is not only for enable/disable default shadow implementation but also subclass must
162     * respect this flag.
163     */
164    public final void setShadowEnabled(boolean enabled) {
165        mShadowEnabled = enabled;
166    }
167
168    /**
169     * Returns true if child shadow is enabled.
170     * This is not only for enable/disable default shadow implementation but also subclass must
171     * respect this flag.
172     */
173    public final boolean getShadowEnabled() {
174        return mShadowEnabled;
175    }
176
177    /**
178     * Returns true if opticalBounds is supported (SDK >= 18) so that default shadow
179     * is applied to each individual child of {@link VerticalGridView}.
180     * Subclass may return false to disable.
181     */
182    public boolean isUsingDefaultShadow() {
183        return ShadowOverlayHelper.supportsShadow();
184    }
185
186    /**
187     * Enables or disabled rounded corners on children of this row.
188     * Supported on Android SDK >= L.
189     */
190    public final void enableChildRoundedCorners(boolean enable) {
191        mRoundedCornersEnabled = enable;
192    }
193
194    /**
195     * Returns true if rounded corners are enabled for children of this row.
196     */
197    public final boolean areChildRoundedCornersEnabled() {
198        return mRoundedCornersEnabled;
199    }
200
201    /**
202     * Returns true if SDK >= L, where Z shadow is enabled so that Z order is enabled
203     * on each child of vertical grid.   If subclass returns false in isUsingDefaultShadow()
204     * and does not use Z-shadow on SDK >= L, it should override isUsingZOrder() return false.
205     */
206    public boolean isUsingZOrder(Context context) {
207        return !Settings.getInstance(context).preferStaticShadows();
208    }
209
210    final boolean needsDefaultShadow() {
211        return isUsingDefaultShadow() && getShadowEnabled();
212    }
213
214    /**
215     * Returns the zoom factor used for focus highlighting.
216     */
217    public final int getFocusZoomFactor() {
218        return mFocusZoomFactor;
219    }
220
221    /**
222     * Returns true if the focus dimmer is used for focus highlighting; false otherwise.
223     */
224    public final boolean isFocusDimmerUsed() {
225        return mUseFocusDimmer;
226    }
227
228    @Override
229    public final ViewHolder onCreateViewHolder(ViewGroup parent) {
230        ViewHolder vh = createGridViewHolder(parent);
231        vh.mInitialized = false;
232        vh.mItemBridgeAdapter = new VerticalGridItemBridgeAdapter();
233        initializeGridViewHolder(vh);
234        if (!vh.mInitialized) {
235            throw new RuntimeException("super.initializeGridViewHolder() must be called");
236        }
237        return vh;
238    }
239
240    /**
241     * Subclass may override this to inflate a different layout.
242     */
243    protected ViewHolder createGridViewHolder(ViewGroup parent) {
244        View root = LayoutInflater.from(parent.getContext()).inflate(
245                R.layout.lb_vertical_grid, parent, false);
246        return new ViewHolder((VerticalGridView) root.findViewById(R.id.browse_grid));
247    }
248
249    /**
250     * Called after a {@link VerticalGridPresenter.ViewHolder} is created.
251     * Subclasses may override this method and start by calling
252     * super.initializeGridViewHolder(ViewHolder).
253     *
254     * @param vh The ViewHolder to initialize for the vertical grid.
255     */
256    protected void initializeGridViewHolder(ViewHolder vh) {
257        if (mNumColumns == -1) {
258            throw new IllegalStateException("Number of columns must be set");
259        }
260        if (DEBUG) Log.v(TAG, "mNumColumns " + mNumColumns);
261        vh.getGridView().setNumColumns(mNumColumns);
262        vh.mInitialized = true;
263
264        Context context = vh.mGridView.getContext();
265        if (mShadowOverlayHelper == null) {
266            mShadowOverlayHelper = new ShadowOverlayHelper.Builder()
267                    .needsOverlay(mUseFocusDimmer)
268                    .needsShadow(needsDefaultShadow())
269                    .needsRoundedCorner(areChildRoundedCornersEnabled())
270                    .preferZOrder(isUsingZOrder(context))
271                    .keepForegroundDrawable(mKeepChildForeground)
272                    .options(createShadowOverlayOptions())
273                    .build(context);
274            if (mShadowOverlayHelper.needsWrapper()) {
275                mShadowOverlayWrapper = new ItemBridgeAdapterShadowOverlayWrapper(
276                        mShadowOverlayHelper);
277            }
278        }
279        vh.mItemBridgeAdapter.setWrapper(mShadowOverlayWrapper);
280        mShadowOverlayHelper.prepareParentForShadow(vh.mGridView);
281        vh.getGridView().setFocusDrawingOrderEnabled(mShadowOverlayHelper.getShadowType()
282                == ShadowOverlayHelper.SHADOW_STATIC);
283        FocusHighlightHelper.setupBrowseItemFocusHighlight(vh.mItemBridgeAdapter,
284                mFocusZoomFactor, mUseFocusDimmer);
285
286        final ViewHolder gridViewHolder = vh;
287        vh.getGridView().setOnChildSelectedListener(new OnChildSelectedListener() {
288            @Override
289            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
290                selectChildView(gridViewHolder, view);
291            }
292        });
293    }
294
295    /**
296     * Set if keeps foreground of child of this grid, the foreground will not
297     * be used for overlay color.  Default value is true.
298     *
299     * @param keep   True if keep foreground of child of this grid.
300     */
301    public final void setKeepChildForeground(boolean keep) {
302        mKeepChildForeground = keep;
303    }
304
305    /**
306     * Returns true if keeps foreground of child of this grid, the foreground will not
307     * be used for overlay color.  Default value is true.
308     *
309     * @return   True if keeps foreground of child of this grid.
310     */
311    public final boolean getKeepChildForeground() {
312        return mKeepChildForeground;
313    }
314
315    /**
316     * Create ShadowOverlayHelper Options.  Subclass may override.
317     * e.g.
318     * <code>
319     * return new ShadowOverlayHelper.Options().roundedCornerRadius(10);
320     * </code>
321     *
322     * @return   The options to be used for shadow, overlay and rouded corner.
323     */
324    protected ShadowOverlayHelper.Options createShadowOverlayOptions() {
325        return ShadowOverlayHelper.Options.DEFAULT;
326    }
327
328    @Override
329    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
330        if (DEBUG) Log.v(TAG, "onBindViewHolder " + item);
331        ViewHolder vh = (ViewHolder) viewHolder;
332        vh.mItemBridgeAdapter.setAdapter((ObjectAdapter) item);
333        vh.getGridView().setAdapter(vh.mItemBridgeAdapter);
334    }
335
336    @Override
337    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
338        if (DEBUG) Log.v(TAG, "onUnbindViewHolder");
339        ViewHolder vh = (ViewHolder) viewHolder;
340        vh.mItemBridgeAdapter.setAdapter(null);
341        vh.getGridView().setAdapter(null);
342    }
343
344    /**
345     * Sets the item selected listener.
346     * Since this is a grid the row parameter is always null.
347     */
348    public final void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
349        mOnItemViewSelectedListener = listener;
350    }
351
352    /**
353     * Returns the item selected listener.
354     */
355    public final OnItemViewSelectedListener getOnItemViewSelectedListener() {
356        return mOnItemViewSelectedListener;
357    }
358
359    /**
360     * Sets the item clicked listener.
361     * OnItemViewClickedListener will override {@link View.OnClickListener} that
362     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
363     * So in general, developer should choose one of the listeners but not both.
364     */
365    public final void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
366        mOnItemViewClickedListener = listener;
367    }
368
369    /**
370     * Returns the item clicked listener.
371     */
372    public final OnItemViewClickedListener getOnItemViewClickedListener() {
373        return mOnItemViewClickedListener;
374    }
375
376    private void selectChildView(ViewHolder vh, View view) {
377        if (getOnItemViewSelectedListener() != null) {
378            ItemBridgeAdapter.ViewHolder ibh = (view == null) ? null :
379                    (ItemBridgeAdapter.ViewHolder) vh.getGridView().getChildViewHolder(view);
380            if (ibh == null) {
381                getOnItemViewSelectedListener().onItemSelected(null, null, null, null);
382            } else {
383                getOnItemViewSelectedListener().onItemSelected(ibh.mHolder, ibh.mItem, null, null);
384            }
385        }
386    }
387
388    /**
389     * Changes the visibility of views.  The entrance transition will be run against the views that
390     * change visibilities.  This method is called by the fragment, it should not be called
391     * directly by the application.
392     *
393     * @param holder         The ViewHolder for the vertical grid.
394     * @param afterEntrance  true if children of vertical grid participating in entrance transition
395     *                       should be set to visible, false otherwise.
396     */
397    public void setEntranceTransitionState(VerticalGridPresenter.ViewHolder holder,
398            boolean afterEntrance) {
399        holder.mGridView.setChildrenVisibility(afterEntrance? View.VISIBLE : View.INVISIBLE);
400    }
401}
402