VerticalGridFragment.java revision afe1da48b4673f64c67c130237dd485a34a69c1b
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.app;
15
16import android.support.v17.leanback.R;
17import android.support.v17.leanback.widget.OnItemViewClickedListener;
18import android.support.v17.leanback.widget.OnItemViewSelectedListener;
19import android.support.v17.leanback.widget.Presenter;
20import android.support.v17.leanback.widget.Row;
21import android.support.v17.leanback.widget.RowPresenter;
22import android.support.v17.leanback.widget.TitleView;
23import android.support.v17.leanback.widget.VerticalGridPresenter;
24import android.support.v17.leanback.widget.ObjectAdapter;
25import android.support.v17.leanback.widget.OnItemClickedListener;
26import android.support.v17.leanback.widget.OnItemSelectedListener;
27import android.support.v17.leanback.widget.SearchOrbView;
28import android.app.Fragment;
29import android.graphics.drawable.Drawable;
30import android.os.Bundle;
31import android.util.Log;
32import android.view.LayoutInflater;
33import android.view.View;
34import android.view.ViewGroup;
35import android.view.ViewGroup.MarginLayoutParams;
36import android.widget.ImageView;
37import android.widget.TextView;
38
39/**
40 * A fragment for creating leanback vertical grids.
41 *
42 * <p>Renders a vertical grid of objects given a {@link VerticalGridPresenter} and
43 * an {@link ObjectAdapter}.
44 */
45public class VerticalGridFragment extends Fragment {
46    private static final String TAG = "VerticalGridFragment";
47    private static boolean DEBUG = false;
48
49    private BrowseFrameLayout mBrowseFrame;
50    private String mTitle;
51    private Drawable mBadgeDrawable;
52    private ObjectAdapter mAdapter;
53    private VerticalGridPresenter mGridPresenter;
54    private VerticalGridPresenter.ViewHolder mGridViewHolder;
55    private OnItemSelectedListener mOnItemSelectedListener;
56    private OnItemClickedListener mOnItemClickedListener;
57    private OnItemViewSelectedListener mOnItemViewSelectedListener;
58    private OnItemViewClickedListener mOnItemViewClickedListener;
59    private View.OnClickListener mExternalOnSearchClickedListener;
60    private int mSelectedPosition = -1;
61
62    private TitleView mTitleView;
63    private SearchOrbView.Colors mSearchAffordanceColors;
64    private boolean mSearchAffordanceColorSet;
65    private boolean mShowingTitle = true;
66
67    // transition related
68    private static TransitionHelper sTransitionHelper = TransitionHelper.getInstance();
69    private Object mTitleUpTransition;
70    private Object mTitleDownTransition;
71    private Object mSceneWithTitle;
72    private Object mSceneWithoutTitle;
73
74    /**
75     * Sets the badge drawable displayed in the title area.
76     */
77    public void setBadgeDrawable(Drawable drawable) {
78        if (drawable != mBadgeDrawable) {
79            mBadgeDrawable = drawable;
80            if (mTitleView != null) {
81                mTitleView.setBadgeDrawable(drawable);
82            }
83        }
84    }
85
86    /**
87     * Returns the badge drawable.
88     */
89    public Drawable getBadgeDrawable() {
90        return mBadgeDrawable;
91    }
92
93    /**
94     * Sets a title for the fragment.
95     */
96    public void setTitle(String title) {
97        mTitle = title;
98        if (mTitleView != null) {
99            mTitleView.setTitle(mTitle);
100        }
101    }
102
103    /**
104     * Returns the title for the fragment.
105     */
106    public String getTitle() {
107        return mTitle;
108    }
109
110    /**
111     * Sets the grid presenter.
112     */
113    public void setGridPresenter(VerticalGridPresenter gridPresenter) {
114        if (gridPresenter == null) {
115            throw new IllegalArgumentException("Grid presenter may not be null");
116        }
117        mGridPresenter = gridPresenter;
118        mGridPresenter.setOnItemViewSelectedListener(mRowSelectedListener);
119        if (mOnItemViewClickedListener != null) {
120            mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
121        }
122        if (mOnItemClickedListener != null) {
123            mGridPresenter.setOnItemClickedListener(mOnItemClickedListener);
124        }
125    }
126
127    /**
128     * Returns the grid presenter.
129     */
130    public VerticalGridPresenter getGridPresenter() {
131        return mGridPresenter;
132    }
133
134    /**
135     * Sets the object adapter for the fragment.
136     */
137    public void setAdapter(ObjectAdapter adapter) {
138        mAdapter = adapter;
139        updateAdapter();
140    }
141
142    /**
143     * Returns the object adapter.
144     */
145    public ObjectAdapter getAdapter() {
146        return mAdapter;
147    }
148
149    final private OnItemViewSelectedListener mRowSelectedListener =
150            new OnItemViewSelectedListener() {
151        @Override
152        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
153                RowPresenter.ViewHolder rowViewHolder, Row row) {
154            int position = mGridViewHolder.getGridView().getSelectedPosition();
155            if (DEBUG) Log.v(TAG, "row selected position " + position);
156            onRowSelected(position);
157            if (mOnItemSelectedListener != null) {
158                mOnItemSelectedListener.onItemSelected(item, row);
159            }
160            if (mOnItemViewSelectedListener != null) {
161                mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
162                        rowViewHolder, row);
163            }
164        }
165    };
166
167    /**
168     * Sets an item selection listener.
169     * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
170     */
171    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
172        mOnItemSelectedListener = listener;
173    }
174
175    /**
176     * Sets an item selection listener.
177     */
178    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
179        mOnItemViewSelectedListener = listener;
180    }
181
182    private void onRowSelected(int position) {
183        if (position != mSelectedPosition) {
184            if (!mGridViewHolder.getGridView().hasPreviousViewInSameRow(position)) {
185                // if has no sibling in front of it,  show title
186                if (!mShowingTitle) {
187                    sTransitionHelper.runTransition(mSceneWithTitle, mTitleDownTransition);
188                    mShowingTitle = true;
189                }
190            } else if (mShowingTitle) {
191                sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleUpTransition);
192                mShowingTitle = false;
193            }
194            mSelectedPosition = position;
195        }
196    }
197
198    /**
199     * Sets an item clicked listener.
200     * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
201     */
202    public void setOnItemClickedListener(OnItemClickedListener listener) {
203        mOnItemClickedListener = listener;
204        if (mGridPresenter != null) {
205            mGridPresenter.setOnItemClickedListener(mOnItemClickedListener);
206        }
207    }
208
209    /**
210     * Returns the item clicked listener.
211     * @deprecated Use {@link #getOnItemViewClickedListener()}
212     */
213    public OnItemClickedListener getOnItemClickedListener() {
214        return mOnItemClickedListener;
215    }
216
217    /**
218     * Sets an item clicked listener.
219     */
220    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
221        mOnItemViewClickedListener = listener;
222        if (mGridPresenter != null) {
223            mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
224        }
225    }
226
227    /**
228     * Returns the item clicked listener.
229     */
230    public OnItemViewClickedListener getOnItemViewClickedListener() {
231        return mOnItemViewClickedListener;
232    }
233
234    /**
235     * Sets a click listener for the search affordance.
236     *
237     * <p>The presence of a listener will change the visibility of the search
238     * affordance in the title area. When set to non-null, the title area will
239     * contain a call to search action.
240     *
241     * <p>The listener's onClick method will be invoked when the user clicks on
242     * the search action.
243     *
244     * @param listener The listener to invoke when the search affordance is
245     *        clicked, or null to hide the search affordance.
246     */
247    public void setOnSearchClickedListener(View.OnClickListener listener) {
248        mExternalOnSearchClickedListener = listener;
249        if (mTitleView != null) {
250            mTitleView.setOnSearchClickedListener(listener);
251        }
252    }
253
254    /**
255     * Sets the {@link SearchOrbView.Colors} used to draw the search affordance.
256     */
257    public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
258        mSearchAffordanceColors = colors;
259        mSearchAffordanceColorSet = true;
260        if (mTitleView != null) {
261            mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
262        }
263    }
264
265    /**
266     * Returns the {@link SearchOrbView.Colors} used to draw the search affordance.
267     */
268    public SearchOrbView.Colors getSearchAffordanceColors() {
269        if (mSearchAffordanceColorSet) {
270            return mSearchAffordanceColors;
271        }
272        if (mTitleView == null) {
273            throw new IllegalStateException("Fragment views not yet created");
274        }
275        return mTitleView.getSearchAffordanceColors();
276    }
277
278    /**
279     * Sets the color used to draw the search affordance.
280     * A default brighter color will be set by the framework.
281     *
282     * @param color The color to use for the search affordance.
283     */
284    public void setSearchAffordanceColor(int color) {
285        setSearchAffordanceColors(new SearchOrbView.Colors(color));
286    }
287
288    /**
289     * Returns the color used to draw the search affordance.
290     */
291    public int getSearchAffordanceColor() {
292        return getSearchAffordanceColors().color;
293    }
294
295    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
296            new BrowseFrameLayout.OnFocusSearchListener() {
297        @Override
298        public View onFocusSearch(View focused, int direction) {
299            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
300
301            final View searchOrbView = mTitleView.getSearchAffordanceView();
302            if (focused == searchOrbView && (
303                    direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT)) {
304                return mGridViewHolder.view;
305
306            } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
307                    && direction == View.FOCUS_UP) {
308                return searchOrbView;
309
310            } else {
311                return null;
312            }
313        }
314    };
315
316    @Override
317    public View onCreateView(LayoutInflater inflater, ViewGroup container,
318            Bundle savedInstanceState) {
319        ViewGroup root = (ViewGroup) inflater.inflate(R.layout.lb_vertical_grid_fragment,
320                container, false);
321
322        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
323        mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
324
325        mTitleView = (TitleView) root.findViewById(R.id.browse_title_group);
326        mTitleView.setBadgeDrawable(mBadgeDrawable);
327        mTitleView.setTitle(mTitle);
328        if (mSearchAffordanceColorSet) {
329            mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
330        }
331        if (mExternalOnSearchClickedListener != null) {
332            mTitleView.setOnSearchClickedListener(mExternalOnSearchClickedListener);
333        }
334
335        mSceneWithTitle = sTransitionHelper.createScene(root, new Runnable() {
336            @Override
337            public void run() {
338                TitleTransitionHelper.showTitle(mTitleView, true);
339            }
340        });
341        mSceneWithoutTitle = sTransitionHelper.createScene(root, new Runnable() {
342            @Override
343            public void run() {
344                TitleTransitionHelper.showTitle(mTitleView, false);
345            }
346        });
347        mTitleUpTransition = TitleTransitionHelper.createTransitionTitleUp(sTransitionHelper);
348        mTitleDownTransition = TitleTransitionHelper.createTransitionTitleDown(sTransitionHelper);
349        sTransitionHelper.excludeChildren(mTitleUpTransition, R.id.browse_grid_dock, true);
350        sTransitionHelper.excludeChildren(mTitleDownTransition, R.id.browse_grid_dock, true);
351
352        return root;
353    }
354
355    @Override
356    public void onViewCreated(View view, Bundle savedInstanceState) {
357        ViewGroup gridDock = (ViewGroup) view.findViewById(R.id.browse_grid_dock);
358        mGridViewHolder = mGridPresenter.onCreateViewHolder(gridDock);
359        gridDock.addView(mGridViewHolder.view);
360
361        updateAdapter();
362    }
363
364    @Override
365    public void onStart() {
366        super.onStart();
367        mGridViewHolder.getGridView().requestFocus();
368    }
369
370    @Override
371    public void onDestroyView() {
372        super.onDestroyView();
373        mGridViewHolder = null;
374    }
375
376    /**
377     * Sets the selected item position.
378     */
379    public void setSelectedPosition(int position) {
380        mSelectedPosition = position;
381        if(mGridViewHolder != null && mGridViewHolder.getGridView().getAdapter() != null) {
382            mGridViewHolder.getGridView().setSelectedPositionSmooth(position);
383        }
384    }
385
386    private void updateAdapter() {
387        if (mGridViewHolder != null) {
388            mGridPresenter.onBindViewHolder(mGridViewHolder, mAdapter);
389            if (mSelectedPosition != -1) {
390                mGridViewHolder.getGridView().setSelectedPosition(mSelectedPosition);
391            }
392        }
393    }
394}
395