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