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