BrowseFragment.java revision 056561d81012983d2d27b8665c4aeabb427174cb
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.HorizontalGridView;
18import android.support.v17.leanback.widget.Presenter;
19import android.support.v17.leanback.widget.PresenterSelector;
20import android.support.v17.leanback.widget.VerticalGridView;
21import android.support.v17.leanback.widget.Row;
22import android.support.v17.leanback.widget.ObjectAdapter;
23import android.support.v17.leanback.widget.OnItemSelectedListener;
24import android.support.v17.leanback.widget.OnItemClickedListener;
25import android.support.v17.leanback.widget.SearchOrbView;
26import android.support.v7.widget.RecyclerView;
27import android.util.Log;
28import android.util.SparseIntArray;
29import android.util.TypedValue;
30import android.app.Activity;
31import android.app.Fragment;
32import android.app.FragmentManager;
33import android.app.FragmentManager.BackStackEntry;
34import android.content.res.TypedArray;
35import android.os.Bundle;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.view.View.OnClickListener;
39import android.view.ViewGroup;
40import android.view.ViewGroup.MarginLayoutParams;
41import android.view.animation.DecelerateInterpolator;
42import android.widget.ImageView;
43import android.widget.TextView;
44import android.graphics.Color;
45import android.graphics.drawable.Drawable;
46
47import java.util.ArrayList;
48
49import static android.support.v7.widget.RecyclerView.NO_POSITION;
50
51/**
52 * Wrapper fragment for leanback browse screens. Composed of a
53 * RowsFragment and a HeadersFragment.
54 * <p>
55 * The fragment comes with default back key support to show headers.
56 * For app customized {@link Activity#onBackPressed()}, app must disable
57 * BrowseFragment's default back key support by calling
58 * {@link #setHeadersTransitionOnBackEnabled(boolean)} with false and use
59 * {@link BrowseFragment.BrowseTransitionListener} and {@link #startHeadersTransition(boolean)}.
60 */
61public class BrowseFragment extends Fragment {
62
63    public static class Params {
64        private String mTitle;
65        private Drawable mBadgeDrawable;
66        private int mHeadersState;
67
68        /**
69         * Sets the badge image.
70         */
71        public void setBadgeImage(Drawable drawable) {
72            mBadgeDrawable = drawable;
73        }
74
75        /**
76         * Returns the badge image.
77         */
78        public Drawable getBadgeImage() {
79            return mBadgeDrawable;
80        }
81
82        /**
83         * Sets a title for the browse fragment.
84         */
85        public void setTitle(String title) {
86            mTitle = title;
87        }
88
89        /**
90         * Returns the title for the browse fragment.
91         */
92        public String getTitle() {
93            return mTitle;
94        }
95
96        /**
97         * Sets the state for the headers column in the browse fragment.
98         */
99        public void setHeadersState(int headersState) {
100            if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) {
101                Log.e(TAG, "Invalid headers state: " + headersState
102                        + ", default to enabled and shown.");
103                mHeadersState = HEADERS_ENABLED;
104            } else {
105                mHeadersState = headersState;
106            }
107        }
108
109        /**
110         * Returns the state for the headers column in the browse fragment.
111         */
112        public int getHeadersState() {
113            return mHeadersState;
114        }
115    }
116
117    final class BackStackListener implements FragmentManager.OnBackStackChangedListener {
118        int mLastEntryCount;
119        int mIndexOfHeadersBackStack;
120
121        BackStackListener() {
122            reset();
123        }
124
125        void reset() {
126            mLastEntryCount = getFragmentManager().getBackStackEntryCount();
127            mIndexOfHeadersBackStack = -1;
128        }
129
130        @Override
131        public void onBackStackChanged() {
132            int count = getFragmentManager().getBackStackEntryCount();
133            // if backstack is growing and last pushed entry is "headers" backstack,
134            // remember the index of the entry.
135            if (count > mLastEntryCount) {
136                BackStackEntry entry = getFragmentManager().getBackStackEntryAt(count - 1);
137                if (mWithHeadersBackStackName.equals(entry.getName())) {
138                    mIndexOfHeadersBackStack = count - 1;
139                }
140            } else if (count < mLastEntryCount) {
141                // if popped "headers" backstack, initiate the show header transition if needed
142                if (mIndexOfHeadersBackStack >= count) {
143                    if (!mShowingHeaders) {
144                        startHeadersTransitionInternal(true);
145                    }
146                }
147            }
148            mLastEntryCount = count;
149        }
150    }
151
152    /**
153     * Listener for browse transitions.
154     */
155    public static class BrowseTransitionListener {
156        /**
157         * Callback when headers transition starts.
158         */
159        public void onHeadersTransitionStart(boolean withHeaders) {
160        }
161        /**
162         * Callback when headers transition stops.
163         */
164        public void onHeadersTransitionStop(boolean withHeaders) {
165        }
166    }
167
168    private static final String TAG = "BrowseFragment";
169
170    private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_";
171
172    private static boolean DEBUG = false;
173
174    /** The headers fragment is enabled and shown by default. */
175    public static final int HEADERS_ENABLED = 1;
176
177    /** The headers fragment is enabled and hidden by default. */
178    public static final int HEADERS_HIDDEN = 2;
179
180    /** The headers fragment is disabled and will never be shown. */
181    public static final int HEADERS_DISABLED = 3;
182
183    private static final float SLIDE_DISTANCE_FACTOR = 2;
184
185    private RowsFragment mRowsFragment;
186    private HeadersFragment mHeadersFragment;
187
188    private ObjectAdapter mAdapter;
189
190    private Params mParams;
191    private int mBrandColor = Color.TRANSPARENT;
192    private boolean mBrandColorSet;
193
194    private BrowseFrameLayout mBrowseFrame;
195    private ImageView mBadgeView;
196    private TextView mTitleView;
197    private ViewGroup mBrowseTitle;
198    private SearchOrbView mSearchOrbView;
199    private boolean mShowingTitle = true;
200    private boolean mHeadersBackStackEnabled = true;
201    private String mWithHeadersBackStackName;
202    private boolean mShowingHeaders = true;
203    private boolean mCanShowHeaders = true;
204    private int mContainerListMarginLeft;
205    private int mContainerListAlignTop;
206    private int mSearchAffordanceColor;
207    private boolean mSearchAffordanceColorSet;
208    private OnItemSelectedListener mExternalOnItemSelectedListener;
209    private OnClickListener mExternalOnSearchClickedListener;
210    private OnItemClickedListener mOnItemClickedListener;
211    private int mSelectedPosition = -1;
212
213    private PresenterSelector mHeaderPresenterSelector;
214
215    // transition related:
216    private static TransitionHelper sTransitionHelper = TransitionHelper.getInstance();
217    private int mReparentHeaderId = View.generateViewId();
218    private Object mSceneWithTitle;
219    private Object mSceneWithoutTitle;
220    private Object mSceneWithHeaders;
221    private Object mSceneWithoutHeaders;
222    private Object mTitleUpTransition;
223    private Object mTitleDownTransition;
224    private Object mHeadersTransition;
225    private int mHeadersTransitionStartDelay;
226    private int mHeadersTransitionDuration;
227    private BackStackListener mBackStackChangedListener;
228    private BrowseTransitionListener mBrowseTransitionListener;
229
230    private static final String ARG_TITLE = BrowseFragment.class.getCanonicalName() + ".title";
231    private static final String ARG_BADGE_URI = BrowseFragment.class.getCanonicalName() + ".badge";
232    private static final String ARG_HEADERS_STATE =
233        BrowseFragment.class.getCanonicalName() + ".headersState";
234
235    /**
236     * @param args Bundle to use for the arguments, if null a new Bundle will be created.
237     */
238    public static Bundle createArgs(Bundle args, String title, String badgeUri) {
239        return createArgs(args, title, badgeUri, HEADERS_ENABLED);
240    }
241
242    public static Bundle createArgs(Bundle args, String title, String badgeUri, int headersState) {
243        if (args == null) {
244            args = new Bundle();
245        }
246        args.putString(ARG_TITLE, title);
247        args.putString(ARG_BADGE_URI, badgeUri);
248        args.putInt(ARG_HEADERS_STATE, headersState);
249        return args;
250    }
251
252    /**
253     * Set browse parameters.
254     */
255    public void setBrowseParams(Params params) {
256        mParams = params;
257        setBadgeDrawable(mParams.mBadgeDrawable);
258        setTitle(mParams.mTitle);
259        setHeadersState(mParams.mHeadersState);
260    }
261
262    /**
263     * Returns browse parameters.
264     */
265    public Params getBrowseParams() {
266        return mParams;
267    }
268
269    /**
270     * Sets the brand color for the browse fragment.
271     */
272    public void setBrandColor(int color) {
273        mBrandColor = color;
274        mBrandColorSet = true;
275
276        if (mHeadersFragment != null) {
277            mHeadersFragment.setBackgroundColor(mBrandColor);
278        }
279    }
280
281    /**
282     * Returns the brand color for the browse fragment.
283     * The default is transparent.
284     */
285    public int getBrandColor() {
286        return mBrandColor;
287    }
288
289    /**
290     * Sets the list of rows for the fragment.
291     */
292    public void setAdapter(ObjectAdapter adapter) {
293        mAdapter = adapter;
294        if (mRowsFragment != null) {
295            mRowsFragment.setAdapter(adapter);
296            mHeadersFragment.setAdapter(adapter);
297        }
298    }
299
300    /**
301     * Returns the list of rows.
302     */
303    public ObjectAdapter getAdapter() {
304        return mAdapter;
305    }
306
307    /**
308     * Sets an item selection listener.
309     */
310    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
311        mExternalOnItemSelectedListener = listener;
312    }
313
314    /**
315     * Sets an item clicked listener on the fragment.
316     * OnItemClickedListener will override {@link View.OnClickListener} that
317     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
318     * So in general,  developer should choose one of the listeners but not both.
319     */
320    public void setOnItemClickedListener(OnItemClickedListener listener) {
321        mOnItemClickedListener = listener;
322        if (mRowsFragment != null) {
323            mRowsFragment.setOnItemClickedListener(listener);
324        }
325    }
326
327    /**
328     * Returns the item Clicked listener.
329     */
330    public OnItemClickedListener getOnItemClickedListener() {
331        return mOnItemClickedListener;
332    }
333
334    /**
335     * Sets a click listener for the search affordance.
336     *
337     * The presence of a listener will change the visibility of the search affordance in the
338     * title area. When set to non-null the title area will contain a call to search action.
339     *
340     * The listener onClick method will be invoked when the user click on the search action.
341     *
342     * @param listener The listener.
343     */
344    public void setOnSearchClickedListener(View.OnClickListener listener) {
345        mExternalOnSearchClickedListener = listener;
346        if (mSearchOrbView != null) {
347            mSearchOrbView.setOnOrbClickedListener(listener);
348        }
349    }
350
351    /**
352     * Sets the color used to draw the search affordance.
353     */
354    public void setSearchAffordanceColor(int color) {
355        mSearchAffordanceColor = color;
356        mSearchAffordanceColorSet = true;
357
358        if (mSearchOrbView != null) {
359            mSearchOrbView.setOrbColor(mSearchAffordanceColor);
360        }
361    }
362
363    /**
364     * Returns the color used to draw the search affordance.
365     * Can be called only after an activity has been attached.
366     */
367    public int getSearchAffordanceColor() {
368        if (getActivity() == null) {
369            throw new IllegalStateException("Activity must be attached");
370        }
371
372        if (mSearchAffordanceColorSet) {
373            return mSearchAffordanceColor;
374        }
375
376        TypedValue outValue = new TypedValue();
377        getActivity().getTheme().resolveAttribute(R.attr.defaultSearchColor, outValue, true);
378        return getResources().getColor(outValue.resourceId);
379    }
380
381    /**
382     * Start headers transition.
383     */
384    public void startHeadersTransition(boolean withHeaders) {
385        if (!mCanShowHeaders) {
386            throw new IllegalStateException("Cannot start headers transition");
387        }
388        if (isInHeadersTransition() || mShowingHeaders == withHeaders) {
389            return;
390        }
391        startHeadersTransitionInternal(withHeaders);
392    }
393
394    /**
395     * Returns true if headers transition is currently running.
396     */
397    public boolean isInHeadersTransition() {
398        return mHeadersTransition != null;
399    }
400
401    /**
402     * Returns true if headers is showing.
403     */
404    public boolean isShowingHeaders() {
405        return mShowingHeaders;
406    }
407
408    /**
409     * Set listener for browse fragment transitions.
410     */
411    public void setBrowseTransitionListener(BrowseTransitionListener listener) {
412        mBrowseTransitionListener = listener;
413    }
414
415    private void startHeadersTransitionInternal(boolean withHeaders) {
416        mShowingHeaders = withHeaders;
417        mRowsFragment.onTransitionStart();
418        mHeadersFragment.onTransitionStart();
419        createHeadersTransition();
420        if (mBrowseTransitionListener != null) {
421            mBrowseTransitionListener.onHeadersTransitionStart(withHeaders);
422        }
423        sTransitionHelper.runTransition(withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders,
424                mHeadersTransition);
425        if (mHeadersBackStackEnabled) {
426            if (!withHeaders) {
427                getFragmentManager().beginTransaction()
428                        .addToBackStack(mWithHeadersBackStackName).commit();
429            } else {
430                int count = getFragmentManager().getBackStackEntryCount();
431                if (count > 0) {
432                    BackStackEntry entry = getFragmentManager().getBackStackEntryAt(count - 1);
433                    if (mWithHeadersBackStackName.equals(entry.getName())) {
434                        getFragmentManager().popBackStack();
435                    }
436                }
437            }
438        }
439    }
440
441    private boolean isVerticalScrolling() {
442        // don't run transition
443        return mHeadersFragment.getVerticalGridView().getScrollState()
444                != HorizontalGridView.SCROLL_STATE_IDLE
445                || mRowsFragment.getVerticalGridView().getScrollState()
446                != HorizontalGridView.SCROLL_STATE_IDLE;
447    }
448
449    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
450            new BrowseFrameLayout.OnFocusSearchListener() {
451        @Override
452        public View onFocusSearch(View focused, int direction) {
453            // If headers fragment is disabled, just return null.
454            if (!mCanShowHeaders) return null;
455
456            // if headers is running transition,  focus stays
457            if (isInHeadersTransition()) return focused;
458            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
459            if (direction == View.FOCUS_LEFT) {
460                if (isVerticalScrolling() || mShowingHeaders) {
461                    return focused;
462                }
463                return mHeadersFragment.getVerticalGridView();
464            } else if (direction == View.FOCUS_RIGHT) {
465                if (isVerticalScrolling() || !mShowingHeaders) {
466                    return focused;
467                }
468                return mRowsFragment.getVerticalGridView();
469            } else if (focused == mSearchOrbView && direction == View.FOCUS_DOWN) {
470                return mShowingHeaders ? mHeadersFragment.getVerticalGridView() :
471                    mRowsFragment.getVerticalGridView();
472
473            } else if (focused != mSearchOrbView && mSearchOrbView.getVisibility() == View.VISIBLE
474                    && direction == View.FOCUS_UP) {
475                return mSearchOrbView;
476
477            } else {
478                return null;
479            }
480        }
481    };
482
483    private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener =
484            new BrowseFrameLayout.OnChildFocusListener() {
485        @Override
486        public void onRequestChildFocus(View child, View focused) {
487            int childId = child.getId();
488            if (!mCanShowHeaders || isInHeadersTransition()) return;
489            if (childId == R.id.browse_container_dock && mShowingHeaders) {
490                startHeadersTransitionInternal(false);
491            } else if (childId == R.id.browse_headers_dock && !mShowingHeaders) {
492                startHeadersTransitionInternal(true);
493            }
494        }
495    };
496
497    @Override
498    public void onCreate(Bundle savedInstanceState) {
499        super.onCreate(savedInstanceState);
500        TypedArray ta = getActivity().obtainStyledAttributes(R.styleable.LeanbackTheme);
501        mContainerListMarginLeft = (int) ta.getDimension(
502                R.styleable.LeanbackTheme_browseRowsMarginStart, 0);
503        mContainerListAlignTop = (int) ta.getDimension(
504                R.styleable.LeanbackTheme_browseRowsMarginTop, 0);
505        ta.recycle();
506
507        mHeadersTransitionStartDelay = getResources()
508                .getInteger(R.integer.lb_browse_headers_transition_delay);
509        mHeadersTransitionDuration = getResources()
510                .getInteger(R.integer.lb_browse_headers_transition_duration);
511
512        readArguments(getArguments());
513        if (mCanShowHeaders && mHeadersBackStackEnabled) {
514            mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this;
515            mBackStackChangedListener = new BackStackListener();
516            getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener);
517            if (!mShowingHeaders) {
518                getFragmentManager().beginTransaction()
519                        .addToBackStack(mWithHeadersBackStackName).commit();
520            }
521        }
522
523    }
524
525    @Override
526    public View onCreateView(LayoutInflater inflater, ViewGroup container,
527            Bundle savedInstanceState) {
528        if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) {
529            mRowsFragment = new RowsFragment();
530            mHeadersFragment = new HeadersFragment();
531            getChildFragmentManager().beginTransaction()
532                    .replace(R.id.browse_headers_dock, mHeadersFragment)
533                    .replace(R.id.browse_container_dock, mRowsFragment).commit();
534        } else {
535            mHeadersFragment = (HeadersFragment) getChildFragmentManager()
536                    .findFragmentById(R.id.browse_headers_dock);
537            mRowsFragment = (RowsFragment) getChildFragmentManager()
538                    .findFragmentById(R.id.browse_container_dock);
539        }
540
541        mHeadersFragment.setHeadersGone(!mCanShowHeaders);
542
543        mRowsFragment.setAdapter(mAdapter);
544        if (mHeaderPresenterSelector != null) {
545            mHeadersFragment.setPresenterSelector(mHeaderPresenterSelector);
546        }
547        mHeadersFragment.setAdapter(mAdapter);
548
549        mRowsFragment.setOnItemSelectedListener(mRowSelectedListener);
550        mHeadersFragment.setOnItemSelectedListener(mHeaderSelectedListener);
551        mHeadersFragment.setOnHeaderClickedListener(mHeaderClickedListener);
552        mRowsFragment.setOnItemClickedListener(mOnItemClickedListener);
553
554        View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);
555
556        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
557        mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
558        mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
559
560        mBrowseTitle = (ViewGroup) root.findViewById(R.id.browse_title_group);
561        mBadgeView = (ImageView) mBrowseTitle.findViewById(R.id.browse_badge);
562        mTitleView = (TextView) mBrowseTitle.findViewById(R.id.browse_title);
563        mSearchOrbView = (SearchOrbView) mBrowseTitle.findViewById(R.id.browse_orb);
564        mSearchOrbView.setOrbColor(getSearchAffordanceColor());
565        if (mExternalOnSearchClickedListener != null) {
566            mSearchOrbView.setOnOrbClickedListener(mExternalOnSearchClickedListener);
567        }
568
569        if (mParams != null) {
570            setBadgeDrawable(mParams.mBadgeDrawable);
571            setTitle(mParams.mTitle);
572            setHeadersState(mParams.mHeadersState);
573            if (mBrandColorSet) {
574                mHeadersFragment.setBackgroundColor(mBrandColor);
575            }
576        }
577
578        mSceneWithTitle = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
579            @Override
580            public void run() {
581                showTitle(true);
582            }
583        });
584        mSceneWithoutTitle = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
585            @Override
586            public void run() {
587                showTitle(false);
588            }
589        });
590        mSceneWithHeaders = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
591            @Override
592            public void run() {
593                showHeaders(true);
594            }
595        });
596        mSceneWithoutHeaders =  sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
597            @Override
598            public void run() {
599                showHeaders(false);
600            }
601        });
602        mTitleUpTransition = sTransitionHelper.createChangeBounds(false);
603        sTransitionHelper.setInterpolator(mTitleUpTransition, new DecelerateInterpolator(4));
604        mTitleDownTransition = sTransitionHelper.createChangeBounds(false);
605        sTransitionHelper.setInterpolator(mTitleDownTransition, new DecelerateInterpolator());
606
607        sTransitionHelper.excludeChildren(mTitleUpTransition, R.id.browse_headers, true);
608        sTransitionHelper.excludeChildren(mTitleDownTransition, R.id.browse_headers, true);
609        sTransitionHelper.excludeChildren(mTitleUpTransition, R.id.container_list, true);
610        sTransitionHelper.excludeChildren(mTitleDownTransition, R.id.container_list, true);
611
612        return root;
613    }
614
615    private void createHeadersTransition() {
616        mHeadersTransition = sTransitionHelper.createTransitionSet(false);
617        sTransitionHelper.excludeChildren(mHeadersTransition, R.id.browse_title_group, true);
618        Object changeBounds = sTransitionHelper.createChangeBounds(false);
619        Object fadeIn = sTransitionHelper.createFadeTransition(TransitionHelper.FADE_IN);
620        Object fadeOut = sTransitionHelper.createFadeTransition(TransitionHelper.FADE_OUT);
621
622        sTransitionHelper.setDuration(fadeOut, mHeadersTransitionDuration);
623        sTransitionHelper.addTransition(mHeadersTransition, fadeOut);
624        if (mShowingHeaders) {
625            sTransitionHelper.setStartDelay(changeBounds, mHeadersTransitionStartDelay);
626        }
627        sTransitionHelper.setDuration(changeBounds, mHeadersTransitionDuration);
628        sTransitionHelper.addTransition(mHeadersTransition, changeBounds);
629        sTransitionHelper.setDuration(fadeIn, mHeadersTransitionDuration);
630        sTransitionHelper.setStartDelay(fadeIn, mHeadersTransitionStartDelay);
631        sTransitionHelper.addTransition(mHeadersTransition, fadeIn);
632
633        sTransitionHelper.setTransitionListener(mHeadersTransition, new TransitionListener() {
634            @Override
635            public void onTransitionStart(Object transition) {
636            }
637            @Override
638            public void onTransitionEnd(Object transition) {
639                mHeadersTransition = null;
640                mRowsFragment.onTransitionEnd();
641                mHeadersFragment.onTransitionEnd();
642                if (mShowingHeaders) {
643                    VerticalGridView headerGridView = mHeadersFragment.getVerticalGridView();
644                    if (headerGridView != null && !headerGridView.hasFocus()) {
645                        headerGridView.requestFocus();
646                    }
647                } else {
648                    VerticalGridView rowsGridView = mRowsFragment.getVerticalGridView();
649                    if (rowsGridView != null && !rowsGridView.hasFocus()) {
650                        rowsGridView.requestFocus();
651                    }
652                }
653                if (mBrowseTransitionListener != null) {
654                    mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
655                }
656            }
657        });
658    }
659
660    public void setHeaderPresenterSelector(PresenterSelector headerPresenterSelector) {
661        mHeaderPresenterSelector = headerPresenterSelector;
662        if (mHeadersFragment != null) {
663            mHeadersFragment.setPresenterSelector(mHeaderPresenterSelector);
664        }
665    }
666
667    private void showTitle(boolean show) {
668        MarginLayoutParams lp = (MarginLayoutParams) mBrowseTitle.getLayoutParams();
669        lp.topMargin = show ? 0 : -mBrowseTitle.getHeight();
670        mBrowseTitle.setLayoutParams(lp);
671    }
672
673    private void showHeaders(boolean show) {
674        if (DEBUG) Log.v(TAG, "showHeaders " + show);
675        mHeadersFragment.setHeadersEnabled(show);
676        MarginLayoutParams lp;
677        View containerList;
678
679        containerList = mRowsFragment.getView();
680        lp = (MarginLayoutParams) containerList.getLayoutParams();
681        lp.leftMargin = show ? mContainerListMarginLeft : 0;
682        containerList.setLayoutParams(lp);
683
684        containerList = mHeadersFragment.getView();
685        lp = (MarginLayoutParams) containerList.getLayoutParams();
686        lp.leftMargin = show ? 0 : -mContainerListMarginLeft;
687        containerList.setLayoutParams(lp);
688
689        mRowsFragment.setExpand(!show);
690    }
691
692    private HeadersFragment.OnHeaderClickedListener mHeaderClickedListener =
693        new HeadersFragment.OnHeaderClickedListener() {
694            @Override
695            public void onHeaderClicked() {
696                if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) {
697                    return;
698                }
699                startHeadersTransitionInternal(false);
700                mRowsFragment.getVerticalGridView().requestFocus();
701            }
702        };
703
704    private OnItemSelectedListener mRowSelectedListener = new OnItemSelectedListener() {
705        @Override
706        public void onItemSelected(Object item, Row row) {
707            int position = mRowsFragment.getVerticalGridView().getSelectedPosition();
708            if (DEBUG) Log.v(TAG, "row selected position " + position);
709            onRowSelected(position);
710            if (mExternalOnItemSelectedListener != null) {
711                mExternalOnItemSelectedListener.onItemSelected(item, row);
712            }
713        }
714    };
715
716    private OnItemSelectedListener mHeaderSelectedListener = new OnItemSelectedListener() {
717        @Override
718        public void onItemSelected(Object item, Row row) {
719            int position = mHeadersFragment.getVerticalGridView().getSelectedPosition();
720            if (DEBUG) Log.v(TAG, "header selected position " + position);
721            onRowSelected(position);
722        }
723    };
724
725    private void onRowSelected(int position) {
726        if (position != mSelectedPosition) {
727            mSetSelectionRunnable.mPosition = position;
728            mBrowseFrame.getHandler().post(mSetSelectionRunnable);
729
730            if (getAdapter() == null || getAdapter().size() == 0 || position == 0) {
731                if (!mShowingTitle) {
732                    sTransitionHelper.runTransition(mSceneWithTitle, mTitleDownTransition);
733                    mShowingTitle = true;
734                }
735            } else if (mShowingTitle) {
736                sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleUpTransition);
737                mShowingTitle = false;
738            }
739        }
740    }
741
742    private class SetSelectionRunnable implements Runnable {
743        int mPosition;
744        @Override
745        public void run() {
746            setSelection(mPosition);
747        }
748    }
749
750    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
751
752    private void setSelection(int position) {
753        if (position != NO_POSITION) {
754            mRowsFragment.setSelectedPosition(position);
755            mHeadersFragment.setSelectedPosition(position);
756        }
757        mSelectedPosition = position;
758    }
759
760    @Override
761    public void onStart() {
762        super.onStart();
763        mHeadersFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
764        mHeadersFragment.setItemAlignment();
765        mRowsFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
766        mRowsFragment.setItemAlignment();
767
768        if (mCanShowHeaders && mShowingHeaders && mHeadersFragment.getView() != null) {
769            mHeadersFragment.getView().requestFocus();
770        } else if ((!mCanShowHeaders || !mShowingHeaders)
771                && mRowsFragment.getView() != null) {
772            mRowsFragment.getView().requestFocus();
773        }
774        if (mCanShowHeaders) {
775            showHeaders(mShowingHeaders);
776        }
777    }
778
779    /**
780     * Enable/disable headers transition on back key support.  This is enabled by default.
781     * BrowseFragment will add a back stack entry when headers are showing.
782     * Headers transition on back key only works for {@link #HEADERS_ENABLED}
783     * or {@link #HEADERS_HIDDEN}.
784     * <p>
785     * NOTE: If app has its own onBackPressed() handling,
786     * app must disable this feature, app may use {@link #startHeadersTransition(boolean)}
787     * and {@link BrowseTransitionListener} in its own back stack handling.
788     */
789    public final void setHeadersTransitionOnBackEnabled(boolean headersBackStackEnabled) {
790        mHeadersBackStackEnabled = headersBackStackEnabled;
791    }
792
793    /**
794     * Returns true if headers transition on back key support is enabled.
795     */
796    public final boolean isHeadersTransitionOnBackEnabled() {
797        return mHeadersBackStackEnabled;
798    }
799
800    private void readArguments(Bundle args) {
801        if (args == null) {
802            return;
803        }
804        if (args.containsKey(ARG_TITLE)) {
805            setTitle(args.getString(ARG_TITLE));
806        }
807
808        if (args.containsKey(ARG_BADGE_URI)) {
809            setBadgeUri(args.getString(ARG_BADGE_URI));
810        }
811
812        if (args.containsKey(ARG_HEADERS_STATE)) {
813            setHeadersState(args.getInt(ARG_HEADERS_STATE));
814        }
815    }
816
817    private void setBadgeUri(String badgeUri) {
818        // TODO - need a drawable downloader
819    }
820
821    private void setBadgeDrawable(Drawable drawable) {
822        if (mBadgeView == null) {
823            return;
824        }
825        mBadgeView.setImageDrawable(drawable);
826        if (drawable != null) {
827            mBadgeView.setVisibility(View.VISIBLE);
828            mTitleView.setVisibility(View.GONE);
829        } else {
830            mBadgeView.setVisibility(View.GONE);
831            mTitleView.setVisibility(View.VISIBLE);
832        }
833    }
834
835    private void setTitle(String title) {
836        if (mTitleView != null) {
837            mTitleView.setText(title);
838        }
839    }
840
841    private void setHeadersState(int headersState) {
842        if (DEBUG) Log.v(TAG, "setHeadersState " + headersState);
843        switch (headersState) {
844            case HEADERS_ENABLED:
845                mCanShowHeaders = true;
846                mShowingHeaders = true;
847                break;
848            case HEADERS_HIDDEN:
849                mCanShowHeaders = true;
850                mShowingHeaders = false;
851                break;
852            case HEADERS_DISABLED:
853                mCanShowHeaders = false;
854                mShowingHeaders = false;
855                break;
856            default:
857                Log.w(TAG, "Unknown headers state: " + headersState);
858                break;
859        }
860        if (mHeadersFragment != null) {
861            mHeadersFragment.setHeadersGone(!mCanShowHeaders);
862        }
863    }
864}
865