2 * Copyright (C) 2006 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package android.widget;
19 import com.android.internal.R;
21 import android.app.LocalActivityManager;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.graphics.drawable.Drawable;
25 import android.os.Build;
26 import android.util.AttributeSet;
27 import android.view.KeyEvent;
28 import android.view.LayoutInflater;
29 import android.view.SoundEffectConstants;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.ViewTreeObserver;
33 import android.view.Window;
35 import java.util.ArrayList;
36 import java.util.List;
39 * Container for a tabbed window view. This object holds two children: a set of tab labels that the
40 * user clicks to select a specific tab, and a FrameLayout object that displays the contents of that
41 * page. The individual elements are typically controlled using this container object, rather than
42 * setting values on the child elements themselves.
44 * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-tabwidget.html">Tab Layout
47 public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchModeChangeListener {
49 private TabWidget mTabWidget;
50 private FrameLayout mTabContent;
51 private List<TabSpec> mTabSpecs = new ArrayList<TabSpec>(2);
53 * This field should be made private, so it is hidden from the SDK.
56 protected int mCurrentTab = -1;
57 private View mCurrentView = null;
59 * This field should be made private, so it is hidden from the SDK.
62 protected LocalActivityManager mLocalActivityManager = null;
63 private OnTabChangeListener mOnTabChangeListener;
64 private OnKeyListener mTabKeyListener;
66 public TabHost(Context context) {
71 public TabHost(Context context, AttributeSet attrs) {
72 super(context, attrs);
76 private void initTabHost() {
77 setFocusableInTouchMode(true);
78 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
85 * Get a new {@link TabSpec} associated with this tab host.
86 * @param tag required tag of tab.
88 public TabSpec newTabSpec(String tag) {
89 return new TabSpec(tag);
95 * <p>Call setup() before adding tabs if loading TabHost using findViewById().
96 * <i><b>However</i></b>: You do not need to call setup() after getTabHost()
97 * in {@link android.app.TabActivity TabActivity}.
99 <pre>mTabHost = (TabHost)findViewById(R.id.tabhost);
101 mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
103 public void setup() {
104 mTabWidget = (TabWidget) findViewById(com.android.internal.R.id.tabs);
105 if (mTabWidget == null) {
106 throw new RuntimeException(
107 "Your TabHost must have a TabWidget whose id attribute is 'android.R.id.tabs'");
110 // KeyListener to attach to all tabs. Detects non-navigation keys
111 // and relays them to the tab content.
112 mTabKeyListener = new OnKeyListener() {
113 public boolean onKey(View v, int keyCode, KeyEvent event) {
115 case KeyEvent.KEYCODE_DPAD_CENTER:
116 case KeyEvent.KEYCODE_DPAD_LEFT:
117 case KeyEvent.KEYCODE_DPAD_RIGHT:
118 case KeyEvent.KEYCODE_DPAD_UP:
119 case KeyEvent.KEYCODE_DPAD_DOWN:
120 case KeyEvent.KEYCODE_ENTER:
124 mTabContent.requestFocus(View.FOCUS_FORWARD);
125 return mTabContent.dispatchKeyEvent(event);
130 mTabWidget.setTabSelectionListener(new TabWidget.OnTabSelectionChanged() {
131 public void onTabSelectionChanged(int tabIndex, boolean clicked) {
132 setCurrentTab(tabIndex);
134 mTabContent.requestFocus(View.FOCUS_FORWARD);
139 mTabContent = (FrameLayout) findViewById(com.android.internal.R.id.tabcontent);
140 if (mTabContent == null) {
141 throw new RuntimeException(
142 "Your TabHost must have a FrameLayout whose id attribute is "
143 + "'android.R.id.tabcontent'");
148 * If you are using {@link TabSpec#setContent(android.content.Intent)}, this
149 * must be called since the activityGroup is needed to launch the local activity.
151 * This is done for you if you extend {@link android.app.TabActivity}.
152 * @param activityGroup Used to launch activities for tab content.
154 public void setup(LocalActivityManager activityGroup) {
156 mLocalActivityManager = activityGroup;
161 protected void onAttachedToWindow() {
162 super.onAttachedToWindow();
163 final ViewTreeObserver treeObserver = getViewTreeObserver();
164 if (treeObserver != null) {
165 treeObserver.addOnTouchModeChangeListener(this);
170 protected void onDetachedFromWindow() {
171 super.onDetachedFromWindow();
172 final ViewTreeObserver treeObserver = getViewTreeObserver();
173 if (treeObserver != null) {
174 treeObserver.removeOnTouchModeChangeListener(this);
181 public void onTouchModeChanged(boolean isInTouchMode) {
182 if (!isInTouchMode) {
183 // leaving touch mode.. if nothing has focus, let's give it to
184 // the indicator of the current tab
185 if (mCurrentView != null && (!mCurrentView.hasFocus() || mCurrentView.isFocused())) {
186 mTabWidget.getChildTabViewAt(mCurrentTab).requestFocus();
193 * @param tabSpec Specifies how to create the indicator and content.
195 public void addTab(TabSpec tabSpec) {
197 if (tabSpec.mIndicatorStrategy == null) {
198 throw new IllegalArgumentException("you must specify a way to create the tab indicator.");
201 if (tabSpec.mContentStrategy == null) {
202 throw new IllegalArgumentException("you must specify a way to create the tab content");
204 View tabIndicator = tabSpec.mIndicatorStrategy.createIndicatorView();
205 tabIndicator.setOnKeyListener(mTabKeyListener);
207 // If this is a custom view, then do not draw the bottom strips for
208 // the tab indicators.
209 if (tabSpec.mIndicatorStrategy instanceof ViewIndicatorStrategy) {
210 mTabWidget.setStripEnabled(false);
212 mTabWidget.addView(tabIndicator);
213 mTabSpecs.add(tabSpec);
215 if (mCurrentTab == -1) {
222 * Removes all tabs from the tab widget associated with this tab host.
224 public void clearAllTabs() {
225 mTabWidget.removeAllViews();
227 mTabContent.removeAllViews();
233 public TabWidget getTabWidget() {
237 public int getCurrentTab() {
241 public String getCurrentTabTag() {
242 if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
243 return mTabSpecs.get(mCurrentTab).getTag();
248 public View getCurrentTabView() {
249 if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
250 return mTabWidget.getChildTabViewAt(mCurrentTab);
255 public View getCurrentView() {
259 public void setCurrentTabByTag(String tag) {
261 for (i = 0; i < mTabSpecs.size(); i++) {
262 if (mTabSpecs.get(i).getTag().equals(tag)) {
270 * Get the FrameLayout which holds tab content
272 public FrameLayout getTabContentView() {
277 public boolean dispatchKeyEvent(KeyEvent event) {
278 final boolean handled = super.dispatchKeyEvent(event);
280 // unhandled key ups change focus to tab indicator for embedded activities
281 // when there is nothing that will take focus from default focus searching
283 && (event.getAction() == KeyEvent.ACTION_DOWN)
284 && (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP)
285 && (mCurrentView != null)
286 && (mCurrentView.isRootNamespace())
287 && (mCurrentView.hasFocus())
288 && (mCurrentView.findFocus().focusSearch(View.FOCUS_UP) == null)) {
289 mTabWidget.getChildTabViewAt(mCurrentTab).requestFocus();
290 playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
298 public void dispatchWindowFocusChanged(boolean hasFocus) {
299 if (mCurrentView != null){
300 mCurrentView.dispatchWindowFocusChanged(hasFocus);
304 public void setCurrentTab(int index) {
305 if (index < 0 || index >= mTabSpecs.size()) {
309 if (index == mCurrentTab) {
313 // notify old tab content
314 if (mCurrentTab != -1) {
315 mTabSpecs.get(mCurrentTab).mContentStrategy.tabClosed();
319 final TabHost.TabSpec spec = mTabSpecs.get(index);
321 // Call the tab widget's focusCurrentTab(), instead of just
322 // selecting the tab.
323 mTabWidget.focusCurrentTab(mCurrentTab);
326 mCurrentView = spec.mContentStrategy.getContentView();
328 if (mCurrentView.getParent() == null) {
332 new ViewGroup.LayoutParams(
333 ViewGroup.LayoutParams.MATCH_PARENT,
334 ViewGroup.LayoutParams.MATCH_PARENT));
337 if (!mTabWidget.hasFocus()) {
338 // if the tab widget didn't take focus (likely because we're in touch mode)
339 // give the current tab content view a shot
340 mCurrentView.requestFocus();
343 //mTabContent.requestFocus(View.FOCUS_FORWARD);
344 invokeOnTabChangeListener();
348 * Register a callback to be invoked when the selected state of any of the items
349 * in this list changes
351 * The callback that will run
353 public void setOnTabChangedListener(OnTabChangeListener l) {
354 mOnTabChangeListener = l;
357 private void invokeOnTabChangeListener() {
358 if (mOnTabChangeListener != null) {
359 mOnTabChangeListener.onTabChanged(getCurrentTabTag());
364 * Interface definition for a callback to be invoked when tab changed
366 public interface OnTabChangeListener {
367 void onTabChanged(String tabId);
372 * Makes the content of a tab when it is selected. Use this if your tab
373 * content needs to be created on demand, i.e. you are not showing an
374 * existing view or starting an activity.
376 public interface TabContentFactory {
378 * Callback to make the tab contents
381 * Which tab was selected.
382 * @return The view to display the contents of the selected tab.
384 View createTabContent(String tag);
389 * A tab has a tab indicator, content, and a tag that is used to keep
390 * track of it. This builder helps choose among these options.
392 * For the tab indicator, your choices are:
394 * 2) set a label and an icon
396 * For the tab content, your choices are:
397 * 1) the id of a {@link View}
398 * 2) a {@link TabContentFactory} that creates the {@link View} content.
399 * 3) an {@link Intent} that launches an {@link android.app.Activity}.
401 public class TabSpec {
405 private IndicatorStrategy mIndicatorStrategy;
406 private ContentStrategy mContentStrategy;
408 private TabSpec(String tag) {
413 * Specify a label as the tab indicator.
415 public TabSpec setIndicator(CharSequence label) {
416 mIndicatorStrategy = new LabelIndicatorStrategy(label);
421 * Specify a label and icon as the tab indicator.
423 public TabSpec setIndicator(CharSequence label, Drawable icon) {
424 mIndicatorStrategy = new LabelAndIconIndicatorStrategy(label, icon);
429 * Specify a view as the tab indicator.
431 public TabSpec setIndicator(View view) {
432 mIndicatorStrategy = new ViewIndicatorStrategy(view);
437 * Specify the id of the view that should be used as the content
440 public TabSpec setContent(int viewId) {
441 mContentStrategy = new ViewIdContentStrategy(viewId);
446 * Specify a {@link android.widget.TabHost.TabContentFactory} to use to
447 * create the content of the tab.
449 public TabSpec setContent(TabContentFactory contentFactory) {
450 mContentStrategy = new FactoryContentStrategy(mTag, contentFactory);
455 * Specify an intent to use to launch an activity as the tab content.
457 public TabSpec setContent(Intent intent) {
458 mContentStrategy = new IntentContentStrategy(mTag, intent);
463 public String getTag() {
469 * Specifies what you do to create a tab indicator.
471 private static interface IndicatorStrategy {
474 * Return the view for the indicator.
476 View createIndicatorView();
480 * Specifies what you do to manage the tab content.
482 private static interface ContentStrategy {
485 * Return the content view. The view should may be cached locally.
487 View getContentView();
490 * Perhaps do something when the tab associated with this content has
491 * been closed (i.e make it invisible, or remove it).
497 * How to create a tab indicator that just has a label.
499 private class LabelIndicatorStrategy implements IndicatorStrategy {
501 private final CharSequence mLabel;
503 private LabelIndicatorStrategy(CharSequence label) {
507 public View createIndicatorView() {
508 final Context context = getContext();
509 LayoutInflater inflater =
510 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
511 View tabIndicator = inflater.inflate(R.layout.tab_indicator,
512 mTabWidget, // tab widget is the parent
513 false); // no inflate params
515 final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
518 if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
519 // Donut apps get old color scheme
520 tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);
521 tv.setTextColor(context.getResources().getColorStateList(R.color.tab_indicator_text_v4));
529 * How we create a tab indicator that has a label and an icon
531 private class LabelAndIconIndicatorStrategy implements IndicatorStrategy {
533 private final CharSequence mLabel;
534 private final Drawable mIcon;
536 private LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon) {
541 public View createIndicatorView() {
542 final Context context = getContext();
543 LayoutInflater inflater =
544 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
545 View tabIndicator = inflater.inflate(R.layout.tab_indicator,
546 mTabWidget, // tab widget is the parent
547 false); // no inflate params
549 final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
552 final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.icon);
553 iconView.setImageDrawable(mIcon);
555 if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
556 // Donut apps get old color scheme
557 tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);
558 tv.setTextColor(context.getResources().getColorStateList(R.color.tab_indicator_text_v4));
566 * How to create a tab indicator by specifying a view.
568 private class ViewIndicatorStrategy implements IndicatorStrategy {
570 private final View mView;
572 private ViewIndicatorStrategy(View view) {
576 public View createIndicatorView() {
582 * How to create the tab content via a view id.
584 private class ViewIdContentStrategy implements ContentStrategy {
586 private final View mView;
588 private ViewIdContentStrategy(int viewId) {
589 mView = mTabContent.findViewById(viewId);
591 mView.setVisibility(View.GONE);
593 throw new RuntimeException("Could not create tab content because " +
594 "could not find view with id " + viewId);
598 public View getContentView() {
599 mView.setVisibility(View.VISIBLE);
603 public void tabClosed() {
604 mView.setVisibility(View.GONE);
609 * How tab content is managed using {@link TabContentFactory}.
611 private class FactoryContentStrategy implements ContentStrategy {
612 private View mTabContent;
613 private final CharSequence mTag;
614 private TabContentFactory mFactory;
616 public FactoryContentStrategy(CharSequence tag, TabContentFactory factory) {
621 public View getContentView() {
622 if (mTabContent == null) {
623 mTabContent = mFactory.createTabContent(mTag.toString());
625 mTabContent.setVisibility(View.VISIBLE);
629 public void tabClosed() {
630 mTabContent.setVisibility(View.GONE);
635 * How tab content is managed via an {@link Intent}: the content view is the
636 * decorview of the launched activity.
638 private class IntentContentStrategy implements ContentStrategy {
640 private final String mTag;
641 private final Intent mIntent;
643 private View mLaunchedView;
645 private IntentContentStrategy(String tag, Intent intent) {
650 public View getContentView() {
651 if (mLocalActivityManager == null) {
652 throw new IllegalStateException("Did you forget to call 'public void setup(LocalActivityManager activityGroup)'?");
654 final Window w = mLocalActivityManager.startActivity(
656 final View wd = w != null ? w.getDecorView() : null;
657 if (mLaunchedView != wd && mLaunchedView != null) {
658 if (mLaunchedView.getParent() != null) {
659 mTabContent.removeView(mLaunchedView);
664 // XXX Set FOCUS_AFTER_DESCENDANTS on embedded activities for now so they can get
665 // focus if none of their children have it. They need focus to be able to
666 // display menu items.
668 // Replace this with something better when Bug 628886 is fixed...
670 if (mLaunchedView != null) {
671 mLaunchedView.setVisibility(View.VISIBLE);
672 mLaunchedView.setFocusableInTouchMode(true);
673 ((ViewGroup) mLaunchedView).setDescendantFocusability(
674 FOCUS_AFTER_DESCENDANTS);
676 return mLaunchedView;
679 public void tabClosed() {
680 if (mLaunchedView != null) {
681 mLaunchedView.setVisibility(View.GONE);