Fix bug 5122319 - When action bar tabs run out of space they should

collapse in to a spinner.

When tabs are not given the option of dropping to their own row,
collapse them into a spinner when they would measure too large to be
visible all at once.

Fix bug 5095167 - zombie tabs return when they shouldn't when activity
handles its own orientation changes

Change-Id: I074419d99a22aa5dd1cbc00a66e600ec5cb0b54a
This commit is contained in:
Adam Powell
2011-08-10 22:49:02 -07:00
parent bdbe6939ff
commit f5645cbafe
4 changed files with 236 additions and 65 deletions

View File

@@ -156,8 +156,6 @@ public class ActionBarImpl extends ActionBar {
"with a compatible window decor layout");
}
mHasEmbeddedTabs = mContext.getResources().getBoolean(
com.android.internal.R.bool.action_bar_embed_tabs);
mActionView.setContextView(mContextView);
mContextDisplayMode = mActionView.isSplitActionBar() ?
CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL;
@@ -166,25 +164,31 @@ public class ActionBarImpl extends ActionBar {
// Newer apps need to enable it explicitly.
setHomeButtonEnabled(mContext.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.ICE_CREAM_SANDWICH);
setHasEmbeddedTabs(mContext.getResources().getBoolean(
com.android.internal.R.bool.action_bar_embed_tabs));
}
public void onConfigurationChanged(Configuration newConfig) {
mHasEmbeddedTabs = mContext.getResources().getBoolean(
com.android.internal.R.bool.action_bar_embed_tabs);
setHasEmbeddedTabs(mContext.getResources().getBoolean(
com.android.internal.R.bool.action_bar_embed_tabs));
}
private void setHasEmbeddedTabs(boolean hasEmbeddedTabs) {
mHasEmbeddedTabs = hasEmbeddedTabs;
// Switch tab layout configuration if needed
if (!mHasEmbeddedTabs) {
mActionView.setEmbeddedTabView(null);
mContainerView.setTabContainer(mTabScrollView);
} else {
mContainerView.setTabContainer(null);
if (mTabScrollView != null) {
mTabScrollView.setVisibility(View.VISIBLE);
}
mActionView.setEmbeddedTabView(mTabScrollView);
}
mActionView.setCollapsable(!mHasEmbeddedTabs &&
getNavigationMode() == NAVIGATION_MODE_TABS);
final boolean isInTabMode = getNavigationMode() == NAVIGATION_MODE_TABS;
if (mTabScrollView != null) {
mTabScrollView.setVisibility(isInTabMode ? View.VISIBLE : View.GONE);
}
mActionView.setCollapsable(!mHasEmbeddedTabs && isInTabMode);
}
private void ensureTabsExist() {
@@ -192,7 +196,7 @@ public class ActionBarImpl extends ActionBar {
return;
}
ScrollingTabContainerView tabScroller = mActionView.createTabContainer();
ScrollingTabContainerView tabScroller = new ScrollingTabContainerView(mContext);
if (mHasEmbeddedTabs) {
tabScroller.setVisibility(View.VISIBLE);
@@ -925,18 +929,14 @@ public class ActionBarImpl extends ActionBar {
case NAVIGATION_MODE_TABS:
mSavedTabPosition = getSelectedNavigationIndex();
selectTab(null);
if (!mActionView.hasEmbeddedTabs()) {
mTabScrollView.setVisibility(View.GONE);
}
mTabScrollView.setVisibility(View.GONE);
break;
}
mActionView.setNavigationMode(mode);
switch (mode) {
case NAVIGATION_MODE_TABS:
ensureTabsExist();
if (!mActionView.hasEmbeddedTabs()) {
mTabScrollView.setVisibility(View.VISIBLE);
}
mTabScrollView.setVisibility(View.VISIBLE);
if (mSavedTabPosition != INVALID_POSITION) {
setSelectedNavigationItem(mSavedTabPosition);
mSavedTabPosition = INVALID_POSITION;

View File

@@ -102,7 +102,7 @@ public class ActionBarContainer extends FrameLayout {
return true;
}
public void setTabContainer(View tabView) {
public void setTabContainer(ScrollingTabContainerView tabView) {
if (mTabContainer != null) {
removeView(mTabContainer);
}
@@ -110,6 +110,7 @@ public class ActionBarContainer extends FrameLayout {
if (tabView != null) {
addView(tabView);
tabView.getLayoutParams().width = LayoutParams.MATCH_PARENT;
tabView.setAllowCollapse(false);
}
}

View File

@@ -332,7 +332,10 @@ public class ActionBarView extends AbsActionBarView {
mIncludeTabs = tabs != null;
if (mIncludeTabs && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
addView(mTabScrollView);
mTabScrollView.getLayoutParams().width = LayoutParams.WRAP_CONTENT;
ViewGroup.LayoutParams lp = mTabScrollView.getLayoutParams();
lp.width = LayoutParams.WRAP_CONTENT;
lp.height = LayoutParams.MATCH_PARENT;
tabs.setAllowCollapse(true);
}
}
@@ -649,18 +652,6 @@ public class ActionBarView extends AbsActionBarView {
}
}
public ScrollingTabContainerView createTabContainer() {
final LinearLayout tabLayout = new LinearLayout(getContext(), null,
com.android.internal.R.attr.actionBarTabBarStyle);
tabLayout.setMeasureWithLargestChildEnabled(true);
tabLayout.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, mContentHeight));
final ScrollingTabContainerView scroller = new ScrollingTabContainerView(mContext);
scroller.setTabLayout(tabLayout);
return scroller;
}
public void setDropdownAdapter(SpinnerAdapter adapter) {
mSpinnerAdapter = adapter;
if (mSpinner != null) {

View File

@@ -30,18 +30,32 @@ import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
public class ScrollingTabContainerView extends HorizontalScrollView {
/**
* This widget implements the dynamic action bar tab behavior that can change
* across different configurations or circumstances.
*/
public class ScrollingTabContainerView extends HorizontalScrollView
implements AdapterView.OnItemSelectedListener {
private static final String TAG = "ScrollingTabContainerView";
Runnable mTabSelector;
private TabClickListener mTabClickListener;
private LinearLayout mTabLayout;
private Spinner mTabSpinner;
private boolean mAllowCollapse;
int mMaxTabWidth;
private int mContentHeight;
private int mSelectedTabIndex;
protected Animator mVisibilityAnim;
protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
@@ -53,14 +67,19 @@ public class ScrollingTabContainerView extends HorizontalScrollView {
public ScrollingTabContainerView(Context context) {
super(context);
setHorizontalScrollBarEnabled(false);
mTabLayout = createTabLayout();
addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT));
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
setFillViewport(widthMode == MeasureSpec.EXACTLY);
final boolean lockedExpanded = widthMode == MeasureSpec.EXACTLY;
setFillViewport(lockedExpanded);
final int childCount = getChildCount();
final int childCount = mTabLayout.getChildCount();
if (childCount > 1 &&
(widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)) {
if (childCount > 2) {
@@ -72,14 +91,85 @@ public class ScrollingTabContainerView extends HorizontalScrollView {
mMaxTabWidth = -1;
}
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode != MeasureSpec.UNSPECIFIED) {
if (mContentHeight == 0 && heightMode == MeasureSpec.EXACTLY) {
// Use this as our content height.
mContentHeight = heightSize;
}
heightSize = Math.min(heightSize, mContentHeight);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
}
final boolean canCollapse = !lockedExpanded && mAllowCollapse;
if (canCollapse) {
// See if we should expand
mTabLayout.measure(MeasureSpec.UNSPECIFIED, heightMeasureSpec);
if (mTabLayout.getMeasuredWidth() > MeasureSpec.getSize(widthMeasureSpec)) {
performCollapse();
} else {
performExpand();
}
} else {
performExpand();
}
final int oldWidth = getMeasuredWidth();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int newWidth = getMeasuredWidth();
if (lockedExpanded && oldWidth != newWidth) {
// Recenter the tab display if we're at a new (scrollable) size.
setTabSelected(mSelectedTabIndex);
}
}
/**
* Indicates whether this view is collapsed into a dropdown menu instead
* of traditional tabs.
* @return true if showing as a spinner
*/
private boolean isCollapsed() {
return mTabSpinner != null && mTabSpinner.getParent() == this;
}
public void setAllowCollapse(boolean allowCollapse) {
mAllowCollapse = allowCollapse;
}
private void performCollapse() {
if (isCollapsed()) return;
if (mTabSpinner == null) {
mTabSpinner = createSpinner();
}
removeView(mTabLayout);
addView(mTabSpinner, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT));
if (mTabSpinner.getAdapter() == null) {
mTabSpinner.setAdapter(new TabAdapter());
}
if (mTabSelector != null) {
removeCallbacks(mTabSelector);
mTabSelector = null;
}
mTabSpinner.setSelection(mSelectedTabIndex);
}
private boolean performExpand() {
if (!isCollapsed()) return false;
removeView(mTabSpinner);
addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT));
setTabSelected(mTabSpinner.getSelectedItemPosition());
return false;
}
public void setTabSelected(int position) {
if (mTabLayout == null) {
return;
}
mSelectedTabIndex = position;
final int tabCount = mTabLayout.getChildCount();
for (int i = 0; i < tabCount; i++) {
final View child = mTabLayout.getChildAt(i);
@@ -92,10 +182,28 @@ public class ScrollingTabContainerView extends HorizontalScrollView {
}
public void setContentHeight(int contentHeight) {
mTabLayout.getLayoutParams().height = contentHeight;
mContentHeight = contentHeight;
requestLayout();
}
private LinearLayout createTabLayout() {
final LinearLayout tabLayout = new LinearLayout(getContext(), null,
com.android.internal.R.attr.actionBarTabBarStyle);
tabLayout.setMeasureWithLargestChildEnabled(true);
tabLayout.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT));
return tabLayout;
}
private Spinner createSpinner() {
final Spinner spinner = new Spinner(getContext(), null,
com.android.internal.R.attr.actionDropDownStyle);
spinner.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT));
spinner.setOnItemSelectedListener(this);
return spinner;
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -132,7 +240,7 @@ public class ScrollingTabContainerView extends HorizontalScrollView {
}
}
public void animateToTab(int position) {
public void animateToTab(final int position) {
final View tabView = mTabLayout.getChildAt(position);
if (mTabSelector != null) {
removeCallbacks(mTabSelector);
@@ -147,22 +255,15 @@ public class ScrollingTabContainerView extends HorizontalScrollView {
post(mTabSelector);
}
public void setTabLayout(LinearLayout tabLayout) {
if (mTabLayout != tabLayout) {
if (mTabLayout != null) {
((ViewGroup) mTabLayout.getParent()).removeView(mTabLayout);
}
if (tabLayout != null) {
addView(tabLayout);
}
mTabLayout = tabLayout;
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
if (mTabSelector != null) {
// Re-post the selector we saved
post(mTabSelector);
}
}
public LinearLayout getTabLayout() {
return mTabLayout;
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
@@ -171,49 +272,91 @@ public class ScrollingTabContainerView extends HorizontalScrollView {
}
}
private TabView createTabView(ActionBar.Tab tab) {
final TabView tabView = new TabView(getContext(), tab);
tabView.setFocusable(true);
private TabView createTabView(ActionBar.Tab tab, boolean forAdapter) {
final TabView tabView = new TabView(getContext(), tab, forAdapter);
if (forAdapter) {
tabView.setBackgroundDrawable(null);
tabView.setLayoutParams(new ListView.LayoutParams(ListView.LayoutParams.MATCH_PARENT,
mContentHeight));
} else {
tabView.setFocusable(true);
if (mTabClickListener == null) {
mTabClickListener = new TabClickListener();
if (mTabClickListener == null) {
mTabClickListener = new TabClickListener();
}
tabView.setOnClickListener(mTabClickListener);
}
tabView.setOnClickListener(mTabClickListener);
return tabView;
}
public void addTab(ActionBar.Tab tab, boolean setSelected) {
View tabView = createTabView(tab);
TabView tabView = createTabView(tab, false);
mTabLayout.addView(tabView, new LinearLayout.LayoutParams(0,
LayoutParams.MATCH_PARENT, 1));
if (mTabSpinner != null) {
((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
}
if (setSelected) {
tabView.setSelected(true);
}
if (mAllowCollapse) {
requestLayout();
}
}
public void addTab(ActionBar.Tab tab, int position, boolean setSelected) {
final TabView tabView = createTabView(tab);
final TabView tabView = createTabView(tab, false);
mTabLayout.addView(tabView, position, new LinearLayout.LayoutParams(
0, LayoutParams.MATCH_PARENT, 1));
if (mTabSpinner != null) {
((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
}
if (setSelected) {
tabView.setSelected(true);
}
if (mAllowCollapse) {
requestLayout();
}
}
public void updateTab(int position) {
((TabView) mTabLayout.getChildAt(position)).update();
if (mTabSpinner != null) {
((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
}
if (mAllowCollapse) {
requestLayout();
}
}
public void removeTabAt(int position) {
if (mTabLayout != null) {
mTabLayout.removeViewAt(position);
mTabLayout.removeViewAt(position);
if (mTabSpinner != null) {
((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
}
if (mAllowCollapse) {
requestLayout();
}
}
public void removeAllTabs() {
if (mTabLayout != null) {
mTabLayout.removeAllViews();
mTabLayout.removeAllViews();
if (mTabSpinner != null) {
((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
}
if (mAllowCollapse) {
requestLayout();
}
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
TabView tabView = (TabView) view;
tabView.getTab().select();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
private class TabView extends LinearLayout {
@@ -222,10 +365,19 @@ public class ScrollingTabContainerView extends HorizontalScrollView {
private ImageView mIconView;
private View mCustomView;
public TabView(Context context, ActionBar.Tab tab) {
public TabView(Context context, ActionBar.Tab tab, boolean forList) {
super(context, null, com.android.internal.R.attr.actionBarTabStyle);
mTab = tab;
if (forList) {
setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
}
update();
}
public void bindTab(ActionBar.Tab tab) {
mTab = tab;
update();
}
@@ -303,6 +455,33 @@ public class ScrollingTabContainerView extends HorizontalScrollView {
}
}
private class TabAdapter extends BaseAdapter {
@Override
public int getCount() {
return mTabLayout.getChildCount();
}
@Override
public Object getItem(int position) {
return ((TabView) mTabLayout.getChildAt(position)).getTab();
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = createTabView((ActionBar.Tab) getItem(position), true);
} else {
((TabView) convertView).bindTab((ActionBar.Tab) getItem(position));
}
return convertView;
}
}
private class TabClickListener implements OnClickListener {
public void onClick(View view) {
TabView tabView = (TabView) view;