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        public void onBind(final ItemBridgeAdapter.ViewHolder itemViewHolder) {
35            // Only when having an OnItemClickListner, we attach the OnClickListener.
36            if (getOnItemViewClickedListener() != null) {
37                final View itemView = itemViewHolder.mHolder.view;
38                itemView.setOnClickListener(new View.OnClickListener() {
39                    @Override
40                    public void onClick(View view) {
41                        if (getOnItemViewClickedListener() != null) {
42                            // Row is always null
43                            getOnItemViewClickedListener().onItemClicked(
44                                    itemViewHolder.mHolder, itemViewHolder.mItem, null, null);
45                        }
46                    }
47                });
48            }
49        }
50
51        @Override
52        public void onUnbind(ItemBridgeAdapter.ViewHolder viewHolder) {
53            if (getOnItemViewClickedListener() != null) {
54                viewHolder.mHolder.view.setOnClickListener(null);
55            }
56        }
57
58        @Override
59        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
60            viewHolder.itemView.setActivated(true);
61        }
62    }
63
64    /**
65     * ViewHolder for the VerticalGridPresenter.
66     */
67    public static class ViewHolder extends Presenter.ViewHolder {
68        ItemBridgeAdapter mItemBridgeAdapter;
69        final VerticalGridView mGridView;
70        boolean mInitialized;
71
72        public ViewHolder(VerticalGridView view) {
73            super(view);
74            mGridView = view;
75        }
76
77        public VerticalGridView getGridView() {
78            return mGridView;
79        }
80    }
81
82    private int mNumColumns = -1;
83    private int mFocusZoomFactor;
84    private boolean mUseFocusDimmer;
85    private boolean mShadowEnabled = true;
86    private OnItemViewSelectedListener mOnItemViewSelectedListener;
87    private OnItemViewClickedListener mOnItemViewClickedListener;
88    private boolean mRoundedCornersEnabled = true;
89
90    /**
91     * Constructs a VerticalGridPresenter with defaults.
92     * Uses {@link FocusHighlight#ZOOM_FACTOR_MEDIUM} for focus zooming and
93     * enabled dimming on focus.
94     */
95    public VerticalGridPresenter() {
96        this(FocusHighlight.ZOOM_FACTOR_LARGE);
97    }
98
99    /**
100     * Constructs a VerticalGridPresenter with the given parameters.
101     *
102     * @param focusZoomFactor Controls the zoom factor used when an item view is focused. One of
103     *         {@link FocusHighlight#ZOOM_FACTOR_NONE},
104     *         {@link FocusHighlight#ZOOM_FACTOR_SMALL},
105     *         {@link FocusHighlight#ZOOM_FACTOR_XSMALL},
106     *         {@link FocusHighlight#ZOOM_FACTOR_MEDIUM},
107     *         {@link FocusHighlight#ZOOM_FACTOR_LARGE}
108     * enabled dimming on focus.
109     */
110    public VerticalGridPresenter(int focusZoomFactor) {
111        this(focusZoomFactor, true);
112    }
113
114    /**
115     * Constructs a VerticalGridPresenter with the given parameters.
116     *
117     * @param focusZoomFactor Controls the zoom factor used when an item view is focused. One of
118     *         {@link FocusHighlight#ZOOM_FACTOR_NONE},
119     *         {@link FocusHighlight#ZOOM_FACTOR_SMALL},
120     *         {@link FocusHighlight#ZOOM_FACTOR_XSMALL},
121     *         {@link FocusHighlight#ZOOM_FACTOR_MEDIUM},
122     *         {@link FocusHighlight#ZOOM_FACTOR_LARGE}
123     * @param useFocusDimmer determines if the FocusHighlighter will use the dimmer
124     */
125    public VerticalGridPresenter(int focusZoomFactor, boolean useFocusDimmer) {
126        mFocusZoomFactor = focusZoomFactor;
127        mUseFocusDimmer = useFocusDimmer;
128    }
129
130    /**
131     * Sets the number of columns in the vertical grid.
132     */
133    public void setNumberOfColumns(int numColumns) {
134        if (numColumns < 0) {
135            throw new IllegalArgumentException("Invalid number of columns");
136        }
137        if (mNumColumns != numColumns) {
138            mNumColumns = numColumns;
139        }
140    }
141
142    /**
143     * Returns the number of columns in the vertical grid.
144     */
145    public int getNumberOfColumns() {
146        return mNumColumns;
147    }
148
149    /**
150     * Enable or disable child shadow.
151     * This is not only for enable/disable default shadow implementation but also subclass must
152     * respect this flag.
153     */
154    public final void setShadowEnabled(boolean enabled) {
155        mShadowEnabled = enabled;
156    }
157
158    /**
159     * Returns true if child shadow is enabled.
160     * This is not only for enable/disable default shadow implementation but also subclass must
161     * respect this flag.
162     */
163    public final boolean getShadowEnabled() {
164        return mShadowEnabled;
165    }
166
167    /**
168     * Returns true if opticalBounds is supported (SDK >= 18) so that default shadow
169     * is applied to each individual child of {@link VerticalGridView}.
170     * Subclass may return false to disable.
171     */
172    public boolean isUsingDefaultShadow() {
173        return ShadowOverlayContainer.supportsShadow();
174    }
175
176    /**
177     * Enables or disabled rounded corners on children of this row.
178     * Supported on Android SDK >= L.
179     */
180    public final void enableChildRoundedCorners(boolean enable) {
181        mRoundedCornersEnabled = enable;
182    }
183
184    /**
185     * Returns true if rounded corners are enabled for children of this row.
186     */
187    public final boolean areChildRoundedCornersEnabled() {
188        return mRoundedCornersEnabled;
189    }
190
191    /**
192     * Returns true if SDK >= L, where Z shadow is enabled so that Z order is enabled
193     * on each child of vertical grid.   If subclass returns false in isUsingDefaultShadow()
194     * and does not use Z-shadow on SDK >= L, it should override isUsingZOrder() return false.
195     */
196    public boolean isUsingZOrder(Context context) {
197        return ShadowOverlayContainer.supportsDynamicShadow() &&
198                !Settings.getInstance(context).preferStaticShadows();
199    }
200
201    final boolean needsDefaultShadow() {
202        return isUsingDefaultShadow() && getShadowEnabled();
203    }
204
205    /**
206     * Returns the zoom factor used for focus highlighting.
207     */
208    public final int getFocusZoomFactor() {
209        return mFocusZoomFactor;
210    }
211
212    /**
213     * Returns true if the focus dimmer is used for focus highlighting; false otherwise.
214     */
215    public final boolean isFocusDimmerUsed() {
216        return mUseFocusDimmer;
217    }
218
219
220    @Override
221    public final ViewHolder onCreateViewHolder(ViewGroup parent) {
222        ViewHolder vh = createGridViewHolder(parent);
223        vh.mInitialized = false;
224        vh.mItemBridgeAdapter = new VerticalGridItemBridgeAdapter();
225        initializeGridViewHolder(vh);
226        if (!vh.mInitialized) {
227            throw new RuntimeException("super.initializeGridViewHolder() must be called");
228        }
229        return vh;
230    }
231
232    /**
233     * Subclass may override this to inflate a different layout.
234     */
235    protected ViewHolder createGridViewHolder(ViewGroup parent) {
236        View root = LayoutInflater.from(parent.getContext()).inflate(
237                R.layout.lb_vertical_grid, parent, false);
238        return new ViewHolder((VerticalGridView) root.findViewById(R.id.browse_grid));
239    }
240
241    private ItemBridgeAdapter.Wrapper mWrapper = new ItemBridgeAdapter.Wrapper() {
242        @Override
243        public View createWrapper(View root) {
244            ShadowOverlayContainer wrapper = new ShadowOverlayContainer(root.getContext());
245            wrapper.setLayoutParams(
246                    new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
247            wrapper.initialize(needsDefaultShadow(), true, areChildRoundedCornersEnabled());
248            return wrapper;
249        }
250        @Override
251        public void wrap(View wrapper, View wrapped) {
252            ((ShadowOverlayContainer) wrapper).wrap(wrapped);
253        }
254    };
255
256    /**
257     * Called after a {@link VerticalGridPresenter.ViewHolder} is created.
258     * Subclasses may override this method and start by calling
259     * super.initializeGridViewHolder(ViewHolder).
260     *
261     * @param vh The ViewHolder to initialize for the vertical grid.
262     */
263    protected void initializeGridViewHolder(ViewHolder vh) {
264        if (mNumColumns == -1) {
265            throw new IllegalStateException("Number of columns must be set");
266        }
267        if (DEBUG) Log.v(TAG, "mNumColumns " + mNumColumns);
268        vh.getGridView().setNumColumns(mNumColumns);
269        vh.mInitialized = true;
270
271        vh.mItemBridgeAdapter.setWrapper(mWrapper);
272        if (needsDefaultShadow() || areChildRoundedCornersEnabled()) {
273            ShadowOverlayContainer.prepareParentForShadow(vh.getGridView());
274            ((ViewGroup) vh.view).setClipChildren(false);
275        }
276        vh.getGridView().setFocusDrawingOrderEnabled(!isUsingZOrder(vh.getGridView().getContext()));
277        FocusHighlightHelper.setupBrowseItemFocusHighlight(vh.mItemBridgeAdapter,
278                mFocusZoomFactor, mUseFocusDimmer);
279
280        final ViewHolder gridViewHolder = vh;
281        vh.getGridView().setOnChildSelectedListener(new OnChildSelectedListener() {
282            @Override
283            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
284                selectChildView(gridViewHolder, view);
285            }
286        });
287    }
288
289    @Override
290    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
291        if (DEBUG) Log.v(TAG, "onBindViewHolder " + item);
292        ViewHolder vh = (ViewHolder) viewHolder;
293        vh.mItemBridgeAdapter.setAdapter((ObjectAdapter) item);
294        vh.getGridView().setAdapter(vh.mItemBridgeAdapter);
295    }
296
297    @Override
298    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
299        if (DEBUG) Log.v(TAG, "onUnbindViewHolder");
300        ViewHolder vh = (ViewHolder) viewHolder;
301        vh.mItemBridgeAdapter.setAdapter(null);
302        vh.getGridView().setAdapter(null);
303    }
304
305    /**
306     * Sets the item selected listener.
307     * Since this is a grid the row parameter is always null.
308     */
309    public final void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
310        mOnItemViewSelectedListener = listener;
311    }
312
313    /**
314     * Returns the item selected listener.
315     */
316    public final OnItemViewSelectedListener getOnItemViewSelectedListener() {
317        return mOnItemViewSelectedListener;
318    }
319
320    /**
321     * Sets the item clicked listener.
322     * OnItemViewClickedListener will override {@link View.OnClickListener} that
323     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
324     * So in general, developer should choose one of the listeners but not both.
325     */
326    public final void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
327        mOnItemViewClickedListener = listener;
328    }
329
330    /**
331     * Returns the item clicked listener.
332     */
333    public final OnItemViewClickedListener getOnItemViewClickedListener() {
334        return mOnItemViewClickedListener;
335    }
336
337    private void selectChildView(ViewHolder vh, View view) {
338        if (getOnItemViewSelectedListener() != null) {
339            ItemBridgeAdapter.ViewHolder ibh = (view == null) ? null :
340                    (ItemBridgeAdapter.ViewHolder) vh.getGridView().getChildViewHolder(view);
341            if (ibh == null) {
342                getOnItemViewSelectedListener().onItemSelected(null, null, null, null);
343            } else {
344                getOnItemViewSelectedListener().onItemSelected(ibh.mHolder, ibh.mItem, null, null);
345            }
346        }
347    }
348}
349