Java tutorial
/* This file is auto-generated from BrowseFragment.java. DO NOT MODIFY. */ /* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.sgottard.sofa.support; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Rect; import android.os.Bundle; import android.support.v17.leanback.transition.TransitionListener; import android.support.v17.leanback.widget.BrowseFrameLayout; import android.support.v17.leanback.widget.HorizontalGridView; import android.support.v17.leanback.widget.ListRow; import android.support.v17.leanback.widget.ObjectAdapter; import android.support.v17.leanback.widget.OnItemViewClickedListener; import android.support.v17.leanback.widget.OnItemViewSelectedListener; import android.support.v17.leanback.widget.Presenter; import android.support.v17.leanback.widget.PresenterSelector; import android.support.v17.leanback.widget.Row; import android.support.v17.leanback.widget.RowHeaderPresenter; import android.support.v17.leanback.widget.RowPresenter; import android.support.v17.leanback.widget.TitleView; import android.support.v17.leanback.widget.VerticalGridView; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager.BackStackEntry; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.ViewCompat; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewGroup.MarginLayoutParams; import com.sgottard.sofa.ContentFragment; import com.sgottard.sofa.R; import static android.support.v7.widget.RecyclerView.NO_POSITION; /** * A fragment for creating Leanback browse screens. It is composed of a * RowsSupportFragment and a HeadersSupportFragment. * <p> * A BrowseSupportFragment renders the elements of its {@link ObjectAdapter} as a set * of rows in a vertical list. The elements in this adapter must be subclasses * of {@link Row}. * <p> * The HeadersSupportFragment can be set to be either shown or hidden by default, or * may be disabled entirely. See {@link #setHeadersState} for details. * <p> * By default the BrowseSupportFragment includes support for returning to the headers * when the user presses Back. For Activities that customize {@link * android.support.v4.app.FragmentActivity#onBackPressed()}, you must disable this default Back key support by * calling {@link #setHeadersTransitionOnBackEnabled(boolean)} with false and * use {@link android.support.v17.leanback.app.BrowseSupportFragment.BrowseTransitionListener} and * {@link #startHeadersTransition(boolean)}. * <p> * The recommended theme to use with a BrowseSupportFragment is * {@link R.style#Theme_Leanback_Browse}. * </p> */ public class BrowseSupportFragment extends BaseSupportFragment { // BUNDLE attribute for saving header show/hide status when backstack is used: static final String HEADER_STACK_INDEX = "headerStackIndex"; // BUNDLE attribute for saving header show/hide status when backstack is not used: static final String HEADER_SHOW = "headerShow"; final class BackStackListener implements FragmentManager.OnBackStackChangedListener { int mLastEntryCount; int mIndexOfHeadersBackStack; BackStackListener() { mLastEntryCount = getFragmentManager().getBackStackEntryCount(); mIndexOfHeadersBackStack = -1; } void load(Bundle savedInstanceState) { if (savedInstanceState != null) { mIndexOfHeadersBackStack = savedInstanceState.getInt(HEADER_STACK_INDEX, -1); mShowingHeaders = mIndexOfHeadersBackStack == -1; } else { if (!mShowingHeaders) { getFragmentManager().beginTransaction().addToBackStack(mWithHeadersBackStackName).commit(); } } } void save(Bundle outState) { outState.putInt(HEADER_STACK_INDEX, mIndexOfHeadersBackStack); } @Override public void onBackStackChanged() { if (getFragmentManager() == null) { Log.w(TAG, "getFragmentManager() is null, stack:", new Exception()); return; } int count = getFragmentManager().getBackStackEntryCount(); // if backstack is growing and last pushed entry is "headers" backstack, // remember the index of the entry. if (count > mLastEntryCount) { BackStackEntry entry = getFragmentManager().getBackStackEntryAt(count - 1); if (mWithHeadersBackStackName.equals(entry.getName())) { mIndexOfHeadersBackStack = count - 1; } } else if (count < mLastEntryCount) { // if popped "headers" backstack, initiate the show header transition if needed if (mIndexOfHeadersBackStack >= count) { mIndexOfHeadersBackStack = -1; if (!mShowingHeaders) { startHeadersTransitionInternal(true); } } } mLastEntryCount = count; } } /** * Listener for transitions between browse headers and rows. */ public static class BrowseTransitionListener { /** * Callback when headers transition starts. * * @param withHeaders True if the transition will result in headers * being shown, false otherwise. */ public void onHeadersTransitionStart(boolean withHeaders) { } /** * Callback when headers transition stops. * * @param withHeaders True if the transition will result in headers * being shown, false otherwise. */ public void onHeadersTransitionStop(boolean withHeaders) { } } private class SetSelectionRunnable implements Runnable { static final int TYPE_INVALID = -1; static final int TYPE_INTERNAL_SYNC = 0; static final int TYPE_USER_REQUEST = 1; private int mPosition; private int mType; private boolean mSmooth; SetSelectionRunnable() { reset(); } void post(int position, int type, boolean smooth) { // Posting the set selection, rather than calling it immediately, prevents an issue // with adapter changes. Example: a row is added before the current selected row; // first the fast lane view updates its selection, then the rows fragment has that // new selection propagated immediately; THEN the rows view processes the same adapter // change and moves the selection again. if (type >= mType) { mPosition = position; mType = type; mSmooth = smooth; mBrowseFrame.removeCallbacks(this); mBrowseFrame.post(this); } } @Override public void run() { setSelection(mPosition, mSmooth); reset(); } private void reset() { mPosition = -1; mType = TYPE_INVALID; mSmooth = false; } } private static final String TAG = "BrowseSupportFragment"; private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_"; private static boolean DEBUG = false; /** The headers fragment is enabled and shown by default. */ public static final int HEADERS_ENABLED = 1; /** The headers fragment is enabled and hidden by default. */ public static final int HEADERS_HIDDEN = 2; /** The headers fragment is disabled and will never be shown. */ public static final int HEADERS_DISABLED = 3; private ContentFragment mCurrentFragment; private RowsSupportFragment mRowsSupportFragment; private HeadersSupportFragment mHeadersSupportFragment; private ObjectAdapter mAdapter; private int mHeadersState = HEADERS_ENABLED; private int mBrandColor = Color.TRANSPARENT; private boolean mBrandColorSet; private BrowseFrameLayout mBrowseFrame; private boolean mHeadersBackStackEnabled = true; private String mWithHeadersBackStackName; private boolean mShowingHeaders = true; private boolean mCanShowHeaders = true; private int mContainerListMarginStart; private int mContainerListAlignTop; private boolean mRowScaleEnabled = true; private OnItemViewSelectedListener mExternalOnItemViewSelectedListener; private OnItemViewClickedListener mOnItemViewClickedListener; private int mSelectedPosition = -1; private PresenterSelector mHeaderPresenterSelector; private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable(); // transition related: private Object mSceneWithHeaders; private Object mSceneWithoutHeaders; private Object mSceneAfterEntranceTransition; private Object mHeadersTransition; private BackStackListener mBackStackChangedListener; private BrowseTransitionListener mBrowseTransitionListener; private static final String ARG_TITLE = BrowseSupportFragment.class.getCanonicalName() + ".title"; private static final String ARG_BADGE_URI = BrowseSupportFragment.class.getCanonicalName() + ".badge"; private static final String ARG_HEADERS_STATE = BrowseSupportFragment.class.getCanonicalName() + ".headersState"; /** * Creates arguments for a browse fragment. * * @param args The Bundle to place arguments into, or null if the method * should return a new Bundle. * @param title The title of the BrowseSupportFragment. * @param headersState The initial state of the headers of the * BrowseSupportFragment. Must be one of {@link #HEADERS_ENABLED}, {@link * #HEADERS_HIDDEN}, or {@link #HEADERS_DISABLED}. * @return A Bundle with the given arguments for creating a BrowseSupportFragment. */ public static Bundle createArgs(Bundle args, String title, int headersState) { if (args == null) { args = new Bundle(); } args.putString(ARG_TITLE, title); args.putInt(ARG_HEADERS_STATE, headersState); return args; } /** * Sets the brand color for the browse fragment. The brand color is used as * the primary color for UI elements in the browse fragment. For example, * the background color of the headers fragment uses the brand color. * * @param color The color to use as the brand color of the fragment. */ public void setBrandColor(int color) { mBrandColor = color; mBrandColorSet = true; if (mHeadersSupportFragment != null) { mHeadersSupportFragment.setBackgroundColor(mBrandColor); } } /** * Returns the brand color for the browse fragment. * The default is transparent. */ public int getBrandColor() { return mBrandColor; } /** * Sets the adapter containing the rows for the fragment. * * <p>The items referenced by the adapter must be be derived from * {@link Row}. These rows will be used by the rows fragment and the headers * fragment (if not disabled) to render the browse rows. * * @param adapter An ObjectAdapter for the browse rows. All items must * derive from {@link Row}. */ public void setAdapter(ObjectAdapter adapter) { mAdapter = adapter; Object firstElement = mAdapter.get(0); if (firstElement instanceof ListRow && !(((ListRow) firstElement).getAdapter().get(0) instanceof RowsSupportFragment) && !(((ListRow) firstElement).getAdapter().get(0) instanceof ContentFragment)) { if (mRowsSupportFragment != null && mHeadersSupportFragment != null) { mHeadersSupportFragment.setAdapter(adapter); mRowsSupportFragment.setAdapter(adapter); } } else { mRowsSupportFragment = null; if (mHeadersSupportFragment != null) { mHeadersSupportFragment.setAdapter(adapter); } mCurrentFragment = (ContentFragment) ((ListRow) firstElement).getAdapter().get(0); } } /** * Returns the adapter containing the rows for the fragment. */ public ObjectAdapter getAdapter() { return mAdapter; } /** * Sets an item selection listener. */ public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) { mExternalOnItemViewSelectedListener = listener; } /** * Returns an item selection listener. */ public OnItemViewSelectedListener getOnItemViewSelectedListener() { return mExternalOnItemViewSelectedListener; } /** * Sets an item clicked listener on the fragment. * OnItemViewClickedListener will override {@link OnClickListener} that * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}. * So in general, developer should choose one of the listeners but not both. */ public void setOnItemViewClickedListener(OnItemViewClickedListener listener) { mOnItemViewClickedListener = listener; if (mRowsSupportFragment != null) { mRowsSupportFragment.setOnItemViewClickedListener(listener); } } /** * Returns the item Clicked listener. */ public OnItemViewClickedListener getOnItemViewClickedListener() { return mOnItemViewClickedListener; } /** * Starts a headers transition. * * <p>This method will begin a transition to either show or hide the * headers, depending on the value of withHeaders. If headers are disabled * for this browse fragment, this method will throw an exception. * * @param withHeaders True if the headers should transition to being shown, * false if the transition should result in headers being hidden. */ public void startHeadersTransition(boolean withHeaders) { if (!mCanShowHeaders) { throw new IllegalStateException("Cannot start headers transition"); } if (isInHeadersTransition() || mShowingHeaders == withHeaders) { return; } startHeadersTransitionInternal(withHeaders); } /** * Returns true if the headers transition is currently running. */ public boolean isInHeadersTransition() { return mHeadersTransition != null; } /** * Returns true if headers are shown. */ public boolean isShowingHeaders() { return mShowingHeaders; } /** * Sets a listener for browse fragment transitions. * * @param listener The listener to call when a browse headers transition * begins or ends. */ public void setBrowseTransitionListener(BrowseTransitionListener listener) { mBrowseTransitionListener = listener; } /** * Enables scaling of rows when headers are present. * By default enabled to increase density. * * @param enable true to enable row scaling */ public void enableRowScaling(boolean enable) { mRowScaleEnabled = enable; if (mRowsSupportFragment != null) { mRowsSupportFragment.enableRowScaling(mRowScaleEnabled); } } private void startHeadersTransitionInternal(final boolean withHeaders) { if (getFragmentManager().isDestroyed()) { return; } mShowingHeaders = withHeaders; RowsSupportFragment target = null; if (mRowsSupportFragment != null) { target = mRowsSupportFragment; } else if (mCurrentFragment instanceof RowsSupportFragment) { target = (RowsSupportFragment) mCurrentFragment; } Runnable transitionRunnable = new Runnable() { @Override public void run() { mHeadersSupportFragment.onTransitionStart(); createHeadersTransition(); if (mBrowseTransitionListener != null) { mBrowseTransitionListener.onHeadersTransitionStart(withHeaders); } sTransitionHelper.runTransition(withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders, mHeadersTransition); if (mHeadersBackStackEnabled) { if (!withHeaders) { getFragmentManager().beginTransaction().addToBackStack(mWithHeadersBackStackName).commit(); } else { int index = mBackStackChangedListener.mIndexOfHeadersBackStack; if (index >= 0) { BackStackEntry entry = getFragmentManager().getBackStackEntryAt(index); getFragmentManager().popBackStackImmediate(entry.getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE); } } } } }; if (target != null) { target.onExpandTransitionStart(!withHeaders, transitionRunnable); } else { // used for custom fragments, just run the headers transition transitionRunnable.run(); } } private boolean isVerticalScrolling() { // don't run transition boolean isScrolling = (mHeadersSupportFragment.getVerticalGridView() .getScrollState() != HorizontalGridView.SCROLL_STATE_IDLE); if (mRowsSupportFragment != null) { isScrolling = isScrolling || mRowsSupportFragment.getVerticalGridView() .getScrollState() != HorizontalGridView.SCROLL_STATE_IDLE; } else if (mCurrentFragment != null && mCurrentFragment instanceof ContentFragment) { isScrolling = isScrolling || mCurrentFragment.isScrolling(); } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) { isScrolling = isScrolling || mRowsSupportFragment.getVerticalGridView() .getScrollState() != HorizontalGridView.SCROLL_STATE_IDLE; } return isScrolling; } private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener = new BrowseFrameLayout.OnFocusSearchListener() { @Override public View onFocusSearch(View focused, int direction) { // if headers is running transition, focus stays if (mCanShowHeaders && isInHeadersTransition()) { return focused; } if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction); if (getTitleView() != null && focused != getTitleView() && direction == View.FOCUS_UP) { return getTitleView(); } if (getTitleView() != null && getTitleView().hasFocus() && direction == View.FOCUS_DOWN) { if (mCanShowHeaders && mShowingHeaders) { return mHeadersSupportFragment.getVerticalGridView(); } else { if (mRowsSupportFragment != null) { return mRowsSupportFragment.getVerticalGridView(); } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) { return ((RowsSupportFragment) mCurrentFragment).getVerticalGridView(); } else { return mCurrentFragment.getFocusRootView(); } } } boolean isRtl = ViewCompat.getLayoutDirection(focused) == ViewCompat.LAYOUT_DIRECTION_RTL; int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT; int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT; if (mCanShowHeaders && direction == towardStart) { if (isVerticalScrolling() || mShowingHeaders) { return focused; } return mHeadersSupportFragment.getVerticalGridView(); } else if (direction == towardEnd) { if (isVerticalScrolling()) { return focused; } if (mRowsSupportFragment != null) { return mRowsSupportFragment.getVerticalGridView(); } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) { return ((RowsSupportFragment) mCurrentFragment).getVerticalGridView(); } else { return mCurrentFragment.getFocusRootView(); } } else { return null; } } }; private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener = new BrowseFrameLayout.OnChildFocusListener() { @Override public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { if (getChildFragmentManager().isDestroyed()) { return true; } // Make sure not changing focus when requestFocus() is called. if (mCanShowHeaders && mShowingHeaders) { if (mHeadersSupportFragment != null && mHeadersSupportFragment.getView() != null && mHeadersSupportFragment.getView().requestFocus(direction, previouslyFocusedRect)) { return true; } } if (mRowsSupportFragment != null && mRowsSupportFragment.getView() != null && mRowsSupportFragment.getView().requestFocus(direction, previouslyFocusedRect)) { return true; } if (mCurrentFragment != null && mCurrentFragment.getFocusRootView() != null && mCurrentFragment.getFocusRootView().requestFocus(direction, previouslyFocusedRect)) { return true; } if (getTitleView() != null && getTitleView().requestFocus(direction, previouslyFocusedRect)) { return true; } return false; }; @Override public void onRequestChildFocus(View child, View focused) { if (getChildFragmentManager().isDestroyed()) { return; } if (!mCanShowHeaders || isInHeadersTransition()) return; int childId = child.getId(); if (childId == R.id.browse_container_dock && mShowingHeaders) { startHeadersTransitionInternal(false); } else if (childId == R.id.browse_headers_dock && !mShowingHeaders) { startHeadersTransitionInternal(true); } } }; @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mBackStackChangedListener != null) { mBackStackChangedListener.save(outState); } else { outState.putBoolean(HEADER_SHOW, mShowingHeaders); } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TypedArray ta = getActivity().obtainStyledAttributes(R.styleable.LeanbackTheme); mContainerListMarginStart = (int) ta.getDimension(R.styleable.LeanbackTheme_browseRowsMarginStart, getActivity().getResources().getDimensionPixelSize(R.dimen.lb_browse_rows_margin_start)); mContainerListAlignTop = (int) ta.getDimension(R.styleable.LeanbackTheme_browseRowsMarginTop, getActivity().getResources().getDimensionPixelSize(R.dimen.lb_browse_rows_margin_top)); ta.recycle(); readArguments(getArguments()); if (mCanShowHeaders) { if (mHeadersBackStackEnabled) { mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this; mBackStackChangedListener = new BackStackListener(); getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener); mBackStackChangedListener.load(savedInstanceState); } else { if (savedInstanceState != null) { mShowingHeaders = savedInstanceState.getBoolean(HEADER_SHOW); } } } } @Override public void onDestroy() { if (mBackStackChangedListener != null) { getFragmentManager().removeOnBackStackChangedListener(mBackStackChangedListener); } super.onDestroy(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) { mHeadersSupportFragment = new HeadersSupportFragment(); if (mRowsSupportFragment == null && mCurrentFragment == null) { mRowsSupportFragment = new RowsSupportFragment(); getChildFragmentManager().beginTransaction() .replace(R.id.browse_headers_dock, mHeadersSupportFragment) .replace(R.id.browse_container_dock, mRowsSupportFragment).commit(); } else { getChildFragmentManager().beginTransaction() .replace(R.id.browse_headers_dock, mHeadersSupportFragment) .replace(R.id.browse_container_dock, (Fragment) mCurrentFragment).commit(); } } else { mHeadersSupportFragment = (HeadersSupportFragment) getChildFragmentManager() .findFragmentById(R.id.browse_headers_dock); Fragment fragment = getChildFragmentManager().findFragmentById(R.id.browse_container_dock); if (fragment instanceof RowsSupportFragment) { mRowsSupportFragment = (RowsSupportFragment) fragment; } else { mCurrentFragment = (ContentFragment) fragment; } } mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders); if (mRowsSupportFragment != null) { mRowsSupportFragment.setAdapter(mAdapter); mRowsSupportFragment.enableRowScaling(mRowScaleEnabled); mRowsSupportFragment.setOnItemViewSelectedListener(mRowViewSelectedListener); mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener); } if (mHeaderPresenterSelector != null) { mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector); } mHeadersSupportFragment.setAdapter(mAdapter); mHeadersSupportFragment.setOnHeaderViewSelectedListener(mHeaderViewSelectedListener); mHeadersSupportFragment.setOnHeaderClickedListener(mHeaderClickedListener); View root = inflater.inflate(R.layout.lb_browse_fragment, container, false); setTitleView((TitleView) root.findViewById(R.id.browse_title_group)); mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame); mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener); mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener); if (mBrandColorSet) { mHeadersSupportFragment.setBackgroundColor(mBrandColor); } mSceneWithHeaders = sTransitionHelper.createScene(mBrowseFrame, new Runnable() { @Override public void run() { showHeaders(true); } }); mSceneWithoutHeaders = sTransitionHelper.createScene(mBrowseFrame, new Runnable() { @Override public void run() { showHeaders(false); } }); mSceneAfterEntranceTransition = sTransitionHelper.createScene(mBrowseFrame, new Runnable() { @Override public void run() { setEntranceTransitionEndState(); } }); return root; } private void createHeadersTransition() { mHeadersTransition = sTransitionHelper.loadTransition(getActivity(), mShowingHeaders ? R.transition.lb_browse_headers_in : R.transition.lb_browse_headers_out); sTransitionHelper.setTransitionListener(mHeadersTransition, new TransitionListener() { @Override public void onTransitionStart(Object transition) { } @Override public void onTransitionEnd(Object transition) { mHeadersTransition = null; if (mRowsSupportFragment != null) { mRowsSupportFragment.onTransitionEnd(); } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) { ((RowsSupportFragment) mCurrentFragment).onTransitionEnd(); } mHeadersSupportFragment.onTransitionEnd(); if (mShowingHeaders) { VerticalGridView headerGridView = mHeadersSupportFragment.getVerticalGridView(); if (headerGridView != null && !headerGridView.hasFocus()) { headerGridView.requestFocus(); } } else { VerticalGridView rowsGridView = null; if (mRowsSupportFragment != null) { rowsGridView = mRowsSupportFragment.getVerticalGridView(); } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) { rowsGridView = ((RowsSupportFragment) mCurrentFragment).getVerticalGridView(); } if (rowsGridView != null && !rowsGridView.hasFocus()) { rowsGridView.requestFocus(); } } toggleTitle(); if (mBrowseTransitionListener != null) { mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders); } } }); } /** * Sets the {@link PresenterSelector} used to render the row headers. * * @param headerPresenterSelector The PresenterSelector that will determine * the Presenter for each row header. */ public void setHeaderPresenterSelector(PresenterSelector headerPresenterSelector) { mHeaderPresenterSelector = headerPresenterSelector; if (mHeadersSupportFragment != null) { mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector); } } private void setRowsAlignedLeft(boolean alignLeft) { ViewGroup.MarginLayoutParams lp; View containerList; if (mRowsSupportFragment != null) { containerList = mRowsSupportFragment.getView(); lp = (ViewGroup.MarginLayoutParams) containerList.getLayoutParams(); lp.setMarginStart(alignLeft ? 0 : mContainerListMarginStart); containerList.setLayoutParams(lp); } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) { containerList = ((RowsSupportFragment) mCurrentFragment).getView(); if (containerList == null) { mCurrentFragment.setExtraMargin(mContainerListAlignTop, mContainerListMarginStart); } else { lp = (ViewGroup.MarginLayoutParams) containerList.getLayoutParams(); lp.setMarginStart(alignLeft ? 0 : mContainerListMarginStart); containerList.setLayoutParams(lp); } } else { containerList = mCurrentFragment.getView(); if (containerList == null) { mCurrentFragment.setExtraMargin(mContainerListAlignTop, mContainerListMarginStart); } else { lp = (ViewGroup.MarginLayoutParams) containerList.getLayoutParams(); lp.setMarginStart(alignLeft ? 0 : mContainerListMarginStart); containerList.setLayoutParams(lp); } } } private void setHeadersOnScreen(boolean onScreen) { MarginLayoutParams lp; View containerList; containerList = mHeadersSupportFragment.getView(); lp = (MarginLayoutParams) containerList.getLayoutParams(); lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart); containerList.setLayoutParams(lp); } private void showHeaders(boolean show) { if (DEBUG) Log.v(TAG, "showHeaders " + show); mHeadersSupportFragment.setHeadersEnabled(show); setHeadersOnScreen(show); setRowsAlignedLeft(!show); if (mRowsSupportFragment != null) { mRowsSupportFragment.setExpand(!show); } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) { ((RowsSupportFragment) mCurrentFragment).setExpand(!show); } } private HeadersSupportFragment.OnHeaderClickedListener mHeaderClickedListener = new HeadersSupportFragment.OnHeaderClickedListener() { @Override public void onHeaderClicked() { if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) { return; } startHeadersTransitionInternal(false); if (mRowsSupportFragment != null) { mRowsSupportFragment.getVerticalGridView().requestFocus(); } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) { ((RowsSupportFragment) mCurrentFragment).getVerticalGridView().requestFocus(); } else { mCurrentFragment.getFocusRootView().requestFocus(); } } }; private OnItemViewSelectedListener mRowViewSelectedListener = new OnItemViewSelectedListener() { @Override public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { int position = -1; if (mRowsSupportFragment != null) { position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition(); onRowSelected(position); } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) { position = ((RowsSupportFragment) mCurrentFragment).getVerticalGridView().getSelectedPosition(); toggleTitle(); } if (DEBUG) Log.v(TAG, "row selected position " + position); if (mExternalOnItemViewSelectedListener != null) { mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item, rowViewHolder, row); } } }; private HeadersSupportFragment.OnHeaderViewSelectedListener mHeaderViewSelectedListener = new HeadersSupportFragment.OnHeaderViewSelectedListener() { @Override public void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row) { int position = mHeadersSupportFragment.getVerticalGridView().getSelectedPosition(); if (DEBUG) Log.v(TAG, "header selected position " + position); // switch fragments (if needed) if (mRowsSupportFragment == null) { ContentFragment nextFragment = (ContentFragment) ((ListRow) mAdapter.get(position)).getAdapter() .get(0); FragmentManager cfManager = getChildFragmentManager(); Fragment foundFragment = cfManager.findFragmentById(R.id.browse_container_dock); if (foundFragment == null || (foundFragment instanceof ContentFragment && !foundFragment.equals(nextFragment))) { FragmentTransaction transaction = cfManager.beginTransaction(); transaction.replace(R.id.browse_container_dock, (Fragment) nextFragment, nextFragment.getTag()); transaction.commit(); mCurrentFragment = nextFragment; if (nextFragment instanceof RowsSupportFragment) { ((RowsSupportFragment) nextFragment) .setOnItemViewSelectedListener(mRowViewSelectedListener); ((RowsSupportFragment) nextFragment) .setOnItemViewClickedListener(mOnItemViewClickedListener); } showHeaders(mShowingHeaders); } } else { onRowSelected(position); } } }; private void onRowSelected(int position) { if (position != mSelectedPosition) { mSetSelectionRunnable.post(position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true); toggleTitle(); } } private void setSelection(int position, boolean smooth) { if (position != NO_POSITION) { if (mRowsSupportFragment != null) { mRowsSupportFragment.setSelectedPosition(position, smooth); } mHeadersSupportFragment.setSelectedPosition(position, smooth); } mSelectedPosition = position; } /** * Sets the selected row position with smooth animation. */ public void setSelectedPosition(int position) { setSelectedPosition(position, true); } /** * Sets the selected row position. */ public void setSelectedPosition(int position, boolean smooth) { mSetSelectionRunnable.post(position, SetSelectionRunnable.TYPE_USER_REQUEST, smooth); } @Override public void onStart() { super.onStart(); mHeadersSupportFragment.setWindowAlignmentFromTop(mContainerListAlignTop); mHeadersSupportFragment.setItemAlignment(); if (mRowsSupportFragment != null) { mRowsSupportFragment.setWindowAlignmentFromTop(mContainerListAlignTop); mRowsSupportFragment.setItemAlignment(); mRowsSupportFragment.setScalePivots(0, mContainerListAlignTop); } else if (mCurrentFragment instanceof RowsSupportFragment) { ((RowsSupportFragment) mCurrentFragment).setWindowAlignmentFromTop(mContainerListAlignTop); ((RowsSupportFragment) mCurrentFragment).setItemAlignment(); ((RowsSupportFragment) mCurrentFragment).setScalePivots(0, mContainerListAlignTop); ((RowsSupportFragment) mCurrentFragment).setOnItemViewSelectedListener(mRowViewSelectedListener); ((RowsSupportFragment) mCurrentFragment).setOnItemViewClickedListener(mOnItemViewClickedListener); } else { // FIXME handle custom content } if (mCanShowHeaders && mShowingHeaders && mHeadersSupportFragment.getView() != null) { mHeadersSupportFragment.getView().requestFocus(); } else if (!mCanShowHeaders || !mShowingHeaders) { if (mRowsSupportFragment != null && mRowsSupportFragment.getView() != null) { mRowsSupportFragment.getView().requestFocus(); } else if (mCurrentFragment != null) { mCurrentFragment.getFocusRootView().requestFocus(); } } if (mCanShowHeaders) { showHeaders(mShowingHeaders); } if (isEntranceTransitionEnabled()) { setEntranceTransitionStartState(); } } /** * Enables/disables headers transition on back key support. This is enabled by * default. The BrowseSupportFragment will add a back stack entry when headers are * showing. Running a headers transition when the back key is pressed only * works when the headers state is {@link #HEADERS_ENABLED} or * {@link #HEADERS_HIDDEN}. * <p> * NOTE: If an Activity has its own onBackPressed() handling, you must * disable this feature. You may use {@link #startHeadersTransition(boolean)} * and {@link BrowseTransitionListener} in your own back stack handling. */ public final void setHeadersTransitionOnBackEnabled(boolean headersBackStackEnabled) { mHeadersBackStackEnabled = headersBackStackEnabled; } /** * Returns true if headers transition on back key support is enabled. */ public final boolean isHeadersTransitionOnBackEnabled() { return mHeadersBackStackEnabled; } private void readArguments(Bundle args) { if (args == null) { return; } if (args.containsKey(ARG_TITLE)) { setTitle(args.getString(ARG_TITLE)); } if (args.containsKey(ARG_HEADERS_STATE)) { setHeadersState(args.getInt(ARG_HEADERS_STATE)); } } /** * Sets the state for the headers column in the browse fragment. Must be one * of {@link #HEADERS_ENABLED}, {@link #HEADERS_HIDDEN}, or * {@link #HEADERS_DISABLED}. * * @param headersState The state of the headers for the browse fragment. */ public void setHeadersState(int headersState) { if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) { throw new IllegalArgumentException("Invalid headers state: " + headersState); } if (DEBUG) Log.v(TAG, "setHeadersState " + headersState); if (headersState != mHeadersState) { mHeadersState = headersState; switch (headersState) { case HEADERS_ENABLED: mCanShowHeaders = true; mShowingHeaders = true; break; case HEADERS_HIDDEN: mCanShowHeaders = true; mShowingHeaders = false; break; case HEADERS_DISABLED: mCanShowHeaders = false; mShowingHeaders = false; break; default: Log.w(TAG, "Unknown headers state: " + headersState); break; } if (mHeadersSupportFragment != null) { mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders); } } } /** * Returns the state of the headers column in the browse fragment. */ public int getHeadersState() { return mHeadersState; } @Override protected Object createEntranceTransition() { return sTransitionHelper.loadTransition(getActivity(), R.transition.lb_browse_entrance_transition); } @Override protected void runEntranceTransition(Object entranceTransition) { sTransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition); } @Override protected void onEntranceTransitionStart() { mHeadersSupportFragment.onTransitionStart(); if (mRowsSupportFragment != null) { mRowsSupportFragment.onTransitionEnd(); } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) { ((RowsSupportFragment) mCurrentFragment).onTransitionStart(); } } @Override protected void onEntranceTransitionEnd() { mRowsSupportFragment.onTransitionEnd(); mHeadersSupportFragment.onTransitionEnd(); } void setSearchOrbViewOnScreen(boolean onScreen) { View searchOrbView = getTitleView().getSearchAffordanceView(); MarginLayoutParams lp = (MarginLayoutParams) searchOrbView.getLayoutParams(); lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart); searchOrbView.setLayoutParams(lp); } void setEntranceTransitionStartState() { setHeadersOnScreen(false); setSearchOrbViewOnScreen(false); if (mRowsSupportFragment != null) { mRowsSupportFragment.setEntranceTransitionState(false); } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) { ((RowsSupportFragment) mCurrentFragment).setEntranceTransitionState(false); } } void setEntranceTransitionEndState() { setHeadersOnScreen(mShowingHeaders); setSearchOrbViewOnScreen(true); if (mRowsSupportFragment != null) { mRowsSupportFragment.setEntranceTransitionState(true); } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) { ((RowsSupportFragment) mCurrentFragment).setEntranceTransitionState(true); } } // this has been exposed to the developer, mainly to allow control over the title block // for custom fragments public void toggleTitle(boolean show) { showTitle(show); } private void toggleTitle() { int headersPosition = mHeadersSupportFragment.getVerticalGridView().getSelectedPosition(); headersPosition = headersPosition < 0 ? 0 : headersPosition; int rowsPosition = 0; if (mRowsSupportFragment != null) { rowsPosition = mRowsSupportFragment.getVerticalGridView().getSelectedPosition(); } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) { rowsPosition = ((RowsSupportFragment) mCurrentFragment).getVerticalGridView().getSelectedPosition(); } if ((!mShowingHeaders && rowsPosition == 0) || (mShowingHeaders && headersPosition == 0)) { showTitle(true); } else { showTitle(false); } } }