From 661c908e4e26c99adc2cab7558a02129eaee059d Mon Sep 17 00:00:00 2001 From: Adam Powell Date: Fri, 2 Jul 2010 10:09:44 -0700 Subject: [PATCH] ActionBar tab mode using fragments; needs styles. Change-Id: I8f931dae447e7b64de827d3c17766e5f8ad794e2 --- api/current.xml | 237 ++++++++++++++++++ core/java/android/app/ActionBar.java | 164 +++++++++++- core/java/android/app/Activity.java | 4 +- .../android/internal/app/ActionBarImpl.java | 203 ++++++++++++++- .../internal/widget/ActionBarView.java | 120 ++++++++- 5 files changed, 721 insertions(+), 7 deletions(-) diff --git a/api/current.xml b/api/current.xml index 1fadcfb1f0f71..4d86a04c79fd8 100644 --- a/api/current.xml +++ b/api/current.xml @@ -19457,6 +19457,19 @@ visibility="public" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + callback. + * The {@link ContextModeCallback} will receive lifecycle events for the duration + * of the context mode. + * + * @param callback Callback handler that will manage this context mode. + */ public abstract void startContextMode(ContextModeCallback callback); + + /** + * Finish the current context mode. + */ public abstract void finishContextMode(); - + + /** + * Set the action bar into tabbed navigation mode. + * + * @see #addTab(Tab) + * @see #insertTab(Tab, int) + * @see #removeTab(Tab) + * @see #removeTabAt(int) + */ + 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. + * + * @return A new Tab + * + * @see #addTab(Tab) + * @see #insertTab(Tab, int) + */ + public abstract Tab newTab(); + + /** + * Add a tab for use in tabbed navigation mode. The tab will be added at the end of the list. + * + * @param tab Tab to add + */ + public abstract void addTab(Tab tab); + + /** + * Insert a tab for use in tabbed navigation mode. The tab will be inserted at + * position. + * + * @param tab The tab to add + * @param position The new position of the tab + */ + public abstract void insertTab(Tab tab, int position); + + /** + * Remove a tab from the action bar. + * + * @param tab The tab to remove + */ + public abstract void removeTab(Tab tab); + + /** + * Remove a tab from the action bar. + * + * @param position Position of the tab to remove + */ + public abstract void removeTabAt(int position); + + /** + * Select the specified tab. If it is not a child of this action bar it will be added. + * + * @param tab Tab to select + */ + public abstract void selectTab(Tab tab); + + /** + * Select the tab at position + * + * @param position Position of the tab to select + */ + public abstract void selectTabAt(int position); + /** * Represents a contextual mode of the Action Bar. Context modes can be used for * modal interactions with activity content and replace the normal Action Bar until finished. @@ -350,4 +440,74 @@ public abstract class ActionBar { */ public boolean onNavigationItemSelected(int itemPosition, long itemId); } + + /** + * A tab in the action bar. + * + *

