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:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user