Reworking the ActionBar tab API
Change-Id: Ifbcdc61b4a24633cc1a329c73923b95b03e9ecf0
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 38086f0..29f2e30 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -123,72 +123,6 @@
public abstract int getSelectedNavigationItem();
/**
- * Set the action bar into standard navigation mode, supplying a title and subtitle.
- *
- * Standard navigation mode is default. The title is automatically set to the
- * name of your Activity. Subtitles are displayed underneath the title, usually
- * in a smaller font or otherwise less prominently than the title. Subtitles are
- * good for extended descriptions of activity state.
- *
- * @param title The action bar's title. null is treated as an empty string.
- * @param subtitle The action bar's subtitle. null will remove the subtitle entirely.
- *
- * @see #setStandardNavigationMode()
- * @see #setStandardNavigationMode(CharSequence)
- * @see #setStandardNavigationMode(int)
- * @see #setStandardNavigationMode(int, int)
- */
- public abstract void setStandardNavigationMode(CharSequence title, CharSequence subtitle);
-
- /**
- * Set the action bar into standard navigation mode, supplying a title and subtitle.
- *
- * Standard navigation mode is default. The title is automatically set to the
- * name of your Activity. Subtitles are displayed underneath the title, usually
- * in a smaller font or otherwise less prominently than the title. Subtitles are
- * good for extended descriptions of activity state.
- *
- * @param titleResId Resource ID of a title string
- * @param subtitleResId Resource ID of a subtitle string
- *
- * @see #setStandardNavigationMode()
- * @see #setStandardNavigationMode(CharSequence)
- * @see #setStandardNavigationMode(CharSequence, CharSequence)
- * @see #setStandardNavigationMode(int)
- */
- public abstract void setStandardNavigationMode(int titleResId, int subtitleResId);
-
- /**
- * Set the action bar into standard navigation mode, supplying a title and subtitle.
- *
- * Standard navigation mode is default. The title is automatically set to the
- * name of your Activity on startup if an action bar is present.
- *
- * @param title The action bar's title. null is treated as an empty string.
- *
- * @see #setStandardNavigationMode()
- * @see #setStandardNavigationMode(CharSequence, CharSequence)
- * @see #setStandardNavigationMode(int)
- * @see #setStandardNavigationMode(int, int)
- */
- public abstract void setStandardNavigationMode(CharSequence title);
-
- /**
- * Set the action bar into standard navigation mode, supplying a title and subtitle.
- *
- * Standard navigation mode is default. The title is automatically set to the
- * name of your Activity on startup if an action bar is present.
- *
- * @param titleResId Resource ID of a title string
- *
- * @see #setStandardNavigationMode()
- * @see #setStandardNavigationMode(CharSequence)
- * @see #setStandardNavigationMode(CharSequence, CharSequence)
- * @see #setStandardNavigationMode(int, int)
- */
- public abstract void setStandardNavigationMode(int titleResId);
-
- /**
* Set the action bar into standard navigation mode, using the currently set title
* and/or subtitle.
*
@@ -324,18 +258,6 @@
public abstract void setTabNavigationMode();
/**
- * Set the action bar into tabbed navigation mode.
- *
- * @param containerViewId Id of the container view where tab content fragments should appear.
- *
- * @see #addTab(Tab)
- * @see #insertTab(Tab, int)
- * @see #removeTab(Tab)
- * @see #removeTabAt(int)
- */
- public abstract void setTabNavigationMode(int containerViewId);
-
- /**
* Create and return a new {@link Tab}.
* This tab will not be included in the action bar until it is added.
*
@@ -354,13 +276,13 @@
public abstract void addTab(Tab tab);
/**
- * Insert a tab for use in tabbed navigation mode. The tab will be inserted at
+ * Add a tab for use in tabbed navigation mode. The tab will be inserted at
* <code>position</code>.
*
* @param tab The tab to add
* @param position The new position of the tab
*/
- public abstract void insertTab(Tab tab, int position);
+ public abstract void addTab(Tab tab, int position);
/**
* Remove a tab from the action bar.
@@ -384,6 +306,14 @@
public abstract void selectTab(Tab tab);
/**
+ * Returns the currently selected tab if in tabbed navigation mode and there is at least
+ * one tab present.
+ *
+ * @return The currently selected tab or null
+ */
+ public abstract Tab getSelectedTab();
+
+ /**
* Retrieve the current height of the ActionBar.
*
* @return The ActionBar's height
@@ -477,22 +407,68 @@
public abstract void setText(CharSequence text);
/**
- * Returns the fragment that will be shown when this tab is selected.
+ * Set a custom view to be used for this tab. This overrides values set by
+ * {@link #setText(CharSequence)} and {@link #setIcon(Drawable)}.
*
- * @return Fragment associated with this tab
+ * @param view Custom view to be used as a tab.
*/
- public abstract Fragment getFragment();
+ public abstract void setCustomView(View view);
/**
- * Set the fragment that will be shown when this tab is selected.
+ * Retrieve a previously set custom view for this tab.
*
- * @param fragment Fragment to associate with this tab
+ * @return The custom view set by {@link #setCustomView(View)}.
*/
- public abstract void setFragment(Fragment fragment);
+ public abstract View getCustomView();
+
+ /**
+ * Give this Tab an arbitrary object to hold for later use.
+ *
+ * @param obj Object to store
+ */
+ public abstract void setTag(Object obj);
+
+ /**
+ * @return This Tab's tag object.
+ */
+ public abstract Object getTag();
+
+ /**
+ * Set the {@link TabListener} that will handle switching to and from this tab.
+ * All tabs must have a TabListener set before being added to the ActionBar.
+ *
+ * @param listener Listener to handle tab selection events
+ */
+ public abstract void setTabListener(TabListener listener);
/**
* Select this tab. Only valid if the tab has been added to the action bar.
*/
public abstract void select();
}
+
+ /**
+ * Callback interface invoked when a tab is focused, unfocused, added, or removed.
+ */
+ public interface TabListener {
+ /**
+ * Called when a tab enters the selected state.
+ *
+ * @param tab The tab that was selected
+ * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute
+ * during a tab switch. The previous tab's unselect and this tab's select will be
+ * executed in a single transaction.
+ */
+ public void onTabSelected(Tab tab, FragmentTransaction ft);
+
+ /**
+ * Called when a tab exits the selected state.
+ *
+ * @param tab The tab that was unselected
+ * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute
+ * during a tab switch. This tab's unselect and the newly selected tab's select
+ * will be executed in a single transaction.
+ */
+ public void onTabUnselected(Tab tab, FragmentTransaction ft);
+ }
}
diff --git a/core/java/android/app/BackStackEntry.java b/core/java/android/app/BackStackEntry.java
index 00c2fc4..71fd5e5 100644
--- a/core/java/android/app/BackStackEntry.java
+++ b/core/java/android/app/BackStackEntry.java
@@ -490,4 +490,8 @@
public int getTransitionStyle() {
return mTransitionStyle;
}
+
+ public boolean isEmpty() {
+ return mNumOp == 0;
+ }
}
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 04598a3..9d44106 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -85,6 +85,12 @@
* @return Returns the same FragmentTransaction instance.
*/
public FragmentTransaction show(Fragment fragment);
+
+ /**
+ * @return <code>true</code> if this transaction contains no operations,
+ * <code>false</code> otherwise.
+ */
+ public boolean isEmpty();
/**
* Bit mask that is set for all enter transitions.
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index 1048352..cf029d2 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -122,17 +122,6 @@
}
@Override
- public void setStandardNavigationMode(int titleResId, int subtitleResId) {
- setStandardNavigationMode(mContext.getString(titleResId),
- mContext.getString(subtitleResId));
- }
-
- @Override
- public void setStandardNavigationMode(int titleResId) {
- setStandardNavigationMode(mContext.getString(titleResId));
- }
-
- @Override
public void setTitle(int resId) {
setTitle(mContext.getString(resId));
}
@@ -169,19 +158,6 @@
mActionView.setCallback(null);
}
- public void setStandardNavigationMode(CharSequence title) {
- cleanupTabs();
- setStandardNavigationMode(title, null);
- }
-
- public void setStandardNavigationMode(CharSequence title, CharSequence subtitle) {
- cleanupTabs();
- mActionView.setNavigationMode(NAVIGATION_MODE_STANDARD);
- mActionView.setTitle(title);
- mActionView.setSubtitle(subtitle);
- mActionView.setCallback(null);
- }
-
public void setSelectedNavigationItem(int position) {
switch (mActionView.getNavigationMode()) {
case NAVIGATION_MODE_TABS:
@@ -211,17 +187,7 @@
if (mSelectedTab != null) {
selectTab(null);
}
- if (!mTabs.isEmpty()) {
- if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
- final FragmentTransaction trans = mActivity.openFragmentTransaction();
- final int tabCount = mTabs.size();
- for (int i = 0; i < tabCount; i++) {
- trans.remove(mTabs.get(i).getFragment());
- }
- trans.commit();
- }
- mTabs.clear();
- }
+ mTabs.clear();
}
public void setTitle(CharSequence title) {
@@ -295,31 +261,23 @@
private void configureTab(Tab tab, int position) {
final TabImpl tabi = (TabImpl) tab;
final boolean isFirstTab = mTabs.isEmpty();
- final FragmentTransaction trans = mActivity.openFragmentTransaction();
- final Fragment frag = tabi.getFragment();
+ final ActionBar.TabListener callback = tabi.getCallback();
+
+ if (callback == null) {
+ throw new IllegalStateException("Action Bar Tab must have a Callback");
+ }
tabi.setPosition(position);
mTabs.add(position, tabi);
- if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
- if (!frag.isAdded()) {
- trans.add(mTabContainerViewId, frag);
- }
- }
-
if (isFirstTab) {
- if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
- trans.show(frag);
- } else if (mTabSwitchMode == TAB_SWITCH_ADD_REMOVE) {
- trans.add(mTabContainerViewId, frag);
- }
+ final FragmentTransaction trans = mActivity.getFragmentManager().openTransaction();
mSelectedTab = tabi;
- } else {
- if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
- trans.hide(frag);
+ callback.onTabSelected(tab, trans);
+ if (!trans.isEmpty()) {
+ trans.commit();
}
}
- trans.commit();
}
@Override
@@ -329,8 +287,8 @@
}
@Override
- public void insertTab(Tab tab, int position) {
- mActionView.insertTab(tab, position);
+ public void addTab(Tab tab, int position) {
+ mActionView.addTab(tab, position);
configureTab(tab, position);
}
@@ -367,35 +325,29 @@
}
@Override
- public void setTabNavigationMode(int containerViewId) {
- mTabContainerViewId = containerViewId;
- setTabNavigationMode();
- }
-
- @Override
public void selectTab(Tab tab) {
if (mSelectedTab == tab) {
return;
}
mActionView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION);
- final FragmentTransaction trans = mActivity.openFragmentTransaction();
+ final FragmentTransaction trans = mActivity.getFragmentManager().openTransaction();
if (mSelectedTab != null) {
- if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
- trans.hide(mSelectedTab.getFragment());
- } else if (mTabSwitchMode == TAB_SWITCH_ADD_REMOVE) {
- trans.remove(mSelectedTab.getFragment());
- }
- }
- if (tab != null) {
- if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
- trans.show(tab.getFragment());
- } else if (mTabSwitchMode == TAB_SWITCH_ADD_REMOVE) {
- trans.add(mTabContainerViewId, tab.getFragment());
- }
+ mSelectedTab.getCallback().onTabUnselected(mSelectedTab, trans);
}
mSelectedTab = (TabImpl) tab;
- trans.commit();
+ if (mSelectedTab != null) {
+ mSelectedTab.getCallback().onTabSelected(mSelectedTab, trans);
+ }
+
+ if (!trans.isEmpty()) {
+ trans.commit();
+ }
+ }
+
+ @Override
+ public Tab getSelectedTab() {
+ return mSelectedTab;
}
@Override
@@ -542,14 +494,40 @@
* @hide
*/
public class TabImpl extends ActionBar.Tab {
- private Fragment mFragment;
+ private ActionBar.TabListener mCallback;
+ private Object mTag;
private Drawable mIcon;
private CharSequence mText;
private int mPosition;
+ private View mCustomView;
@Override
- public Fragment getFragment() {
- return mFragment;
+ public Object getTag() {
+ return mTag;
+ }
+
+ @Override
+ public void setTag(Object tag) {
+ mTag = tag;
+ }
+
+ public ActionBar.TabListener getCallback() {
+ return mCallback;
+ }
+
+ @Override
+ public void setTabListener(ActionBar.TabListener callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public View getCustomView() {
+ return mCustomView;
+ }
+
+ @Override
+ public void setCustomView(View view) {
+ mCustomView = view;
}
@Override
@@ -572,11 +550,6 @@
}
@Override
- public void setFragment(Fragment fragment) {
- mFragment = fragment;
- }
-
- @Override
public void setIcon(Drawable icon) {
mIcon = icon;
}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index b9e4e46..6b3d353 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -41,6 +41,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
+import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Spinner;
@@ -87,6 +88,7 @@
private TextView mTitleView;
private TextView mSubtitleView;
private Spinner mSpinner;
+ private HorizontalScrollView mTabScrollView;
private LinearLayout mTabLayout;
private View mCustomNavView;
@@ -119,6 +121,8 @@
private OnClickListener mHomeClickListener = null;
+ private OnClickListener mTabClickListener = null;
+
public ActionBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -346,8 +350,9 @@
break;
case ActionBar.NAVIGATION_MODE_TABS:
if (mTabLayout != null) {
- removeView(mTabLayout);
+ removeView(mTabScrollView);
mTabLayout = null;
+ mTabScrollView = null;
}
}
@@ -365,8 +370,10 @@
addView(mCustomNavView);
break;
case ActionBar.NAVIGATION_MODE_TABS:
+ mTabScrollView = new HorizontalScrollView(getContext());
mTabLayout = new LinearLayout(getContext());
- addView(mTabLayout);
+ mTabScrollView.addView(mTabLayout);
+ addView(mTabScrollView);
break;
}
mNavigationMode = mode;
@@ -401,20 +408,24 @@
private TabView createTabView(ActionBar.Tab tab) {
final TabView tabView = new TabView(getContext(), tab);
tabView.setFocusable(true);
- tabView.setOnClickListener(new TabClickListener());
+
+ if (mTabClickListener == null) {
+ mTabClickListener = new TabClickListener();
+ }
+ tabView.setOnClickListener(mTabClickListener);
return tabView;
}
public void addTab(ActionBar.Tab tab) {
final boolean isFirst = mTabLayout.getChildCount() == 0;
- final TabView tabView = createTabView(tab);
+ View tabView = createTabView(tab);
mTabLayout.addView(tabView);
if (isFirst) {
tabView.setSelected(true);
}
}
- public void insertTab(ActionBar.Tab tab, int position) {
+ public void addTab(ActionBar.Tab tab, int position) {
final boolean isFirst = mTabLayout.getChildCount() == 0;
final TabView tabView = createTabView(tab);
mTabLayout.addView(tabView, position);
@@ -595,8 +606,8 @@
}
break;
case ActionBar.NAVIGATION_MODE_TABS:
- if (mTabLayout != null) {
- mTabLayout.measure(
+ if (mTabScrollView != null) {
+ mTabScrollView.measure(
MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
}
@@ -663,8 +674,8 @@
}
break;
case ActionBar.NAVIGATION_MODE_TABS:
- if (mTabLayout != null) {
- x += positionChild(mTabLayout, x, y, contentHeight) + mSpacing;
+ if (mTabScrollView != null) {
+ x += positionChild(mTabScrollView, x, y, contentHeight) + mSpacing;
}
}
@@ -703,31 +714,36 @@
super(context);
mTab = tab;
- // TODO Style tabs based on the theme
+ final View custom = tab.getCustomView();
+ if (custom != null) {
+ addView(custom);
+ } else {
+ // TODO Style tabs based on the theme
- final Drawable icon = tab.getIcon();
- final CharSequence text = tab.getText();
+ final Drawable icon = tab.getIcon();
+ final CharSequence text = tab.getText();
- if (icon != null) {
- ImageView iconView = new ImageView(context);
- iconView.setImageDrawable(icon);
- LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT);
- lp.gravity = Gravity.CENTER_VERTICAL;
- iconView.setLayoutParams(lp);
- addView(iconView);
- }
+ if (icon != null) {
+ ImageView iconView = new ImageView(context);
+ iconView.setImageDrawable(icon);
+ LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ lp.gravity = Gravity.CENTER_VERTICAL;
+ iconView.setLayoutParams(lp);
+ addView(iconView);
+ }
- if (text != null) {
- TextView textView = new TextView(context);
- textView.setText(text);
- textView.setSingleLine();
- textView.setEllipsize(TruncateAt.END);
- LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT);
- lp.gravity = Gravity.CENTER_VERTICAL;
- textView.setLayoutParams(lp);
- addView(textView);
+ if (text != null) {
+ TextView textView = new TextView(context);
+ textView.setText(text);
+ textView.setSingleLine();
+ textView.setEllipsize(TruncateAt.END);
+ LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ lp.gravity = Gravity.CENTER_VERTICAL;
+ textView.setLayoutParams(lp);
+ addView(textView);
+ }
}
setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,