OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / frameworks / base / core / java / android / widget / TabHost.java
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 com.android.internal.R;
20
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;
34
35 import java.util.ArrayList;
36 import java.util.List;
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  * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-tabwidget.html">Tab Layout
45  * tutorial</a>.</p>
46  */
47 public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchModeChangeListener {
48
49     private TabWidget mTabWidget;
50     private FrameLayout mTabContent;
51     private List<TabSpec> mTabSpecs = new ArrayList<TabSpec>(2);
52     /**
53      * This field should be made private, so it is hidden from the SDK.
54      * {@hide}
55      */
56     protected int mCurrentTab = -1;
57     private View mCurrentView = null;
58     /**
59      * This field should be made private, so it is hidden from the SDK.
60      * {@hide}
61      */
62     protected LocalActivityManager mLocalActivityManager = null;
63     private OnTabChangeListener mOnTabChangeListener;
64     private OnKeyListener mTabKeyListener;
65
66     public TabHost(Context context) {
67         super(context);
68         initTabHost();
69     }
70
71     public TabHost(Context context, AttributeSet attrs) {
72         super(context, attrs);
73         initTabHost();
74     }
75
76     private void initTabHost() {
77         setFocusableInTouchMode(true);
78         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
79
80         mCurrentTab = -1;
81         mCurrentView = null;
82     }
83
84     /**
85      * Get a new {@link TabSpec} associated with this tab host.
86      * @param tag required tag of tab.
87      */
88     public TabSpec newTabSpec(String tag) {
89         return new TabSpec(tag);
90     }
91
92
93
94     /**
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}.
98       * Example:</p>
99 <pre>mTabHost = (TabHost)findViewById(R.id.tabhost);
100 mTabHost.setup();
101 mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
102       */
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'");
108         }
109
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) {
114                 switch (keyCode) {
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:
121                         return false;
122
123                 }
124                 mTabContent.requestFocus(View.FOCUS_FORWARD);
125                 return mTabContent.dispatchKeyEvent(event);
126             }
127
128         };
129
130         mTabWidget.setTabSelectionListener(new TabWidget.OnTabSelectionChanged() {
131             public void onTabSelectionChanged(int tabIndex, boolean clicked) {
132                 setCurrentTab(tabIndex);
133                 if (clicked) {
134                     mTabContent.requestFocus(View.FOCUS_FORWARD);
135                 }
136             }
137         });
138
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'");
144         }
145     }
146
147     /**
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.
150      *
151      * This is done for you if you extend {@link android.app.TabActivity}.
152      * @param activityGroup Used to launch activities for tab content.
153      */
154     public void setup(LocalActivityManager activityGroup) {
155         setup();
156         mLocalActivityManager = activityGroup;
157     }
158
159
160     @Override
161     protected void onAttachedToWindow() {
162         super.onAttachedToWindow();
163         final ViewTreeObserver treeObserver = getViewTreeObserver();
164         if (treeObserver != null) {
165             treeObserver.addOnTouchModeChangeListener(this);
166         }
167     }
168
169     @Override
170     protected void onDetachedFromWindow() {
171         super.onDetachedFromWindow();
172         final ViewTreeObserver treeObserver = getViewTreeObserver();
173         if (treeObserver != null) {
174             treeObserver.removeOnTouchModeChangeListener(this);
175         }
176     }
177
178     /**
179      * {@inheritDoc}
180      */
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();
187             }
188         }
189     }
190
191     /**
192      * Add a tab.
193      * @param tabSpec Specifies how to create the indicator and content.
194      */
195     public void addTab(TabSpec tabSpec) {
196
197         if (tabSpec.mIndicatorStrategy == null) {
198             throw new IllegalArgumentException("you must specify a way to create the tab indicator.");
199         }
200
201         if (tabSpec.mContentStrategy == null) {
202             throw new IllegalArgumentException("you must specify a way to create the tab content");
203         }
204         View tabIndicator = tabSpec.mIndicatorStrategy.createIndicatorView();
205         tabIndicator.setOnKeyListener(mTabKeyListener);
206
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);
211         }
212         mTabWidget.addView(tabIndicator);
213         mTabSpecs.add(tabSpec);
214
215         if (mCurrentTab == -1) {
216             setCurrentTab(0);
217         }
218     }
219
220
221     /**
222      * Removes all tabs from the tab widget associated with this tab host.
223      */
224     public void clearAllTabs() {
225         mTabWidget.removeAllViews();
226         initTabHost();
227         mTabContent.removeAllViews();
228         mTabSpecs.clear();
229         requestLayout();
230         invalidate();
231     }
232
233     public TabWidget getTabWidget() {
234         return mTabWidget;
235     }
236
237     public int getCurrentTab() {
238         return mCurrentTab;
239     }
240
241     public String getCurrentTabTag() {
242         if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
243             return mTabSpecs.get(mCurrentTab).getTag();
244         }
245         return null;
246     }
247
248     public View getCurrentTabView() {
249         if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
250             return mTabWidget.getChildTabViewAt(mCurrentTab);
251         }
252         return null;
253     }
254
255     public View getCurrentView() {
256         return mCurrentView;
257     }
258
259     public void setCurrentTabByTag(String tag) {
260         int i;
261         for (i = 0; i < mTabSpecs.size(); i++) {
262             if (mTabSpecs.get(i).getTag().equals(tag)) {
263                 setCurrentTab(i);
264                 break;
265             }
266         }
267     }
268
269     /**
270      * Get the FrameLayout which holds tab content
271      */
272     public FrameLayout getTabContentView() {
273         return mTabContent;
274     }
275
276     @Override
277     public boolean dispatchKeyEvent(KeyEvent event) {
278         final boolean handled = super.dispatchKeyEvent(event);
279
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
282         if (!handled
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);
291             return true;
292         }
293         return handled;
294     }
295
296
297     @Override
298     public void dispatchWindowFocusChanged(boolean hasFocus) {
299         if (mCurrentView != null){
300             mCurrentView.dispatchWindowFocusChanged(hasFocus);
301         }
302     }
303
304     public void setCurrentTab(int index) {
305         if (index < 0 || index >= mTabSpecs.size()) {
306             return;
307         }
308
309         if (index == mCurrentTab) {
310             return;
311         }
312
313         // notify old tab content
314         if (mCurrentTab != -1) {
315             mTabSpecs.get(mCurrentTab).mContentStrategy.tabClosed();
316         }
317
318         mCurrentTab = index;
319         final TabHost.TabSpec spec = mTabSpecs.get(index);
320
321         // Call the tab widget's focusCurrentTab(), instead of just
322         // selecting the tab.
323         mTabWidget.focusCurrentTab(mCurrentTab);
324
325         // tab content
326         mCurrentView = spec.mContentStrategy.getContentView();
327
328         if (mCurrentView.getParent() == null) {
329             mTabContent
330                     .addView(
331                             mCurrentView,
332                             new ViewGroup.LayoutParams(
333                                     ViewGroup.LayoutParams.MATCH_PARENT,
334                                     ViewGroup.LayoutParams.MATCH_PARENT));
335         }
336
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();
341         }
342
343         //mTabContent.requestFocus(View.FOCUS_FORWARD);
344         invokeOnTabChangeListener();
345     }
346
347     /**
348      * Register a callback to be invoked when the selected state of any of the items
349      * in this list changes
350      * @param l
351      * The callback that will run
352      */
353     public void setOnTabChangedListener(OnTabChangeListener l) {
354         mOnTabChangeListener = l;
355     }
356
357     private void invokeOnTabChangeListener() {
358         if (mOnTabChangeListener != null) {
359             mOnTabChangeListener.onTabChanged(getCurrentTabTag());
360         }
361     }
362
363     /**
364      * Interface definition for a callback to be invoked when tab changed
365      */
366     public interface OnTabChangeListener {
367         void onTabChanged(String tabId);
368     }
369
370
371     /**
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.
375      */
376     public interface TabContentFactory {
377         /**
378          * Callback to make the tab contents
379          *
380          * @param tag
381          *            Which tab was selected.
382          * @return The view to display the contents of the selected tab.
383          */
384         View createTabContent(String tag);
385     }
386
387
388     /**
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.
391      *
392      * For the tab indicator, your choices are:
393      * 1) set a label
394      * 2) set a label and an icon
395      *
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}.
400      */
401     public class TabSpec {
402
403         private String mTag;
404
405         private IndicatorStrategy mIndicatorStrategy;
406         private ContentStrategy mContentStrategy;
407
408         private TabSpec(String tag) {
409             mTag = tag;
410         }
411
412         /**
413          * Specify a label as the tab indicator.
414          */
415         public TabSpec setIndicator(CharSequence label) {
416             mIndicatorStrategy = new LabelIndicatorStrategy(label);
417             return this;
418         }
419
420         /**
421          * Specify a label and icon as the tab indicator.
422          */
423         public TabSpec setIndicator(CharSequence label, Drawable icon) {
424             mIndicatorStrategy = new LabelAndIconIndicatorStrategy(label, icon);
425             return this;
426         }
427
428         /**
429          * Specify a view as the tab indicator.
430          */
431         public TabSpec setIndicator(View view) {
432             mIndicatorStrategy = new ViewIndicatorStrategy(view);
433             return this;
434         }
435
436         /**
437          * Specify the id of the view that should be used as the content
438          * of the tab.
439          */
440         public TabSpec setContent(int viewId) {
441             mContentStrategy = new ViewIdContentStrategy(viewId);
442             return this;
443         }
444
445         /**
446          * Specify a {@link android.widget.TabHost.TabContentFactory} to use to
447          * create the content of the tab.
448          */
449         public TabSpec setContent(TabContentFactory contentFactory) {
450             mContentStrategy = new FactoryContentStrategy(mTag, contentFactory);
451             return this;
452         }
453
454         /**
455          * Specify an intent to use to launch an activity as the tab content.
456          */
457         public TabSpec setContent(Intent intent) {
458             mContentStrategy = new IntentContentStrategy(mTag, intent);
459             return this;
460         }
461
462
463         public String getTag() {
464             return mTag;
465         }
466     }
467
468     /**
469      * Specifies what you do to create a tab indicator.
470      */
471     private static interface IndicatorStrategy {
472
473         /**
474          * Return the view for the indicator.
475          */
476         View createIndicatorView();
477     }
478
479     /**
480      * Specifies what you do to manage the tab content.
481      */
482     private static interface ContentStrategy {
483
484         /**
485          * Return the content view.  The view should may be cached locally.
486          */
487         View getContentView();
488
489         /**
490          * Perhaps do something when the tab associated with this content has
491          * been closed (i.e make it invisible, or remove it).
492          */
493         void tabClosed();
494     }
495
496     /**
497      * How to create a tab indicator that just has a label.
498      */
499     private class LabelIndicatorStrategy implements IndicatorStrategy {
500
501         private final CharSequence mLabel;
502
503         private LabelIndicatorStrategy(CharSequence label) {
504             mLabel = label;
505         }
506
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
514
515             final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
516             tv.setText(mLabel);
517
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));
522             }
523             
524             return tabIndicator;
525         }
526     }
527
528     /**
529      * How we create a tab indicator that has a label and an icon
530      */
531     private class LabelAndIconIndicatorStrategy implements IndicatorStrategy {
532
533         private final CharSequence mLabel;
534         private final Drawable mIcon;
535
536         private LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon) {
537             mLabel = label;
538             mIcon = icon;
539         }
540
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
548
549             final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
550             tv.setText(mLabel);
551
552             final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.icon);
553             iconView.setImageDrawable(mIcon);
554
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));
559             }
560             
561             return tabIndicator;
562         }
563     }
564
565     /**
566      * How to create a tab indicator by specifying a view.
567      */
568     private class ViewIndicatorStrategy implements IndicatorStrategy {
569
570         private final View mView;
571
572         private ViewIndicatorStrategy(View view) {
573             mView = view;
574         }
575
576         public View createIndicatorView() {
577             return mView;
578         }
579     }
580
581     /**
582      * How to create the tab content via a view id.
583      */
584     private class ViewIdContentStrategy implements ContentStrategy {
585
586         private final View mView;
587
588         private ViewIdContentStrategy(int viewId) {
589             mView = mTabContent.findViewById(viewId);
590             if (mView != null) {
591                 mView.setVisibility(View.GONE);
592             } else {
593                 throw new RuntimeException("Could not create tab content because " +
594                         "could not find view with id " + viewId);
595             }
596         }
597
598         public View getContentView() {
599             mView.setVisibility(View.VISIBLE);
600             return mView;
601         }
602
603         public void tabClosed() {
604             mView.setVisibility(View.GONE);
605         }
606     }
607
608     /**
609      * How tab content is managed using {@link TabContentFactory}.
610      */
611     private class FactoryContentStrategy implements ContentStrategy {
612         private View mTabContent;
613         private final CharSequence mTag;
614         private TabContentFactory mFactory;
615
616         public FactoryContentStrategy(CharSequence tag, TabContentFactory factory) {
617             mTag = tag;
618             mFactory = factory;
619         }
620
621         public View getContentView() {
622             if (mTabContent == null) {
623                 mTabContent = mFactory.createTabContent(mTag.toString());
624             }
625             mTabContent.setVisibility(View.VISIBLE);
626             return mTabContent;
627         }
628
629         public void tabClosed() {
630             mTabContent.setVisibility(View.GONE);
631         }
632     }
633
634     /**
635      * How tab content is managed via an {@link Intent}: the content view is the
636      * decorview of the launched activity.
637      */
638     private class IntentContentStrategy implements ContentStrategy {
639
640         private final String mTag;
641         private final Intent mIntent;
642
643         private View mLaunchedView;
644
645         private IntentContentStrategy(String tag, Intent intent) {
646             mTag = tag;
647             mIntent = intent;
648         }
649
650         public View getContentView() {
651             if (mLocalActivityManager == null) {
652                 throw new IllegalStateException("Did you forget to call 'public void setup(LocalActivityManager activityGroup)'?");
653             }
654             final Window w = mLocalActivityManager.startActivity(
655                     mTag, mIntent);
656             final View wd = w != null ? w.getDecorView() : null;
657             if (mLaunchedView != wd && mLaunchedView != null) {
658                 if (mLaunchedView.getParent() != null) {
659                     mTabContent.removeView(mLaunchedView);
660                 }
661             }
662             mLaunchedView = wd;
663
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.
667             //
668             // Replace this with something better when Bug 628886 is fixed...
669             //
670             if (mLaunchedView != null) {
671                 mLaunchedView.setVisibility(View.VISIBLE);
672                 mLaunchedView.setFocusableInTouchMode(true);
673                 ((ViewGroup) mLaunchedView).setDescendantFocusability(
674                         FOCUS_AFTER_DESCENDANTS);
675             }
676             return mLaunchedView;
677         }
678
679         public void tabClosed() {
680             if (mLaunchedView != null) {
681                 mLaunchedView.setVisibility(View.GONE);
682             }
683         }
684     }
685
686 }