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