Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.app;
     18 
     19 import android.animation.LayoutTransition;
     20 import android.app.FragmentManager.BackStackEntry;
     21 import android.content.Context;
     22 import android.content.res.TypedArray;
     23 import android.util.AttributeSet;
     24 import android.view.Gravity;
     25 import android.view.LayoutInflater;
     26 import android.view.View;
     27 import android.view.ViewGroup;
     28 import android.widget.LinearLayout;
     29 import android.widget.TextView;
     30 
     31 /**
     32  * Helper class for showing "bread crumbs" representing the fragment
     33  * stack in an activity.  This is intended to be used with
     34  * {@link ActionBar#setCustomView(View)
     35  * ActionBar.setCustomView(View)} to place the bread crumbs in
     36  * the action bar.
     37  *
     38  * <p>The default style for this view is
     39  * {@link android.R.style#Widget_FragmentBreadCrumbs}.
     40  *
     41  * @deprecated This widget is no longer supported.
     42  */
     43 @Deprecated
     44 public class FragmentBreadCrumbs extends ViewGroup
     45         implements FragmentManager.OnBackStackChangedListener {
     46     Activity mActivity;
     47     LayoutInflater mInflater;
     48     LinearLayout mContainer;
     49     int mMaxVisible = -1;
     50 
     51     // Hahah
     52     BackStackRecord mTopEntry;
     53     BackStackRecord mParentEntry;
     54 
     55     /** Listener to inform when a parent entry is clicked */
     56     private OnClickListener mParentClickListener;
     57 
     58     private OnBreadCrumbClickListener mOnBreadCrumbClickListener;
     59 
     60     private int mGravity;
     61     private int mLayoutResId;
     62     private int mTextColor;
     63 
     64     private static final int DEFAULT_GRAVITY = Gravity.START | Gravity.CENTER_VERTICAL;
     65 
     66     /**
     67      * Interface to intercept clicks on the bread crumbs.
     68      *
     69      * @deprecated This widget is no longer supported.
     70      */
     71     @Deprecated
     72     public interface OnBreadCrumbClickListener {
     73         /**
     74          * Called when a bread crumb is clicked.
     75          *
     76          * @param backStack The BackStackEntry whose bread crumb was clicked.
     77          * May be null, if this bread crumb is for the root of the back stack.
     78          * @param flags Additional information about the entry.  Currently
     79          * always 0.
     80          *
     81          * @return Return true to consume this click.  Return to false to allow
     82          * the default action (popping back stack to this entry) to occur.
     83          */
     84         public boolean onBreadCrumbClick(BackStackEntry backStack, int flags);
     85     }
     86 
     87     public FragmentBreadCrumbs(Context context) {
     88         this(context, null);
     89     }
     90 
     91     public FragmentBreadCrumbs(Context context, AttributeSet attrs) {
     92         this(context, attrs, com.android.internal.R.attr.fragmentBreadCrumbsStyle);
     93     }
     94 
     95     public FragmentBreadCrumbs(Context context, AttributeSet attrs, int defStyleAttr) {
     96         this(context, attrs, defStyleAttr, 0);
     97     }
     98 
     99     /**
    100      * @hide
    101      */
    102     public FragmentBreadCrumbs(
    103             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    104         super(context, attrs, defStyleAttr, defStyleRes);
    105 
    106         final TypedArray a = context.obtainStyledAttributes(attrs,
    107                 com.android.internal.R.styleable.FragmentBreadCrumbs, defStyleAttr, defStyleRes);
    108 
    109         mGravity = a.getInt(com.android.internal.R.styleable.FragmentBreadCrumbs_gravity,
    110                 DEFAULT_GRAVITY);
    111         mLayoutResId = a.getResourceId(
    112                 com.android.internal.R.styleable.FragmentBreadCrumbs_itemLayout,
    113                 com.android.internal.R.layout.fragment_bread_crumb_item);
    114         mTextColor = a.getColor(
    115                 com.android.internal.R.styleable.FragmentBreadCrumbs_itemColor,
    116                 0);
    117 
    118         a.recycle();
    119     }
    120 
    121     /**
    122      * Attach the bread crumbs to their activity.  This must be called once
    123      * when creating the bread crumbs.
    124      */
    125     public void setActivity(Activity a) {
    126         mActivity = a;
    127         mInflater = (LayoutInflater)a.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    128         mContainer = (LinearLayout)mInflater.inflate(
    129                 com.android.internal.R.layout.fragment_bread_crumbs,
    130                 this, false);
    131         addView(mContainer);
    132         a.getFragmentManager().addOnBackStackChangedListener(this);
    133         updateCrumbs();
    134         setLayoutTransition(new LayoutTransition());
    135     }
    136 
    137     /**
    138      * The maximum number of breadcrumbs to show. Older fragment headers will be hidden from view.
    139      * @param visibleCrumbs the number of visible breadcrumbs. This should be greater than zero.
    140      */
    141     public void setMaxVisible(int visibleCrumbs) {
    142         if (visibleCrumbs < 1) {
    143             throw new IllegalArgumentException("visibleCrumbs must be greater than zero");
    144         }
    145         mMaxVisible = visibleCrumbs;
    146     }
    147 
    148     /**
    149      * Inserts an optional parent entry at the first position in the breadcrumbs. Selecting this
    150      * entry will result in a call to the specified listener's
    151      * {@link android.view.View.OnClickListener#onClick(View)}
    152      * method.
    153      *
    154      * @param title the title for the parent entry
    155      * @param shortTitle the short title for the parent entry
    156      * @param listener the {@link android.view.View.OnClickListener} to be called when clicked.
    157      * A null will result in no action being taken when the parent entry is clicked.
    158      */
    159     public void setParentTitle(CharSequence title, CharSequence shortTitle,
    160             OnClickListener listener) {
    161         mParentEntry = createBackStackEntry(title, shortTitle);
    162         mParentClickListener = listener;
    163         updateCrumbs();
    164     }
    165 
    166     /**
    167      * Sets a listener for clicks on the bread crumbs.  This will be called before
    168      * the default click action is performed.
    169      *
    170      * @param listener The new listener to set.  Replaces any existing listener.
    171      */
    172     public void setOnBreadCrumbClickListener(OnBreadCrumbClickListener listener) {
    173         mOnBreadCrumbClickListener = listener;
    174     }
    175 
    176     private BackStackRecord createBackStackEntry(CharSequence title, CharSequence shortTitle) {
    177         if (title == null) return null;
    178 
    179         final BackStackRecord entry = new BackStackRecord(
    180                 (FragmentManagerImpl) mActivity.getFragmentManager());
    181         entry.setBreadCrumbTitle(title);
    182         entry.setBreadCrumbShortTitle(shortTitle);
    183         return entry;
    184     }
    185 
    186     /**
    187      * Set a custom title for the bread crumbs.  This will be the first entry
    188      * shown at the left, representing the root of the bread crumbs.  If the
    189      * title is null, it will not be shown.
    190      */
    191     public void setTitle(CharSequence title, CharSequence shortTitle) {
    192         mTopEntry = createBackStackEntry(title, shortTitle);
    193         updateCrumbs();
    194     }
    195 
    196     @Override
    197     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    198         // Eventually we should implement our own layout of the views, rather than relying on
    199         // a single linear layout.
    200         final int childCount = getChildCount();
    201         if (childCount == 0) {
    202             return;
    203         }
    204 
    205         final View child = getChildAt(0);
    206 
    207         final int childTop = mPaddingTop;
    208         final int childBottom = mPaddingTop + child.getMeasuredHeight() - mPaddingBottom;
    209 
    210         int childLeft;
    211         int childRight;
    212 
    213         final int layoutDirection = getLayoutDirection();
    214         final int horizontalGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
    215         switch (Gravity.getAbsoluteGravity(horizontalGravity, layoutDirection)) {
    216             case Gravity.RIGHT:
    217                 childRight = mRight - mLeft - mPaddingRight;
    218                 childLeft = childRight - child.getMeasuredWidth();
    219                 break;
    220 
    221             case Gravity.CENTER_HORIZONTAL:
    222                 childLeft = mPaddingLeft + (mRight - mLeft - child.getMeasuredWidth()) / 2;
    223                 childRight = childLeft + child.getMeasuredWidth();
    224                 break;
    225 
    226             case Gravity.LEFT:
    227             default:
    228                 childLeft = mPaddingLeft;
    229                 childRight = childLeft + child.getMeasuredWidth();
    230                 break;
    231         }
    232 
    233         if (childLeft < mPaddingLeft) {
    234             childLeft = mPaddingLeft;
    235         }
    236 
    237         if (childRight > mRight - mLeft - mPaddingRight) {
    238             childRight = mRight - mLeft - mPaddingRight;
    239         }
    240 
    241         child.layout(childLeft, childTop, childRight, childBottom);
    242     }
    243 
    244     @Override
    245     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    246         final int count = getChildCount();
    247 
    248         int maxHeight = 0;
    249         int maxWidth = 0;
    250         int measuredChildState = 0;
    251 
    252         // Find rightmost and bottom-most child
    253         for (int i = 0; i < count; i++) {
    254             final View child = getChildAt(i);
    255             if (child.getVisibility() != GONE) {
    256                 measureChild(child, widthMeasureSpec, heightMeasureSpec);
    257                 maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
    258                 maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
    259                 measuredChildState = combineMeasuredStates(measuredChildState,
    260                         child.getMeasuredState());
    261             }
    262         }
    263 
    264         // Account for padding too
    265         maxWidth += mPaddingLeft + mPaddingRight;
    266         maxHeight += mPaddingTop + mPaddingBottom;
    267 
    268         // Check against our minimum height and width
    269         maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    270         maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
    271 
    272         setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, measuredChildState),
    273                 resolveSizeAndState(maxHeight, heightMeasureSpec,
    274                         measuredChildState<<MEASURED_HEIGHT_STATE_SHIFT));
    275     }
    276 
    277     @Override
    278     public void onBackStackChanged() {
    279         updateCrumbs();
    280     }
    281 
    282     /**
    283      * Returns the number of entries before the backstack, including the title of the current
    284      * fragment and any custom parent title that was set.
    285      */
    286     private int getPreEntryCount() {
    287         return (mTopEntry != null ? 1 : 0) + (mParentEntry != null ? 1 : 0);
    288     }
    289 
    290     /**
    291      * Returns the pre-entry corresponding to the index. If there is a parent and a top entry
    292      * set, parent has an index of zero and top entry has an index of 1. Returns null if the
    293      * specified index doesn't exist or is null.
    294      * @param index should not be more than {@link #getPreEntryCount()} - 1
    295      */
    296     private BackStackEntry getPreEntry(int index) {
    297         // If there's a parent entry, then return that for zero'th item, else top entry.
    298         if (mParentEntry != null) {
    299             return index == 0 ? mParentEntry : mTopEntry;
    300         } else {
    301             return mTopEntry;
    302         }
    303     }
    304 
    305     void updateCrumbs() {
    306         FragmentManager fm = mActivity.getFragmentManager();
    307         int numEntries = fm.getBackStackEntryCount();
    308         int numPreEntries = getPreEntryCount();
    309         int numViews = mContainer.getChildCount();
    310         for (int i = 0; i < numEntries + numPreEntries; i++) {
    311             BackStackEntry bse = i < numPreEntries
    312                     ? getPreEntry(i)
    313                     : fm.getBackStackEntryAt(i - numPreEntries);
    314             if (i < numViews) {
    315                 View v = mContainer.getChildAt(i);
    316                 Object tag = v.getTag();
    317                 if (tag != bse) {
    318                     for (int j = i; j < numViews; j++) {
    319                         mContainer.removeViewAt(i);
    320                     }
    321                     numViews = i;
    322                 }
    323             }
    324             if (i >= numViews) {
    325                 final View item = mInflater.inflate(mLayoutResId, this, false);
    326                 final TextView text = (TextView) item.findViewById(com.android.internal.R.id.title);
    327                 text.setText(bse.getBreadCrumbTitle());
    328                 text.setTag(bse);
    329                 text.setTextColor(mTextColor);
    330                 if (i == 0) {
    331                     item.findViewById(com.android.internal.R.id.left_icon).setVisibility(View.GONE);
    332                 }
    333                 mContainer.addView(item);
    334                 text.setOnClickListener(mOnClickListener);
    335             }
    336         }
    337         int viewI = numEntries + numPreEntries;
    338         numViews = mContainer.getChildCount();
    339         while (numViews > viewI) {
    340             mContainer.removeViewAt(numViews - 1);
    341             numViews--;
    342         }
    343         // Adjust the visibility and availability of the bread crumbs and divider
    344         for (int i = 0; i < numViews; i++) {
    345             final View child = mContainer.getChildAt(i);
    346             // Disable the last one
    347             child.findViewById(com.android.internal.R.id.title).setEnabled(i < numViews - 1);
    348             if (mMaxVisible > 0) {
    349                 // Make only the last mMaxVisible crumbs visible
    350                 child.setVisibility(i < numViews - mMaxVisible ? View.GONE : View.VISIBLE);
    351                 final View leftIcon = child.findViewById(com.android.internal.R.id.left_icon);
    352                 // Remove the divider for all but the last mMaxVisible - 1
    353                 leftIcon.setVisibility(i > numViews - mMaxVisible && i != 0 ? View.VISIBLE
    354                         : View.GONE);
    355             }
    356         }
    357     }
    358 
    359     private OnClickListener mOnClickListener = new OnClickListener() {
    360         public void onClick(View v) {
    361             if (v.getTag() instanceof BackStackEntry) {
    362                 BackStackEntry bse = (BackStackEntry) v.getTag();
    363                 if (bse == mParentEntry) {
    364                     if (mParentClickListener != null) {
    365                         mParentClickListener.onClick(v);
    366                     }
    367                 } else {
    368                     if (mOnBreadCrumbClickListener != null) {
    369                         if (mOnBreadCrumbClickListener.onBreadCrumbClick(
    370                                 bse == mTopEntry ? null : bse, 0)) {
    371                             return;
    372                         }
    373                     }
    374                     if (bse == mTopEntry) {
    375                         // Pop everything off the back stack.
    376                         mActivity.getFragmentManager().popBackStack();
    377                     } else {
    378                         mActivity.getFragmentManager().popBackStack(bse.getId(), 0);
    379                     }
    380                 }
    381             }
    382         }
    383     };
    384 }
    385