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