FileDocCategorySizeDatePackage
FragmentBreadCrumbs.javaAPI DocAndroid 5.1 API14699Thu Mar 12 22:22:10 GMT 2015android.app

FragmentBreadCrumbs.java

/*
 * Copyright (C) 2010 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 android.app;

import android.animation.LayoutTransition;
import android.app.FragmentManager.BackStackEntry;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

/**
 * Helper class for showing "bread crumbs" representing the fragment
 * stack in an activity.  This is intended to be used with
 * {@link ActionBar#setCustomView(View)
 * ActionBar.setCustomView(View)} to place the bread crumbs in
 * the action bar.
 *
 * <p>The default style for this view is
 * {@link android.R.style#Widget_FragmentBreadCrumbs}.
 *
 * @deprecated This widget is no longer supported.
 */
@Deprecated
public class FragmentBreadCrumbs extends ViewGroup
        implements FragmentManager.OnBackStackChangedListener {
    Activity mActivity;
    LayoutInflater mInflater;
    LinearLayout mContainer;
    int mMaxVisible = -1;

    // Hahah
    BackStackRecord mTopEntry;
    BackStackRecord mParentEntry;

    /** Listener to inform when a parent entry is clicked */
    private OnClickListener mParentClickListener;

    private OnBreadCrumbClickListener mOnBreadCrumbClickListener;

    private int mGravity;
    private int mLayoutResId;
    private int mTextColor;

    private static final int DEFAULT_GRAVITY = Gravity.START | Gravity.CENTER_VERTICAL;

    /**
     * Interface to intercept clicks on the bread crumbs.
     */
    public interface OnBreadCrumbClickListener {
        /**
         * Called when a bread crumb is clicked.
         * 
         * @param backStack The BackStackEntry whose bread crumb was clicked.
         * May be null, if this bread crumb is for the root of the back stack.
         * @param flags Additional information about the entry.  Currently
         * always 0.
         * 
         * @return Return true to consume this click.  Return to false to allow
         * the default action (popping back stack to this entry) to occur.
         */
        public boolean onBreadCrumbClick(BackStackEntry backStack, int flags);
    }
    
    public FragmentBreadCrumbs(Context context) {
        this(context, null);
    }

    public FragmentBreadCrumbs(Context context, AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.fragmentBreadCrumbsStyle);
    }

    public FragmentBreadCrumbs(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    /**
     * @hide
     */
    public FragmentBreadCrumbs(
            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        final TypedArray a = context.obtainStyledAttributes(attrs,
                com.android.internal.R.styleable.FragmentBreadCrumbs, defStyleAttr, defStyleRes);

        mGravity = a.getInt(com.android.internal.R.styleable.FragmentBreadCrumbs_gravity,
                DEFAULT_GRAVITY);
        mLayoutResId = a.getResourceId(
                com.android.internal.R.styleable.FragmentBreadCrumbs_itemLayout,
                com.android.internal.R.layout.fragment_bread_crumb_item);
        mTextColor = a.getColor(
                com.android.internal.R.styleable.FragmentBreadCrumbs_itemColor,
                0);

        a.recycle();
    }

    /**
     * Attach the bread crumbs to their activity.  This must be called once
     * when creating the bread crumbs.
     */
    public void setActivity(Activity a) {
        mActivity = a;
        mInflater = (LayoutInflater)a.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mContainer = (LinearLayout)mInflater.inflate(
                com.android.internal.R.layout.fragment_bread_crumbs,
                this, false);
        addView(mContainer);
        a.getFragmentManager().addOnBackStackChangedListener(this);
        updateCrumbs();
        setLayoutTransition(new LayoutTransition());
    }

    /**
     * The maximum number of breadcrumbs to show. Older fragment headers will be hidden from view.
     * @param visibleCrumbs the number of visible breadcrumbs. This should be greater than zero.
     */
    public void setMaxVisible(int visibleCrumbs) {
        if (visibleCrumbs < 1) {
            throw new IllegalArgumentException("visibleCrumbs must be greater than zero");
        }
        mMaxVisible = visibleCrumbs;
    }

    /**
     * Inserts an optional parent entry at the first position in the breadcrumbs. Selecting this
     * entry will result in a call to the specified listener's 
     * {@link android.view.View.OnClickListener#onClick(View)}
     * method.
     *
     * @param title the title for the parent entry
     * @param shortTitle the short title for the parent entry
     * @param listener the {@link android.view.View.OnClickListener} to be called when clicked.
     * A null will result in no action being taken when the parent entry is clicked.
     */
    public void setParentTitle(CharSequence title, CharSequence shortTitle,
            OnClickListener listener) {
        mParentEntry = createBackStackEntry(title, shortTitle);
        mParentClickListener = listener;
        updateCrumbs();
    }

    /**
     * Sets a listener for clicks on the bread crumbs.  This will be called before
     * the default click action is performed.
     * 
     * @param listener The new listener to set.  Replaces any existing listener.
     */
    public void setOnBreadCrumbClickListener(OnBreadCrumbClickListener listener) {
        mOnBreadCrumbClickListener = listener;
    }
    
    private BackStackRecord createBackStackEntry(CharSequence title, CharSequence shortTitle) {
        if (title == null) return null;

        final BackStackRecord entry = new BackStackRecord(
                (FragmentManagerImpl) mActivity.getFragmentManager());
        entry.setBreadCrumbTitle(title);
        entry.setBreadCrumbShortTitle(shortTitle);
        return entry;
    }

    /**
     * Set a custom title for the bread crumbs.  This will be the first entry
     * shown at the left, representing the root of the bread crumbs.  If the
     * title is null, it will not be shown.
     */
    public void setTitle(CharSequence title, CharSequence shortTitle) {
        mTopEntry = createBackStackEntry(title, shortTitle);
        updateCrumbs();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // Eventually we should implement our own layout of the views, rather than relying on
        // a single linear layout.
        final int childCount = getChildCount();
        if (childCount == 0) {
            return;
        }

        final View child = getChildAt(0);

        final int childTop = mPaddingTop;
        final int childBottom = mPaddingTop + child.getMeasuredHeight() - mPaddingBottom;

        int childLeft;
        int childRight;

        final int layoutDirection = getLayoutDirection();
        final int horizontalGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
        switch (Gravity.getAbsoluteGravity(horizontalGravity, layoutDirection)) {
            case Gravity.RIGHT:
                childRight = mRight - mLeft - mPaddingRight;
                childLeft = childRight - child.getMeasuredWidth();
                break;

            case Gravity.CENTER_HORIZONTAL:
                childLeft = mPaddingLeft + (mRight - mLeft - child.getMeasuredWidth()) / 2;
                childRight = childLeft + child.getMeasuredWidth();
                break;

            case Gravity.LEFT:
            default:
                childLeft = mPaddingLeft;
                childRight = childLeft + child.getMeasuredWidth();
                break;
        }

        if (childLeft < mPaddingLeft) {
            childLeft = mPaddingLeft;
        }

        if (childRight > mRight - mLeft - mPaddingRight) {
            childRight = mRight - mLeft - mPaddingRight;
        }

        child.layout(childLeft, childTop, childRight, childBottom);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int count = getChildCount();

        int maxHeight = 0;
        int maxWidth = 0;
        int measuredChildState = 0;

        // Find rightmost and bottom-most child
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
                maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
                maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
                measuredChildState = combineMeasuredStates(measuredChildState,
                        child.getMeasuredState());
            }
        }

        // Account for padding too
        maxWidth += mPaddingLeft + mPaddingRight;
        maxHeight += mPaddingTop + mPaddingBottom;

        // Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, measuredChildState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        measuredChildState<<MEASURED_HEIGHT_STATE_SHIFT));
    }

    @Override
    public void onBackStackChanged() {
        updateCrumbs();
    }

    /**
     * Returns the number of entries before the backstack, including the title of the current
     * fragment and any custom parent title that was set.
     */
    private int getPreEntryCount() {
        return (mTopEntry != null ? 1 : 0) + (mParentEntry != null ? 1 : 0);
    }

    /**
     * Returns the pre-entry corresponding to the index. If there is a parent and a top entry
     * set, parent has an index of zero and top entry has an index of 1. Returns null if the
     * specified index doesn't exist or is null.
     * @param index should not be more than {@link #getPreEntryCount()} - 1
     */
    private BackStackEntry getPreEntry(int index) {
        // If there's a parent entry, then return that for zero'th item, else top entry.
        if (mParentEntry != null) {
            return index == 0 ? mParentEntry : mTopEntry;
        } else {
            return mTopEntry;
        }
    }

    void updateCrumbs() {
        FragmentManager fm = mActivity.getFragmentManager();
        int numEntries = fm.getBackStackEntryCount();
        int numPreEntries = getPreEntryCount();
        int numViews = mContainer.getChildCount();
        for (int i = 0; i < numEntries + numPreEntries; i++) {
            BackStackEntry bse = i < numPreEntries
                    ? getPreEntry(i)
                    : fm.getBackStackEntryAt(i - numPreEntries);
            if (i < numViews) {
                View v = mContainer.getChildAt(i);
                Object tag = v.getTag();
                if (tag != bse) {
                    for (int j = i; j < numViews; j++) {
                        mContainer.removeViewAt(i);
                    }
                    numViews = i;
                }
            }
            if (i >= numViews) {
                final View item = mInflater.inflate(mLayoutResId, this, false);
                final TextView text = (TextView) item.findViewById(com.android.internal.R.id.title);
                text.setText(bse.getBreadCrumbTitle());
                text.setTag(bse);
                text.setTextColor(mTextColor);
                if (i == 0) {
                    item.findViewById(com.android.internal.R.id.left_icon).setVisibility(View.GONE);
                }
                mContainer.addView(item);
                text.setOnClickListener(mOnClickListener);
            }
        }
        int viewI = numEntries + numPreEntries;
        numViews = mContainer.getChildCount();
        while (numViews > viewI) {
            mContainer.removeViewAt(numViews - 1);
            numViews--;
        }
        // Adjust the visibility and availability of the bread crumbs and divider
        for (int i = 0; i < numViews; i++) {
            final View child = mContainer.getChildAt(i);
            // Disable the last one
            child.findViewById(com.android.internal.R.id.title).setEnabled(i < numViews - 1);
            if (mMaxVisible > 0) {
                // Make only the last mMaxVisible crumbs visible
                child.setVisibility(i < numViews - mMaxVisible ? View.GONE : View.VISIBLE);
                final View leftIcon = child.findViewById(com.android.internal.R.id.left_icon);
                // Remove the divider for all but the last mMaxVisible - 1
                leftIcon.setVisibility(i > numViews - mMaxVisible && i != 0 ? View.VISIBLE
                        : View.GONE);
            }
        }
    }

    private OnClickListener mOnClickListener = new OnClickListener() {
        public void onClick(View v) {
            if (v.getTag() instanceof BackStackEntry) {
                BackStackEntry bse = (BackStackEntry) v.getTag();
                if (bse == mParentEntry) {
                    if (mParentClickListener != null) {
                        mParentClickListener.onClick(v);
                    }
                } else {
                    if (mOnBreadCrumbClickListener != null) {
                        if (mOnBreadCrumbClickListener.onBreadCrumbClick(
                                bse == mTopEntry ? null : bse, 0)) {
                            return;
                        }
                    }
                    if (bse == mTopEntry) {
                        // Pop everything off the back stack.
                        mActivity.getFragmentManager().popBackStack();
                    } else {
                        mActivity.getFragmentManager().popBackStack(bse.getId(), 0);
                    }
                }
            }
        }
    };
}