Tabs manage the hiding and showing of {@link Fragment}s. + */ + public static abstract class Tab { + /** + * An invalid position for a tab. + * + * @see #getPosition() + */ + public static final int INVALID_POSITION = -1; + + /** + * Return the current position of this tab in the action bar. + * + * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in + * the action bar. + */ + public abstract int getPosition(); + + /** + * Return the icon associated with this tab. + * + * @return The tab's icon + */ + public abstract Drawable getIcon(); + + /** + * Return the text of this tab. + * + * @return The tab's text + */ + public abstract CharSequence getText(); + + /** + * Set the icon displayed on this tab. + * + * @param icon The drawable to use as an icon + */ + public abstract void setIcon(Drawable icon); + + /** + * Set the text displayed on this tab. Text may be truncated if there is not + * room to display the entire string. + * + * @param text The text to display + */ + public abstract void setText(CharSequence text); + + /** + * Returns the fragment that will be shown when this tab is selected. + * + * @return Fragment associated with this tab + */ + public abstract Fragment getFragment(); + + /** + * Set the fragment that will be shown when this tab is selected. + * + * @param fragment Fragment to associate with this tab + */ + public abstract void setFragment(Fragment fragment); + + /** + * Select this tab. Only valid if the tab has been added to the action bar. + */ + public abstract void select(); + } } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 8825b8cc75879..4166b897e8362 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1676,11 +1676,11 @@ public class Activity extends ContextThemeWrapper */ private void initActionBar() { Window window = getWindow(); - if (!window.hasFeature(Window.FEATURE_ACTION_BAR)) { + if (!window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) { return; } - mActionBar = new ActionBarImpl(getWindow().getDecorView()); + mActionBar = new ActionBarImpl(this); } /** diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java index 6ac68aa3b5456..6cf455c341bf8 100644 --- a/core/java/com/android/internal/app/ActionBarImpl.java +++ b/core/java/com/android/internal/app/ActionBarImpl.java @@ -22,6 +22,9 @@ import com.android.internal.widget.ActionBarContextView; import com.android.internal.widget.ActionBarView; import android.app.ActionBar; +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentTransaction; import android.graphics.drawable.Drawable; import android.os.Handler; import android.view.Menu; @@ -31,6 +34,8 @@ import android.widget.LinearLayout; import android.widget.SpinnerAdapter; import android.widget.ViewAnimator; +import java.util.ArrayList; + /** * ActionBarImpl is the ActionBar implementation used * by devices of all screen sizes. If it detects a compatible decor, @@ -42,10 +47,21 @@ public class ActionBarImpl extends ActionBar { private static final int NORMAL_VIEW = 0; private static final int CONTEXT_VIEW = 1; + private static final int TAB_SWITCH_SHOW_HIDE = 0; + private static final int TAB_SWITCH_ADD_REMOVE = 1; + + private Activity mActivity; + private ViewAnimator mAnimatorView; private ActionBarView mActionView; private ActionBarContextView mUpperContextView; private LinearLayout mLowerContextView; + + private ArrayList mTabs = new ArrayList(); + + private int mTabContainerViewId = android.R.id.content; + private TabImpl mSelectedTab; + private int mTabSwitchMode = TAB_SWITCH_ADD_REMOVE; private ContextMode mContextMode; @@ -64,7 +80,9 @@ public class ActionBarImpl extends ActionBar { } }; - public ActionBarImpl(View decor) { + public ActionBarImpl(Activity activity) { + final View decor = activity.getWindow().getDecorView(); + mActivity = activity; mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar); mUpperContextView = (ActionBarContextView) decor.findViewById( com.android.internal.R.id.action_context_bar); @@ -83,32 +101,54 @@ public class ActionBarImpl extends ActionBar { } public void setCustomNavigationMode(View view) { + cleanupTabs(); mActionView.setCustomNavigationView(view); mActionView.setCallback(null); } public void setDropdownNavigationMode(SpinnerAdapter adapter, NavigationCallback callback) { + cleanupTabs(); mActionView.setCallback(callback); mActionView.setNavigationMode(NAVIGATION_MODE_DROPDOWN_LIST); mActionView.setDropdownAdapter(adapter); } public void setStandardNavigationMode() { + cleanupTabs(); mActionView.setNavigationMode(NAVIGATION_MODE_STANDARD); 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); } + private void cleanupTabs() { + 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(); + } + } + public void setTitle(CharSequence title) { mActionView.setTitle(title); } @@ -174,6 +214,113 @@ public class ActionBarImpl extends ActionBar { } } + 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(); + + 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); + } + mSelectedTab = tabi; + } else { + if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) { + trans.hide(frag); + } + } + trans.commit(); + } + + @Override + public void addTab(Tab tab) { + mActionView.addTab(tab); + configureTab(tab, mTabs.size()); + } + + @Override + public void insertTab(Tab tab, int position) { + mActionView.insertTab(tab, position); + configureTab(tab, position); + } + + @Override + public Tab newTab() { + return new TabImpl(); + } + + @Override + public void removeTab(Tab tab) { + removeTabAt(tab.getPosition()); + } + + @Override + public void removeTabAt(int position) { + mActionView.removeTabAt(position); + mTabs.remove(position); + + final int newTabCount = mTabs.size(); + for (int i = position; i < newTabCount; i++) { + mTabs.get(i).setPosition(i); + } + + selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1))); + } + + @Override + public void setTabNavigationMode() { + mActionView.setNavigationMode(NAVIGATION_MODE_TABS); + } + + @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(); + 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 = (TabImpl) tab; + trans.commit(); + } + + @Override + public void selectTabAt(int position) { + selectTab(mTabs.get(position)); + } + /** * @hide */ @@ -233,6 +380,60 @@ public class ActionBarImpl extends ActionBar { if (!actionItem.invoke()) { mCallback.onContextItemClicked(this, item); } + } + } + + /** + * @hide + */ + public class TabImpl extends ActionBar.Tab { + private Fragment mFragment; + private Drawable mIcon; + private CharSequence mText; + private int mPosition; + + @Override + public Fragment getFragment() { + return mFragment; + } + + @Override + public Drawable getIcon() { + return mIcon; + } + + @Override + public int getPosition() { + return mPosition; + } + + public void setPosition(int position) { + mPosition = position; + } + + @Override + public CharSequence getText() { + return mText; + } + + @Override + public void setFragment(Fragment fragment) { + mFragment = fragment; + } + + @Override + public void setIcon(Drawable icon) { + mIcon = icon; + } + + @Override + public void setText(CharSequence text) { + mText = text; + } + + @Override + public void select() { + selectTab(this); } } } diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index e919f1b395287..48707b9595485 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -31,8 +31,10 @@ import android.content.res.TypedArray; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; +import android.text.TextUtils.TruncateAt; import android.util.AttributeSet; import android.util.DisplayMetrics; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; import android.view.View; @@ -85,6 +87,7 @@ public class ActionBarView extends ViewGroup { private TextView mTitleView; private TextView mSubtitleView; private Spinner mSpinner; + private LinearLayout mTabLayout; private View mCustomNavView; private boolean mShowMenu; @@ -128,7 +131,8 @@ public class ActionBarView extends ViewGroup { ApplicationInfo info = context.getApplicationInfo(); PackageManager pm = context.getPackageManager(); - mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode, ActionBar.NAVIGATION_MODE_STANDARD); + mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode, + ActionBar.NAVIGATION_MODE_STANDARD); mTitle = a.getText(R.styleable.ActionBar_title); mSubtitle = a.getText(R.styleable.ActionBar_subtitle); mDisplayOptions = a.getInt(R.styleable.ActionBar_displayOptions, DISPLAY_DEFAULT); @@ -271,6 +275,11 @@ public class ActionBarView extends ViewGroup { mCustomNavView = null; } break; + case ActionBar.NAVIGATION_MODE_TABS: + if (mTabLayout != null) { + removeView(mTabLayout); + mTabLayout = null; + } } switch (mode) { @@ -286,6 +295,10 @@ public class ActionBarView extends ViewGroup { case ActionBar.NAVIGATION_MODE_CUSTOM: addView(mCustomNavView); break; + case ActionBar.NAVIGATION_MODE_TABS: + mTabLayout = new LinearLayout(getContext()); + addView(mTabLayout); + break; } mNavigationMode = mode; requestLayout(); @@ -308,6 +321,35 @@ public class ActionBarView extends ViewGroup { return mDisplayOptions; } + private TabView createTabView(ActionBar.Tab tab) { + final TabView tabView = new TabView(getContext(), tab); + tabView.setFocusable(true); + tabView.setOnClickListener(new TabClickListener()); + return tabView; + } + + public void addTab(ActionBar.Tab tab) { + final boolean isFirst = mTabLayout.getChildCount() == 0; + final TabView tabView = createTabView(tab); + mTabLayout.addView(tabView); + if (isFirst) { + tabView.setSelected(true); + } + } + + public void insertTab(ActionBar.Tab tab, int position) { + final boolean isFirst = mTabLayout.getChildCount() == 0; + final TabView tabView = createTabView(tab); + mTabLayout.addView(tabView, position); + if (isFirst) { + tabView.setSelected(true); + } + } + + public void removeTabAt(int position) { + mTabLayout.removeViewAt(position); + } + @Override protected LayoutParams generateDefaultLayoutParams() { // Used by custom nav views if they don't supply layout params. Everything else @@ -356,7 +398,7 @@ public class ActionBarView extends ViewGroup { case ActionBar.NAVIGATION_MODE_TABS: throw new UnsupportedOperationException( - "Tab navigation isn't supported yet!"); + "Inflating tab navigation isn't supported yet!"); case ActionBar.NAVIGATION_MODE_CUSTOM: if (mCustomNavView != null) { @@ -381,6 +423,14 @@ public class ActionBarView extends ViewGroup { addView(mTitleLayout); } + public void setTabSelected(int position) { + final int tabCount = mTabLayout.getChildCount(); + for (int i = 0; i < tabCount; i++) { + final View child = mTabLayout.getChildAt(i); + child.setSelected(i == position); + } + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); @@ -442,6 +492,13 @@ public class ActionBarView extends ViewGroup { MeasureSpec.makeMeasureSpec(customNavHeight, customNavHeightMode)); } break; + case ActionBar.NAVIGATION_MODE_TABS: + if (mTabLayout != null) { + mTabLayout.measure( + MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + } + break; } setMeasuredDimension(contentWidth, mContentHeight); @@ -486,6 +543,10 @@ public class ActionBarView extends ViewGroup { x += positionChild(mCustomNavView, x, y, contentHeight) + mSpacing; } break; + case ActionBar.NAVIGATION_MODE_TABS: + if (mTabLayout != null) { + x += positionChild(mTabLayout, x, y, contentHeight) + mSpacing; + } } x = r - l - getPaddingRight(); @@ -515,4 +576,59 @@ public class ActionBarView extends ViewGroup { return childWidth; } + + private static class TabView extends LinearLayout { + private ActionBar.Tab mTab; + + public TabView(Context context, ActionBar.Tab tab) { + super(context); + mTab = tab; + + // TODO Style tabs based on the theme + + 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 (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, + LayoutParams.MATCH_PARENT, 1)); + } + + public ActionBar.Tab getTab() { + return mTab; + } + } + + private class TabClickListener implements OnClickListener { + public void onClick(View view) { + TabView tabView = (TabView) view; + tabView.getTab().select(); + final int tabCount = mTabLayout.getChildCount(); + for (int i = 0; i < tabCount; i++) { + final View child = mTabLayout.getChildAt(i); + child.setSelected(child == view); + } + } + } }