385 lines
14 KiB
Java
385 lines
14 KiB
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.
|
||
|
*
|
||
|
* @deprecated This widget is no longer supported.
|
||
|
*/
|
||
|
@Deprecated
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}
|