• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
17 package android.widget;
18 
19 import android.app.LocalActivityManager;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.graphics.drawable.Drawable;
23 import android.os.Build;
24 import android.util.AttributeSet;
25 import android.view.KeyEvent;
26 import android.view.LayoutInflater;
27 import android.view.SoundEffectConstants;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.ViewTreeObserver;
31 import android.view.Window;
32 
33 import java.util.ArrayList;
34 import java.util.List;
35 
36 import com.android.internal.R;
37 
38 /**
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.
43  */
44 public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchModeChangeListener {
45 
46     private TabWidget mTabWidget;
47     private FrameLayout mTabContent;
48     private List<TabSpec> mTabSpecs = new ArrayList<TabSpec>(2);
49     /**
50      * This field should be made private, so it is hidden from the SDK.
51      * {@hide}
52      */
53     protected int mCurrentTab = -1;
54     private View mCurrentView = null;
55     /**
56      * This field should be made private, so it is hidden from the SDK.
57      * {@hide}
58      */
59     protected LocalActivityManager mLocalActivityManager = null;
60     private OnTabChangeListener mOnTabChangeListener;
61     private OnKeyListener mTabKeyListener;
62 
TabHost(Context context)63     public TabHost(Context context) {
64         super(context);
65         initTabHost();
66     }
67 
TabHost(Context context, AttributeSet attrs)68     public TabHost(Context context, AttributeSet attrs) {
69         super(context, attrs);
70         initTabHost();
71     }
72 
initTabHost()73     private void initTabHost() {
74         setFocusableInTouchMode(true);
75         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
76 
77         mCurrentTab = -1;
78         mCurrentView = null;
79     }
80 
81     /**
82      * Get a new {@link TabSpec} associated with this tab host.
83      * @param tag required tag of tab.
84      */
newTabSpec(String tag)85     public TabSpec newTabSpec(String tag) {
86         return new TabSpec(tag);
87     }
88 
89 
90 
91     /**
92       * <p>Call setup() before adding tabs if loading TabHost using findViewById().
93       * <i><b>However</i></b>: You do not need to call setup() after getTabHost()
94       * in {@link android.app.TabActivity TabActivity}.
95       * Example:</p>
96 <pre>mTabHost = (TabHost)findViewById(R.id.tabhost);
97 mTabHost.setup();
98 mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
99       */
setup()100     public void setup() {
101         mTabWidget = (TabWidget) findViewById(com.android.internal.R.id.tabs);
102         if (mTabWidget == null) {
103             throw new RuntimeException(
104                     "Your TabHost must have a TabWidget whose id attribute is 'android.R.id.tabs'");
105         }
106 
107         // KeyListener to attach to all tabs. Detects non-navigation keys
108         // and relays them to the tab content.
109         mTabKeyListener = new OnKeyListener() {
110             public boolean onKey(View v, int keyCode, KeyEvent event) {
111                 switch (keyCode) {
112                     case KeyEvent.KEYCODE_DPAD_CENTER:
113                     case KeyEvent.KEYCODE_DPAD_LEFT:
114                     case KeyEvent.KEYCODE_DPAD_RIGHT:
115                     case KeyEvent.KEYCODE_DPAD_UP:
116                     case KeyEvent.KEYCODE_DPAD_DOWN:
117                     case KeyEvent.KEYCODE_ENTER:
118                         return false;
119 
120                 }
121                 mTabContent.requestFocus(View.FOCUS_FORWARD);
122                 return mTabContent.dispatchKeyEvent(event);
123             }
124 
125         };
126 
127         mTabWidget.setTabSelectionListener(new TabWidget.OnTabSelectionChanged() {
128             public void onTabSelectionChanged(int tabIndex, boolean clicked) {
129                 setCurrentTab(tabIndex);
130                 if (clicked) {
131                     mTabContent.requestFocus(View.FOCUS_FORWARD);
132                 }
133             }
134         });
135 
136         mTabContent = (FrameLayout) findViewById(com.android.internal.R.id.tabcontent);
137         if (mTabContent == null) {
138             throw new RuntimeException(
139                     "Your TabHost must have a FrameLayout whose id attribute is "
140                             + "'android.R.id.tabcontent'");
141         }
142     }
143 
144     /**
145      * If you are using {@link TabSpec#setContent(android.content.Intent)}, this
146      * must be called since the activityGroup is needed to launch the local activity.
147      *
148      * This is done for you if you extend {@link android.app.TabActivity}.
149      * @param activityGroup Used to launch activities for tab content.
150      */
setup(LocalActivityManager activityGroup)151     public void setup(LocalActivityManager activityGroup) {
152         setup();
153         mLocalActivityManager = activityGroup;
154     }
155 
156 
157     @Override
onAttachedToWindow()158     protected void onAttachedToWindow() {
159         super.onAttachedToWindow();
160         final ViewTreeObserver treeObserver = getViewTreeObserver();
161         if (treeObserver != null) {
162             treeObserver.addOnTouchModeChangeListener(this);
163         }
164     }
165 
166     @Override
onDetachedFromWindow()167     protected void onDetachedFromWindow() {
168         super.onDetachedFromWindow();
169         final ViewTreeObserver treeObserver = getViewTreeObserver();
170         if (treeObserver != null) {
171             treeObserver.removeOnTouchModeChangeListener(this);
172         }
173     }
174 
175     /**
176      * {@inheritDoc}
177      */
onTouchModeChanged(boolean isInTouchMode)178     public void onTouchModeChanged(boolean isInTouchMode) {
179         if (!isInTouchMode) {
180             // leaving touch mode.. if nothing has focus, let's give it to
181             // the indicator of the current tab
182             if (mCurrentView != null && (!mCurrentView.hasFocus() || mCurrentView.isFocused())) {
183                 mTabWidget.getChildTabViewAt(mCurrentTab).requestFocus();
184             }
185         }
186     }
187 
188     /**
189      * Add a tab.
190      * @param tabSpec Specifies how to create the indicator and content.
191      */
addTab(TabSpec tabSpec)192     public void addTab(TabSpec tabSpec) {
193 
194         if (tabSpec.mIndicatorStrategy == null) {
195             throw new IllegalArgumentException("you must specify a way to create the tab indicator.");
196         }
197 
198         if (tabSpec.mContentStrategy == null) {
199             throw new IllegalArgumentException("you must specify a way to create the tab content");
200         }
201         View tabIndicator = tabSpec.mIndicatorStrategy.createIndicatorView();
202         tabIndicator.setOnKeyListener(mTabKeyListener);
203 
204         // If this is a custom view, then do not draw the bottom strips for
205         // the tab indicators.
206         if (tabSpec.mIndicatorStrategy instanceof ViewIndicatorStrategy) {
207             mTabWidget.setDrawBottomStrips(false);
208         }
209         mTabWidget.addView(tabIndicator);
210         mTabSpecs.add(tabSpec);
211 
212         if (mCurrentTab == -1) {
213             setCurrentTab(0);
214         }
215     }
216 
217 
218     /**
219      * Removes all tabs from the tab widget associated with this tab host.
220      */
clearAllTabs()221     public void clearAllTabs() {
222         mTabWidget.removeAllViews();
223         initTabHost();
224         mTabContent.removeAllViews();
225         mTabSpecs.clear();
226         requestLayout();
227         invalidate();
228     }
229 
getTabWidget()230     public TabWidget getTabWidget() {
231         return mTabWidget;
232     }
233 
getCurrentTab()234     public int getCurrentTab() {
235         return mCurrentTab;
236     }
237 
getCurrentTabTag()238     public String getCurrentTabTag() {
239         if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
240             return mTabSpecs.get(mCurrentTab).getTag();
241         }
242         return null;
243     }
244 
getCurrentTabView()245     public View getCurrentTabView() {
246         if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
247             return mTabWidget.getChildTabViewAt(mCurrentTab);
248         }
249         return null;
250     }
251 
getCurrentView()252     public View getCurrentView() {
253         return mCurrentView;
254     }
255 
setCurrentTabByTag(String tag)256     public void setCurrentTabByTag(String tag) {
257         int i;
258         for (i = 0; i < mTabSpecs.size(); i++) {
259             if (mTabSpecs.get(i).getTag().equals(tag)) {
260                 setCurrentTab(i);
261                 break;
262             }
263         }
264     }
265 
266     /**
267      * Get the FrameLayout which holds tab content
268      */
getTabContentView()269     public FrameLayout getTabContentView() {
270         return mTabContent;
271     }
272 
273     @Override
dispatchKeyEvent(KeyEvent event)274     public boolean dispatchKeyEvent(KeyEvent event) {
275         final boolean handled = super.dispatchKeyEvent(event);
276 
277         // unhandled key ups change focus to tab indicator for embedded activities
278         // when there is nothing that will take focus from default focus searching
279         if (!handled
280                 && (event.getAction() == KeyEvent.ACTION_DOWN)
281                 && (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP)
282                 && (mCurrentView.isRootNamespace())
283                 && (mCurrentView.hasFocus())
284                 && (mCurrentView.findFocus().focusSearch(View.FOCUS_UP) == null)) {
285             mTabWidget.getChildTabViewAt(mCurrentTab).requestFocus();
286             playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
287             return true;
288         }
289         return handled;
290     }
291 
292 
293     @Override
dispatchWindowFocusChanged(boolean hasFocus)294     public void dispatchWindowFocusChanged(boolean hasFocus) {
295         mCurrentView.dispatchWindowFocusChanged(hasFocus);
296     }
297 
setCurrentTab(int index)298     public void setCurrentTab(int index) {
299         if (index < 0 || index >= mTabSpecs.size()) {
300             return;
301         }
302 
303         if (index == mCurrentTab) {
304             return;
305         }
306 
307         // notify old tab content
308         if (mCurrentTab != -1) {
309             mTabSpecs.get(mCurrentTab).mContentStrategy.tabClosed();
310         }
311 
312         mCurrentTab = index;
313         final TabHost.TabSpec spec = mTabSpecs.get(index);
314 
315         // Call the tab widget's focusCurrentTab(), instead of just
316         // selecting the tab.
317         mTabWidget.focusCurrentTab(mCurrentTab);
318 
319         // tab content
320         mCurrentView = spec.mContentStrategy.getContentView();
321 
322         if (mCurrentView.getParent() == null) {
323             mTabContent
324                     .addView(
325                             mCurrentView,
326                             new ViewGroup.LayoutParams(
327                                     ViewGroup.LayoutParams.FILL_PARENT,
328                                     ViewGroup.LayoutParams.FILL_PARENT));
329         }
330 
331         if (!mTabWidget.hasFocus()) {
332             // if the tab widget didn't take focus (likely because we're in touch mode)
333             // give the current tab content view a shot
334             mCurrentView.requestFocus();
335         }
336 
337         //mTabContent.requestFocus(View.FOCUS_FORWARD);
338         invokeOnTabChangeListener();
339     }
340 
341     /**
342      * Register a callback to be invoked when the selected state of any of the items
343      * in this list changes
344      * @param l
345      * The callback that will run
346      */
setOnTabChangedListener(OnTabChangeListener l)347     public void setOnTabChangedListener(OnTabChangeListener l) {
348         mOnTabChangeListener = l;
349     }
350 
invokeOnTabChangeListener()351     private void invokeOnTabChangeListener() {
352         if (mOnTabChangeListener != null) {
353             mOnTabChangeListener.onTabChanged(getCurrentTabTag());
354         }
355     }
356 
357     /**
358      * Interface definition for a callback to be invoked when tab changed
359      */
360     public interface OnTabChangeListener {
onTabChanged(String tabId)361         void onTabChanged(String tabId);
362     }
363 
364 
365     /**
366      * Makes the content of a tab when it is selected. Use this if your tab
367      * content needs to be created on demand, i.e. you are not showing an
368      * existing view or starting an activity.
369      */
370     public interface TabContentFactory {
371         /**
372          * Callback to make the tab contents
373          *
374          * @param tag
375          *            Which tab was selected.
376          * @return The view to display the contents of the selected tab.
377          */
createTabContent(String tag)378         View createTabContent(String tag);
379     }
380 
381 
382     /**
383      * A tab has a tab indicator, content, and a tag that is used to keep
384      * track of it.  This builder helps choose among these options.
385      *
386      * For the tab indicator, your choices are:
387      * 1) set a label
388      * 2) set a label and an icon
389      *
390      * For the tab content, your choices are:
391      * 1) the id of a {@link View}
392      * 2) a {@link TabContentFactory} that creates the {@link View} content.
393      * 3) an {@link Intent} that launches an {@link android.app.Activity}.
394      */
395     public class TabSpec {
396 
397         private String mTag;
398 
399         private IndicatorStrategy mIndicatorStrategy;
400         private ContentStrategy mContentStrategy;
401 
TabSpec(String tag)402         private TabSpec(String tag) {
403             mTag = tag;
404         }
405 
406         /**
407          * Specify a label as the tab indicator.
408          */
setIndicator(CharSequence label)409         public TabSpec setIndicator(CharSequence label) {
410             mIndicatorStrategy = new LabelIndicatorStrategy(label);
411             return this;
412         }
413 
414         /**
415          * Specify a label and icon as the tab indicator.
416          */
setIndicator(CharSequence label, Drawable icon)417         public TabSpec setIndicator(CharSequence label, Drawable icon) {
418             mIndicatorStrategy = new LabelAndIconIndicatorStrategy(label, icon);
419             return this;
420         }
421 
422         /**
423          * Specify a view as the tab indicator.
424          */
setIndicator(View view)425         public TabSpec setIndicator(View view) {
426             mIndicatorStrategy = new ViewIndicatorStrategy(view);
427             return this;
428         }
429 
430         /**
431          * Specify the id of the view that should be used as the content
432          * of the tab.
433          */
setContent(int viewId)434         public TabSpec setContent(int viewId) {
435             mContentStrategy = new ViewIdContentStrategy(viewId);
436             return this;
437         }
438 
439         /**
440          * Specify a {@link android.widget.TabHost.TabContentFactory} to use to
441          * create the content of the tab.
442          */
setContent(TabContentFactory contentFactory)443         public TabSpec setContent(TabContentFactory contentFactory) {
444             mContentStrategy = new FactoryContentStrategy(mTag, contentFactory);
445             return this;
446         }
447 
448         /**
449          * Specify an intent to use to launch an activity as the tab content.
450          */
setContent(Intent intent)451         public TabSpec setContent(Intent intent) {
452             mContentStrategy = new IntentContentStrategy(mTag, intent);
453             return this;
454         }
455 
456 
getTag()457         public String getTag() {
458             return mTag;
459         }
460     }
461 
462     /**
463      * Specifies what you do to create a tab indicator.
464      */
465     private static interface IndicatorStrategy {
466 
467         /**
468          * Return the view for the indicator.
469          */
createIndicatorView()470         View createIndicatorView();
471     }
472 
473     /**
474      * Specifies what you do to manage the tab content.
475      */
476     private static interface ContentStrategy {
477 
478         /**
479          * Return the content view.  The view should may be cached locally.
480          */
getContentView()481         View getContentView();
482 
483         /**
484          * Perhaps do something when the tab associated with this content has
485          * been closed (i.e make it invisible, or remove it).
486          */
tabClosed()487         void tabClosed();
488     }
489 
490     /**
491      * How to create a tab indicator that just has a label.
492      */
493     private class LabelIndicatorStrategy implements IndicatorStrategy {
494 
495         private final CharSequence mLabel;
496 
LabelIndicatorStrategy(CharSequence label)497         private LabelIndicatorStrategy(CharSequence label) {
498             mLabel = label;
499         }
500 
createIndicatorView()501         public View createIndicatorView() {
502             final Context context = getContext();
503             LayoutInflater inflater =
504                     (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
505             View tabIndicator = inflater.inflate(R.layout.tab_indicator,
506                     mTabWidget, // tab widget is the parent
507                     false); // no inflate params
508 
509             final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
510             tv.setText(mLabel);
511 
512             if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
513                 // Donut apps get old color scheme
514                 tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);
515                 tv.setTextColor(context.getResources().getColorStateList(R.color.tab_indicator_text_v4));
516             }
517 
518             return tabIndicator;
519         }
520     }
521 
522     /**
523      * How we create a tab indicator that has a label and an icon
524      */
525     private class LabelAndIconIndicatorStrategy implements IndicatorStrategy {
526 
527         private final CharSequence mLabel;
528         private final Drawable mIcon;
529 
LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon)530         private LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon) {
531             mLabel = label;
532             mIcon = icon;
533         }
534 
createIndicatorView()535         public View createIndicatorView() {
536             final Context context = getContext();
537             LayoutInflater inflater =
538                     (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
539             View tabIndicator = inflater.inflate(R.layout.tab_indicator,
540                     mTabWidget, // tab widget is the parent
541                     false); // no inflate params
542 
543             final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
544             tv.setText(mLabel);
545 
546             final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.icon);
547             iconView.setImageDrawable(mIcon);
548 
549             if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
550                 // Donut apps get old color scheme
551                 tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);
552                 tv.setTextColor(context.getResources().getColorStateList(R.color.tab_indicator_text_v4));
553             }
554 
555             return tabIndicator;
556         }
557     }
558 
559     /**
560      * How to create a tab indicator by specifying a view.
561      */
562     private class ViewIndicatorStrategy implements IndicatorStrategy {
563 
564         private final View mView;
565 
ViewIndicatorStrategy(View view)566         private ViewIndicatorStrategy(View view) {
567             mView = view;
568         }
569 
createIndicatorView()570         public View createIndicatorView() {
571             return mView;
572         }
573     }
574 
575     /**
576      * How to create the tab content via a view id.
577      */
578     private class ViewIdContentStrategy implements ContentStrategy {
579 
580         private final View mView;
581 
ViewIdContentStrategy(int viewId)582         private ViewIdContentStrategy(int viewId) {
583             mView = mTabContent.findViewById(viewId);
584             if (mView != null) {
585                 mView.setVisibility(View.GONE);
586             } else {
587                 throw new RuntimeException("Could not create tab content because " +
588                         "could not find view with id " + viewId);
589             }
590         }
591 
getContentView()592         public View getContentView() {
593             mView.setVisibility(View.VISIBLE);
594             return mView;
595         }
596 
tabClosed()597         public void tabClosed() {
598             mView.setVisibility(View.GONE);
599         }
600     }
601 
602     /**
603      * How tab content is managed using {@link TabContentFactory}.
604      */
605     private class FactoryContentStrategy implements ContentStrategy {
606         private View mTabContent;
607         private final CharSequence mTag;
608         private TabContentFactory mFactory;
609 
FactoryContentStrategy(CharSequence tag, TabContentFactory factory)610         public FactoryContentStrategy(CharSequence tag, TabContentFactory factory) {
611             mTag = tag;
612             mFactory = factory;
613         }
614 
getContentView()615         public View getContentView() {
616             if (mTabContent == null) {
617                 mTabContent = mFactory.createTabContent(mTag.toString());
618             }
619             mTabContent.setVisibility(View.VISIBLE);
620             return mTabContent;
621         }
622 
tabClosed()623         public void tabClosed() {
624             mTabContent.setVisibility(View.INVISIBLE);
625         }
626     }
627 
628     /**
629      * How tab content is managed via an {@link Intent}: the content view is the
630      * decorview of the launched activity.
631      */
632     private class IntentContentStrategy implements ContentStrategy {
633 
634         private final String mTag;
635         private final Intent mIntent;
636 
637         private View mLaunchedView;
638 
IntentContentStrategy(String tag, Intent intent)639         private IntentContentStrategy(String tag, Intent intent) {
640             mTag = tag;
641             mIntent = intent;
642         }
643 
getContentView()644         public View getContentView() {
645             if (mLocalActivityManager == null) {
646                 throw new IllegalStateException("Did you forget to call 'public void setup(LocalActivityManager activityGroup)'?");
647             }
648             final Window w = mLocalActivityManager.startActivity(
649                     mTag, mIntent);
650             final View wd = w != null ? w.getDecorView() : null;
651             if (mLaunchedView != wd && mLaunchedView != null) {
652                 if (mLaunchedView.getParent() != null) {
653                     mTabContent.removeView(mLaunchedView);
654                 }
655             }
656             mLaunchedView = wd;
657 
658             // XXX Set FOCUS_AFTER_DESCENDANTS on embedded activities for now so they can get
659             // focus if none of their children have it. They need focus to be able to
660             // display menu items.
661             //
662             // Replace this with something better when Bug 628886 is fixed...
663             //
664             if (mLaunchedView != null) {
665                 mLaunchedView.setVisibility(View.VISIBLE);
666                 mLaunchedView.setFocusableInTouchMode(true);
667                 ((ViewGroup) mLaunchedView).setDescendantFocusability(
668                         FOCUS_AFTER_DESCENDANTS);
669             }
670             return mLaunchedView;
671         }
672 
tabClosed()673         public void tabClosed() {
674             if (mLaunchedView != null) {
675                 mLaunchedView.setVisibility(View.GONE);
676             }
677         }
678     }
679 
680 }
681