blob: d0aa0fd605bba14a5fdbbbd6c1cef2baff65c610 [file] [log] [blame]
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001/*
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
17package android.app;
18
Dianne Hackborn8eb2e242010-11-01 12:31:24 -070019import android.animation.LayoutTransition;
Amith Yamasanidcfb9f72010-09-21 14:22:09 -070020import android.app.FragmentManager.BackStackEntry;
Dianne Hackbornc6669ca2010-09-16 01:33:24 -070021import android.content.Context;
Fabrice Di Meglio3cc10f42012-10-10 19:11:47 -070022import android.content.res.TypedArray;
Dianne Hackbornc6669ca2010-09-16 01:33:24 -070023import android.util.AttributeSet;
Fabrice Di Meglio3cc10f42012-10-10 19:11:47 -070024import android.view.Gravity;
Dianne Hackbornc6669ca2010-09-16 01:33:24 -070025import android.view.LayoutInflater;
26import android.view.View;
27import android.view.ViewGroup;
28import android.widget.LinearLayout;
29import 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
Adam Powell1264c332011-01-20 12:08:13 -080034 * {@link ActionBar#setCustomView(View)
35 * ActionBar.setCustomView(View)} to place the bread crumbs in
36 * the action bar.
Dianne Hackbornc6669ca2010-09-16 01:33:24 -070037 *
38 * <p>The default style for this view is
39 * {@link android.R.style#Widget_FragmentBreadCrumbs}.
Alan Viverette1d90d3e2014-05-15 16:04:05 -070040 *
41 * @deprecated This widget is no longer supported.
Dianne Hackbornc6669ca2010-09-16 01:33:24 -070042 */
Alan Viverette1d90d3e2014-05-15 16:04:05 -070043@Deprecated
Dianne Hackbornc6669ca2010-09-16 01:33:24 -070044public class FragmentBreadCrumbs extends ViewGroup
45 implements FragmentManager.OnBackStackChangedListener {
46 Activity mActivity;
47 LayoutInflater mInflater;
48 LinearLayout mContainer;
Amith Yamasani3c9f5192010-12-08 16:48:31 -080049 int mMaxVisible = -1;
Dianne Hackbornc6669ca2010-09-16 01:33:24 -070050
51 // Hahah
52 BackStackRecord mTopEntry;
Amith Yamasanic9ecb732010-12-14 14:23:21 -080053 BackStackRecord mParentEntry;
54
55 /** Listener to inform when a parent entry is clicked */
56 private OnClickListener mParentClickListener;
Dianne Hackbornc6669ca2010-09-16 01:33:24 -070057
Dianne Hackbornd94df452011-02-16 18:53:31 -080058 private OnBreadCrumbClickListener mOnBreadCrumbClickListener;
Fabrice Di Meglio3cc10f42012-10-10 19:11:47 -070059
60 private int mGravity;
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -070061 private int mLayoutResId;
62 private int mTextColor;
Fabrice Di Meglio3cc10f42012-10-10 19:11:47 -070063
64 private static final int DEFAULT_GRAVITY = Gravity.START | Gravity.CENTER_VERTICAL;
65
Dianne Hackbornd94df452011-02-16 18:53:31 -080066 /**
67 * Interface to intercept clicks on the bread crumbs.
68 */
69 public interface OnBreadCrumbClickListener {
70 /**
71 * Called when a bread crumb is clicked.
72 *
73 * @param backStack The BackStackEntry whose bread crumb was clicked.
74 * May be null, if this bread crumb is for the root of the back stack.
75 * @param flags Additional information about the entry. Currently
76 * always 0.
77 *
78 * @return Return true to consume this click. Return to false to allow
79 * the default action (popping back stack to this entry) to occur.
80 */
81 public boolean onBreadCrumbClick(BackStackEntry backStack, int flags);
82 }
83
Dianne Hackbornc6669ca2010-09-16 01:33:24 -070084 public FragmentBreadCrumbs(Context context) {
85 this(context, null);
86 }
87
88 public FragmentBreadCrumbs(Context context, AttributeSet attrs) {
Alan Viveretteaaca5d82013-09-10 15:04:10 -070089 this(context, attrs, com.android.internal.R.attr.fragmentBreadCrumbsStyle);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -070090 }
91
Alan Viverette617feb92013-09-09 18:09:13 -070092 public FragmentBreadCrumbs(Context context, AttributeSet attrs, int defStyleAttr) {
93 this(context, attrs, defStyleAttr, 0);
94 }
Fabrice Di Meglio3cc10f42012-10-10 19:11:47 -070095
Alan Viverette1d90d3e2014-05-15 16:04:05 -070096 /**
97 * @hide
98 */
Alan Viverette617feb92013-09-09 18:09:13 -070099 public FragmentBreadCrumbs(
100 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
101 super(context, attrs, defStyleAttr, defStyleRes);
102
103 final TypedArray a = context.obtainStyledAttributes(attrs,
104 com.android.internal.R.styleable.FragmentBreadCrumbs, defStyleAttr, defStyleRes);
Fabrice Di Meglio3cc10f42012-10-10 19:11:47 -0700105
106 mGravity = a.getInt(com.android.internal.R.styleable.FragmentBreadCrumbs_gravity,
107 DEFAULT_GRAVITY);
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700108 mLayoutResId = a.getResourceId(
109 com.android.internal.R.styleable.FragmentBreadCrumbs_itemLayout,
110 com.android.internal.R.layout.fragment_bread_crumb_item);
111 mTextColor = a.getColor(
112 com.android.internal.R.styleable.FragmentBreadCrumbs_itemColor,
113 0);
Fabrice Di Meglio3cc10f42012-10-10 19:11:47 -0700114
115 a.recycle();
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700116 }
117
118 /**
119 * Attach the bread crumbs to their activity. This must be called once
120 * when creating the bread crumbs.
121 */
122 public void setActivity(Activity a) {
123 mActivity = a;
124 mInflater = (LayoutInflater)a.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
125 mContainer = (LinearLayout)mInflater.inflate(
126 com.android.internal.R.layout.fragment_bread_crumbs,
127 this, false);
128 addView(mContainer);
129 a.getFragmentManager().addOnBackStackChangedListener(this);
130 updateCrumbs();
Dianne Hackborn8eb2e242010-11-01 12:31:24 -0700131 setLayoutTransition(new LayoutTransition());
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700132 }
133
134 /**
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800135 * The maximum number of breadcrumbs to show. Older fragment headers will be hidden from view.
136 * @param visibleCrumbs the number of visible breadcrumbs. This should be greater than zero.
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800137 */
138 public void setMaxVisible(int visibleCrumbs) {
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800139 if (visibleCrumbs < 1) {
140 throw new IllegalArgumentException("visibleCrumbs must be greater than zero");
141 }
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800142 mMaxVisible = visibleCrumbs;
143 }
144
145 /**
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800146 * Inserts an optional parent entry at the first position in the breadcrumbs. Selecting this
147 * entry will result in a call to the specified listener's
148 * {@link android.view.View.OnClickListener#onClick(View)}
149 * method.
150 *
151 * @param title the title for the parent entry
152 * @param shortTitle the short title for the parent entry
153 * @param listener the {@link android.view.View.OnClickListener} to be called when clicked.
154 * A null will result in no action being taken when the parent entry is clicked.
155 */
156 public void setParentTitle(CharSequence title, CharSequence shortTitle,
157 OnClickListener listener) {
158 mParentEntry = createBackStackEntry(title, shortTitle);
159 mParentClickListener = listener;
160 updateCrumbs();
161 }
162
Dianne Hackbornd94df452011-02-16 18:53:31 -0800163 /**
164 * Sets a listener for clicks on the bread crumbs. This will be called before
165 * the default click action is performed.
166 *
167 * @param listener The new listener to set. Replaces any existing listener.
168 */
169 public void setOnBreadCrumbClickListener(OnBreadCrumbClickListener listener) {
170 mOnBreadCrumbClickListener = listener;
171 }
172
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800173 private BackStackRecord createBackStackEntry(CharSequence title, CharSequence shortTitle) {
174 if (title == null) return null;
175
176 final BackStackRecord entry = new BackStackRecord(
177 (FragmentManagerImpl) mActivity.getFragmentManager());
178 entry.setBreadCrumbTitle(title);
179 entry.setBreadCrumbShortTitle(shortTitle);
180 return entry;
181 }
182
183 /**
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700184 * Set a custom title for the bread crumbs. This will be the first entry
185 * shown at the left, representing the root of the bread crumbs. If the
186 * title is null, it will not be shown.
187 */
188 public void setTitle(CharSequence title, CharSequence shortTitle) {
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800189 mTopEntry = createBackStackEntry(title, shortTitle);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700190 updateCrumbs();
191 }
192
193 @Override
194 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Fabrice Di Meglio3cc10f42012-10-10 19:11:47 -0700195 // Eventually we should implement our own layout of the views, rather than relying on
196 // a single linear layout.
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700197 final int childCount = getChildCount();
Fabrice Di Meglio3cc10f42012-10-10 19:11:47 -0700198 if (childCount == 0) {
199 return;
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700200 }
Fabrice Di Meglio3cc10f42012-10-10 19:11:47 -0700201
202 final View child = getChildAt(0);
203
204 final int childTop = mPaddingTop;
205 final int childBottom = mPaddingTop + child.getMeasuredHeight() - mPaddingBottom;
206
207 int childLeft;
208 int childRight;
209
210 final int layoutDirection = getLayoutDirection();
211 final int horizontalGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
212 switch (Gravity.getAbsoluteGravity(horizontalGravity, layoutDirection)) {
213 case Gravity.RIGHT:
214 childRight = mRight - mLeft - mPaddingRight;
215 childLeft = childRight - child.getMeasuredWidth();
216 break;
217
218 case Gravity.CENTER_HORIZONTAL:
219 childLeft = mPaddingLeft + (mRight - mLeft - child.getMeasuredWidth()) / 2;
220 childRight = childLeft + child.getMeasuredWidth();
221 break;
222
223 case Gravity.LEFT:
224 default:
225 childLeft = mPaddingLeft;
226 childRight = childLeft + child.getMeasuredWidth();
227 break;
228 }
229
230 if (childLeft < mPaddingLeft) {
231 childLeft = mPaddingLeft;
232 }
233
234 if (childRight > mRight - mLeft - mPaddingRight) {
235 childRight = mRight - mLeft - mPaddingRight;
236 }
237
238 child.layout(childLeft, childTop, childRight, childBottom);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700239 }
240
241 @Override
242 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
243 final int count = getChildCount();
244
245 int maxHeight = 0;
246 int maxWidth = 0;
Dianne Hackborn189ee182010-12-02 21:48:53 -0800247 int measuredChildState = 0;
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700248
249 // Find rightmost and bottom-most child
250 for (int i = 0; i < count; i++) {
251 final View child = getChildAt(i);
252 if (child.getVisibility() != GONE) {
253 measureChild(child, widthMeasureSpec, heightMeasureSpec);
254 maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
255 maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
Dianne Hackborn189ee182010-12-02 21:48:53 -0800256 measuredChildState = combineMeasuredStates(measuredChildState,
257 child.getMeasuredState());
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700258 }
259 }
260
261 // Account for padding too
262 maxWidth += mPaddingLeft + mPaddingRight;
263 maxHeight += mPaddingTop + mPaddingBottom;
264
265 // Check against our minimum height and width
266 maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
267 maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
268
Dianne Hackborn189ee182010-12-02 21:48:53 -0800269 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, measuredChildState),
270 resolveSizeAndState(maxHeight, heightMeasureSpec,
271 measuredChildState<<MEASURED_HEIGHT_STATE_SHIFT));
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700272 }
273
274 @Override
275 public void onBackStackChanged() {
276 updateCrumbs();
277 }
278
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800279 /**
280 * Returns the number of entries before the backstack, including the title of the current
281 * fragment and any custom parent title that was set.
282 */
283 private int getPreEntryCount() {
284 return (mTopEntry != null ? 1 : 0) + (mParentEntry != null ? 1 : 0);
285 }
286
287 /**
288 * Returns the pre-entry corresponding to the index. If there is a parent and a top entry
289 * set, parent has an index of zero and top entry has an index of 1. Returns null if the
290 * specified index doesn't exist or is null.
291 * @param index should not be more than {@link #getPreEntryCount()} - 1
292 */
293 private BackStackEntry getPreEntry(int index) {
294 // If there's a parent entry, then return that for zero'th item, else top entry.
295 if (mParentEntry != null) {
296 return index == 0 ? mParentEntry : mTopEntry;
297 } else {
298 return mTopEntry;
299 }
300 }
301
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700302 void updateCrumbs() {
303 FragmentManager fm = mActivity.getFragmentManager();
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800304 int numEntries = fm.getBackStackEntryCount();
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800305 int numPreEntries = getPreEntryCount();
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700306 int numViews = mContainer.getChildCount();
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800307 for (int i = 0; i < numEntries + numPreEntries; i++) {
308 BackStackEntry bse = i < numPreEntries
309 ? getPreEntry(i)
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800310 : fm.getBackStackEntryAt(i - numPreEntries);
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800311 if (i < numViews) {
312 View v = mContainer.getChildAt(i);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700313 Object tag = v.getTag();
314 if (tag != bse) {
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800315 for (int j = i; j < numViews; j++) {
316 mContainer.removeViewAt(i);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700317 }
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800318 numViews = i;
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700319 }
320 }
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800321 if (i >= numViews) {
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700322 final View item = mInflater.inflate(mLayoutResId, this, false);
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800323 final TextView text = (TextView) item.findViewById(com.android.internal.R.id.title);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700324 text.setText(bse.getBreadCrumbTitle());
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800325 text.setTag(bse);
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700326 text.setTextColor(mTextColor);
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800327 if (i == 0) {
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800328 item.findViewById(com.android.internal.R.id.left_icon).setVisibility(View.GONE);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700329 }
330 mContainer.addView(item);
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800331 text.setOnClickListener(mOnClickListener);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700332 }
333 }
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800334 int viewI = numEntries + numPreEntries;
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700335 numViews = mContainer.getChildCount();
336 while (numViews > viewI) {
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800337 mContainer.removeViewAt(numViews - 1);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700338 numViews--;
339 }
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800340 // Adjust the visibility and availability of the bread crumbs and divider
341 for (int i = 0; i < numViews; i++) {
342 final View child = mContainer.getChildAt(i);
343 // Disable the last one
344 child.findViewById(com.android.internal.R.id.title).setEnabled(i < numViews - 1);
345 if (mMaxVisible > 0) {
346 // Make only the last mMaxVisible crumbs visible
347 child.setVisibility(i < numViews - mMaxVisible ? View.GONE : View.VISIBLE);
348 final View leftIcon = child.findViewById(com.android.internal.R.id.left_icon);
349 // Remove the divider for all but the last mMaxVisible - 1
350 leftIcon.setVisibility(i > numViews - mMaxVisible && i != 0 ? View.VISIBLE
351 : View.GONE);
352 }
353 }
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700354 }
Amith Yamasanidcfb9f72010-09-21 14:22:09 -0700355
356 private OnClickListener mOnClickListener = new OnClickListener() {
357 public void onClick(View v) {
358 if (v.getTag() instanceof BackStackEntry) {
359 BackStackEntry bse = (BackStackEntry) v.getTag();
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800360 if (bse == mParentEntry) {
361 if (mParentClickListener != null) {
362 mParentClickListener.onClick(v);
363 }
364 } else {
Dianne Hackbornd94df452011-02-16 18:53:31 -0800365 if (mOnBreadCrumbClickListener != null) {
366 if (mOnBreadCrumbClickListener.onBreadCrumbClick(
367 bse == mTopEntry ? null : bse, 0)) {
368 return;
369 }
370 }
371 if (bse == mTopEntry) {
372 // Pop everything off the back stack.
373 mActivity.getFragmentManager().popBackStack();
374 } else {
375 mActivity.getFragmentManager().popBackStack(bse.getId(), 0);
376 }
Amith Yamasanic9ecb732010-12-14 14:23:21 -0800377 }
Amith Yamasanidcfb9f72010-09-21 14:22:09 -0700378 }
379 }
380 };
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700381}