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