blob: 89df51a8ac9917d98a1cc479be43cad66101dda7 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.widget;
18
Gilles Debunnece2baf02010-02-10 15:19:14 -080019import com.android.internal.R;
20
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.app.LocalActivityManager;
22import android.content.Context;
23import android.content.Intent;
Gilles Debunne44c14732010-10-19 11:56:59 -070024import android.content.res.TypedArray;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.graphics.drawable.Drawable;
Mike Cleron76097642009-09-25 17:53:56 -070026import android.os.Build;
Jeff Sharkey11f4a482011-08-08 21:05:40 -070027import android.text.TextUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.util.AttributeSet;
29import android.view.KeyEvent;
30import android.view.LayoutInflater;
31import android.view.SoundEffectConstants;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.ViewTreeObserver;
35import android.view.Window;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080036import android.view.accessibility.AccessibilityEvent;
37import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038
39import java.util.ArrayList;
40import java.util.List;
41
42/**
43 * Container for a tabbed window view. This object holds two children: a set of tab labels that the
44 * user clicks to select a specific tab, and a FrameLayout object that displays the contents of that
45 * page. The individual elements are typically controlled using this container object, rather than
46 * setting values on the child elements themselves.
Scott Main41ec6532010-08-19 16:57:07 -070047 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048 */
49public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchModeChangeListener {
50
Roger Olsson54ed87c2011-01-24 11:19:11 +010051 private static final int TABWIDGET_LOCATION_LEFT = 0;
52 private static final int TABWIDGET_LOCATION_TOP = 1;
53 private static final int TABWIDGET_LOCATION_RIGHT = 2;
54 private static final int TABWIDGET_LOCATION_BOTTOM = 3;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055 private TabWidget mTabWidget;
56 private FrameLayout mTabContent;
57 private List<TabSpec> mTabSpecs = new ArrayList<TabSpec>(2);
58 /**
59 * This field should be made private, so it is hidden from the SDK.
60 * {@hide}
61 */
62 protected int mCurrentTab = -1;
63 private View mCurrentView = null;
64 /**
65 * This field should be made private, so it is hidden from the SDK.
66 * {@hide}
67 */
68 protected LocalActivityManager mLocalActivityManager = null;
69 private OnTabChangeListener mOnTabChangeListener;
70 private OnKeyListener mTabKeyListener;
71
Gilles Debunne44c14732010-10-19 11:56:59 -070072 private int mTabLayoutId;
73
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074 public TabHost(Context context) {
75 super(context);
76 initTabHost();
77 }
78
79 public TabHost(Context context, AttributeSet attrs) {
Alan Viverette617feb92013-09-09 18:09:13 -070080 this(context, attrs, com.android.internal.R.attr.tabWidgetStyle);
81 }
82
83 public TabHost(Context context, AttributeSet attrs, int defStyleAttr) {
84 this(context, attrs, defStyleAttr, 0);
85 }
86
87 public TabHost(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088 super(context, attrs);
Gilles Debunne44c14732010-10-19 11:56:59 -070089
Alan Viverette617feb92013-09-09 18:09:13 -070090 final TypedArray a = context.obtainStyledAttributes(
91 attrs, com.android.internal.R.styleable.TabWidget, defStyleAttr, defStyleRes);
Gilles Debunne44c14732010-10-19 11:56:59 -070092
93 mTabLayoutId = a.getResourceId(R.styleable.TabWidget_tabLayout, 0);
Gilles Debunne44c14732010-10-19 11:56:59 -070094 a.recycle();
95
Gilles Debunne0e23ab72010-12-14 19:26:37 -080096 if (mTabLayoutId == 0) {
97 // In case the tabWidgetStyle does not inherit from Widget.TabWidget and tabLayout is
98 // not defined.
99 mTabLayoutId = R.layout.tab_indicator_holo;
100 }
101
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102 initTabHost();
103 }
104
Romain Guy7237c562009-08-18 17:38:14 -0700105 private void initTabHost() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106 setFocusableInTouchMode(true);
107 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
108
109 mCurrentTab = -1;
110 mCurrentView = null;
111 }
112
113 /**
114 * Get a new {@link TabSpec} associated with this tab host.
115 * @param tag required tag of tab.
116 */
117 public TabSpec newTabSpec(String tag) {
118 return new TabSpec(tag);
119 }
120
121
122
123 /**
Jack Veenstra982be3b2009-05-27 15:07:59 -0700124 * <p>Call setup() before adding tabs if loading TabHost using findViewById().
125 * <i><b>However</i></b>: You do not need to call setup() after getTabHost()
126 * in {@link android.app.TabActivity TabActivity}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127 * Example:</p>
128<pre>mTabHost = (TabHost)findViewById(R.id.tabhost);
129mTabHost.setup();
130mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
131 */
132 public void setup() {
133 mTabWidget = (TabWidget) findViewById(com.android.internal.R.id.tabs);
134 if (mTabWidget == null) {
135 throw new RuntimeException(
136 "Your TabHost must have a TabWidget whose id attribute is 'android.R.id.tabs'");
137 }
Evan Millar3730bb12009-08-21 13:58:41 -0700138
139 // KeyListener to attach to all tabs. Detects non-navigation keys
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 // and relays them to the tab content.
141 mTabKeyListener = new OnKeyListener() {
142 public boolean onKey(View v, int keyCode, KeyEvent event) {
143 switch (keyCode) {
144 case KeyEvent.KEYCODE_DPAD_CENTER:
145 case KeyEvent.KEYCODE_DPAD_LEFT:
146 case KeyEvent.KEYCODE_DPAD_RIGHT:
147 case KeyEvent.KEYCODE_DPAD_UP:
148 case KeyEvent.KEYCODE_DPAD_DOWN:
149 case KeyEvent.KEYCODE_ENTER:
150 return false;
Evan Millar3730bb12009-08-21 13:58:41 -0700151
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152 }
153 mTabContent.requestFocus(View.FOCUS_FORWARD);
154 return mTabContent.dispatchKeyEvent(event);
155 }
Evan Millar3730bb12009-08-21 13:58:41 -0700156
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800157 };
Evan Millar3730bb12009-08-21 13:58:41 -0700158
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800159 mTabWidget.setTabSelectionListener(new TabWidget.OnTabSelectionChanged() {
160 public void onTabSelectionChanged(int tabIndex, boolean clicked) {
161 setCurrentTab(tabIndex);
162 if (clicked) {
163 mTabContent.requestFocus(View.FOCUS_FORWARD);
164 }
165 }
166 });
167
168 mTabContent = (FrameLayout) findViewById(com.android.internal.R.id.tabcontent);
169 if (mTabContent == null) {
170 throw new RuntimeException(
Evan Millar3730bb12009-08-21 13:58:41 -0700171 "Your TabHost must have a FrameLayout whose id attribute is "
Romain Guy7237c562009-08-18 17:38:14 -0700172 + "'android.R.id.tabcontent'");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800173 }
174 }
175
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700176 @Override
177 public void sendAccessibilityEvent(int eventType) {
178 /* avoid super class behavior - TabWidget sends the right events */
179 }
180
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 /**
182 * If you are using {@link TabSpec#setContent(android.content.Intent)}, this
183 * must be called since the activityGroup is needed to launch the local activity.
184 *
185 * This is done for you if you extend {@link android.app.TabActivity}.
186 * @param activityGroup Used to launch activities for tab content.
187 */
188 public void setup(LocalActivityManager activityGroup) {
189 setup();
190 mLocalActivityManager = activityGroup;
191 }
192
193
194 @Override
195 protected void onAttachedToWindow() {
196 super.onAttachedToWindow();
197 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -0800198 treeObserver.addOnTouchModeChangeListener(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199 }
200
201 @Override
202 protected void onDetachedFromWindow() {
203 super.onDetachedFromWindow();
204 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -0800205 treeObserver.removeOnTouchModeChangeListener(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800206 }
207
208 /**
209 * {@inheritDoc}
210 */
211 public void onTouchModeChanged(boolean isInTouchMode) {
212 if (!isInTouchMode) {
213 // leaving touch mode.. if nothing has focus, let's give it to
214 // the indicator of the current tab
Romain Guy7237c562009-08-18 17:38:14 -0700215 if (mCurrentView != null && (!mCurrentView.hasFocus() || mCurrentView.isFocused())) {
Jack Veenstra53175142009-06-01 21:27:01 -0700216 mTabWidget.getChildTabViewAt(mCurrentTab).requestFocus();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800217 }
218 }
219 }
220
221 /**
222 * Add a tab.
223 * @param tabSpec Specifies how to create the indicator and content.
224 */
225 public void addTab(TabSpec tabSpec) {
226
227 if (tabSpec.mIndicatorStrategy == null) {
228 throw new IllegalArgumentException("you must specify a way to create the tab indicator.");
229 }
230
231 if (tabSpec.mContentStrategy == null) {
232 throw new IllegalArgumentException("you must specify a way to create the tab content");
233 }
234 View tabIndicator = tabSpec.mIndicatorStrategy.createIndicatorView();
235 tabIndicator.setOnKeyListener(mTabKeyListener);
Jack Veenstra53175142009-06-01 21:27:01 -0700236
237 // If this is a custom view, then do not draw the bottom strips for
238 // the tab indicators.
239 if (tabSpec.mIndicatorStrategy instanceof ViewIndicatorStrategy) {
Romain Guy61c9d4b2010-03-01 14:12:10 -0800240 mTabWidget.setStripEnabled(false);
Jack Veenstra53175142009-06-01 21:27:01 -0700241 }
Gilles Debunne44c14732010-10-19 11:56:59 -0700242
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243 mTabWidget.addView(tabIndicator);
244 mTabSpecs.add(tabSpec);
245
246 if (mCurrentTab == -1) {
247 setCurrentTab(0);
248 }
249 }
250
251
252 /**
253 * Removes all tabs from the tab widget associated with this tab host.
254 */
255 public void clearAllTabs() {
256 mTabWidget.removeAllViews();
257 initTabHost();
258 mTabContent.removeAllViews();
259 mTabSpecs.clear();
260 requestLayout();
261 invalidate();
262 }
263
264 public TabWidget getTabWidget() {
265 return mTabWidget;
266 }
267
268 public int getCurrentTab() {
269 return mCurrentTab;
270 }
271
272 public String getCurrentTabTag() {
273 if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
274 return mTabSpecs.get(mCurrentTab).getTag();
275 }
276 return null;
277 }
278
279 public View getCurrentTabView() {
280 if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
Jack Veenstra53175142009-06-01 21:27:01 -0700281 return mTabWidget.getChildTabViewAt(mCurrentTab);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282 }
283 return null;
284 }
285
286 public View getCurrentView() {
287 return mCurrentView;
288 }
289
290 public void setCurrentTabByTag(String tag) {
291 int i;
292 for (i = 0; i < mTabSpecs.size(); i++) {
293 if (mTabSpecs.get(i).getTag().equals(tag)) {
294 setCurrentTab(i);
295 break;
296 }
297 }
298 }
299
300 /**
301 * Get the FrameLayout which holds tab content
302 */
303 public FrameLayout getTabContentView() {
304 return mTabContent;
305 }
306
Roger Olsson54ed87c2011-01-24 11:19:11 +0100307 /**
308 * Get the location of the TabWidget.
309 *
310 * @return The TabWidget location.
311 */
312 private int getTabWidgetLocation() {
313 int location = TABWIDGET_LOCATION_TOP;
314
315 switch (mTabWidget.getOrientation()) {
316 case LinearLayout.VERTICAL:
317 location = (mTabContent.getLeft() < mTabWidget.getLeft()) ? TABWIDGET_LOCATION_RIGHT
318 : TABWIDGET_LOCATION_LEFT;
319 break;
320 case LinearLayout.HORIZONTAL:
321 default:
322 location = (mTabContent.getTop() < mTabWidget.getTop()) ? TABWIDGET_LOCATION_BOTTOM
323 : TABWIDGET_LOCATION_TOP;
324 break;
325 }
326 return location;
327 }
328
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800329 @Override
330 public boolean dispatchKeyEvent(KeyEvent event) {
331 final boolean handled = super.dispatchKeyEvent(event);
332
Roger Olsson54ed87c2011-01-24 11:19:11 +0100333 // unhandled key events change focus to tab indicator for embedded
334 // activities when there is nothing that will take focus from default
335 // focus searching
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800336 if (!handled
337 && (event.getAction() == KeyEvent.ACTION_DOWN)
Bjorn Bringertacdef592009-12-10 15:58:49 +0000338 && (mCurrentView != null)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800339 && (mCurrentView.isRootNamespace())
Roger Olsson54ed87c2011-01-24 11:19:11 +0100340 && (mCurrentView.hasFocus())) {
341 int keyCodeShouldChangeFocus = KeyEvent.KEYCODE_DPAD_UP;
342 int directionShouldChangeFocus = View.FOCUS_UP;
343 int soundEffect = SoundEffectConstants.NAVIGATION_UP;
344
345 switch (getTabWidgetLocation()) {
346 case TABWIDGET_LOCATION_LEFT:
347 keyCodeShouldChangeFocus = KeyEvent.KEYCODE_DPAD_LEFT;
348 directionShouldChangeFocus = View.FOCUS_LEFT;
349 soundEffect = SoundEffectConstants.NAVIGATION_LEFT;
350 break;
351 case TABWIDGET_LOCATION_RIGHT:
352 keyCodeShouldChangeFocus = KeyEvent.KEYCODE_DPAD_RIGHT;
353 directionShouldChangeFocus = View.FOCUS_RIGHT;
354 soundEffect = SoundEffectConstants.NAVIGATION_RIGHT;
355 break;
356 case TABWIDGET_LOCATION_BOTTOM:
357 keyCodeShouldChangeFocus = KeyEvent.KEYCODE_DPAD_DOWN;
358 directionShouldChangeFocus = View.FOCUS_DOWN;
359 soundEffect = SoundEffectConstants.NAVIGATION_DOWN;
360 break;
361 case TABWIDGET_LOCATION_TOP:
362 default:
363 keyCodeShouldChangeFocus = KeyEvent.KEYCODE_DPAD_UP;
364 directionShouldChangeFocus = View.FOCUS_UP;
365 soundEffect = SoundEffectConstants.NAVIGATION_UP;
366 break;
367 }
368 if (event.getKeyCode() == keyCodeShouldChangeFocus
369 && mCurrentView.findFocus().focusSearch(directionShouldChangeFocus) == null) {
370 mTabWidget.getChildTabViewAt(mCurrentTab).requestFocus();
371 playSoundEffect(soundEffect);
372 return true;
373 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800374 }
Evan Millar3730bb12009-08-21 13:58:41 -0700375 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800376 }
377
378
379 @Override
380 public void dispatchWindowFocusChanged(boolean hasFocus) {
Bjorn Bringertacdef592009-12-10 15:58:49 +0000381 if (mCurrentView != null){
382 mCurrentView.dispatchWindowFocusChanged(hasFocus);
383 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800384 }
385
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800386 @Override
387 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
388 super.onInitializeAccessibilityEvent(event);
389 event.setClassName(TabHost.class.getName());
390 }
391
392 @Override
393 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
394 super.onInitializeAccessibilityNodeInfo(info);
395 info.setClassName(TabHost.class.getName());
396 }
397
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800398 public void setCurrentTab(int index) {
399 if (index < 0 || index >= mTabSpecs.size()) {
400 return;
401 }
402
403 if (index == mCurrentTab) {
404 return;
405 }
406
407 // notify old tab content
408 if (mCurrentTab != -1) {
409 mTabSpecs.get(mCurrentTab).mContentStrategy.tabClosed();
410 }
411
412 mCurrentTab = index;
413 final TabHost.TabSpec spec = mTabSpecs.get(index);
414
415 // Call the tab widget's focusCurrentTab(), instead of just
416 // selecting the tab.
417 mTabWidget.focusCurrentTab(mCurrentTab);
Evan Millar3730bb12009-08-21 13:58:41 -0700418
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 // tab content
420 mCurrentView = spec.mContentStrategy.getContentView();
421
422 if (mCurrentView.getParent() == null) {
423 mTabContent
424 .addView(
425 mCurrentView,
426 new ViewGroup.LayoutParams(
Romain Guy980a9382010-01-08 15:06:28 -0800427 ViewGroup.LayoutParams.MATCH_PARENT,
428 ViewGroup.LayoutParams.MATCH_PARENT));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800429 }
430
431 if (!mTabWidget.hasFocus()) {
432 // if the tab widget didn't take focus (likely because we're in touch mode)
433 // give the current tab content view a shot
434 mCurrentView.requestFocus();
435 }
436
437 //mTabContent.requestFocus(View.FOCUS_FORWARD);
438 invokeOnTabChangeListener();
439 }
440
441 /**
442 * Register a callback to be invoked when the selected state of any of the items
443 * in this list changes
444 * @param l
445 * The callback that will run
446 */
447 public void setOnTabChangedListener(OnTabChangeListener l) {
448 mOnTabChangeListener = l;
449 }
450
451 private void invokeOnTabChangeListener() {
452 if (mOnTabChangeListener != null) {
453 mOnTabChangeListener.onTabChanged(getCurrentTabTag());
454 }
455 }
456
457 /**
458 * Interface definition for a callback to be invoked when tab changed
459 */
460 public interface OnTabChangeListener {
461 void onTabChanged(String tabId);
462 }
463
464
465 /**
466 * Makes the content of a tab when it is selected. Use this if your tab
467 * content needs to be created on demand, i.e. you are not showing an
468 * existing view or starting an activity.
469 */
470 public interface TabContentFactory {
471 /**
472 * Callback to make the tab contents
Evan Millar3730bb12009-08-21 13:58:41 -0700473 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800474 * @param tag
475 * Which tab was selected.
Jack Veenstra982be3b2009-05-27 15:07:59 -0700476 * @return The view to display the contents of the selected tab.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800477 */
478 View createTabContent(String tag);
479 }
480
481
482 /**
Jack Veenstra982be3b2009-05-27 15:07:59 -0700483 * A tab has a tab indicator, content, and a tag that is used to keep
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 * track of it. This builder helps choose among these options.
485 *
486 * For the tab indicator, your choices are:
487 * 1) set a label
488 * 2) set a label and an icon
489 *
490 * For the tab content, your choices are:
491 * 1) the id of a {@link View}
492 * 2) a {@link TabContentFactory} that creates the {@link View} content.
493 * 3) an {@link Intent} that launches an {@link android.app.Activity}.
494 */
495 public class TabSpec {
496
497 private String mTag;
498
499 private IndicatorStrategy mIndicatorStrategy;
500 private ContentStrategy mContentStrategy;
501
502 private TabSpec(String tag) {
503 mTag = tag;
504 }
505
506 /**
507 * Specify a label as the tab indicator.
508 */
509 public TabSpec setIndicator(CharSequence label) {
510 mIndicatorStrategy = new LabelIndicatorStrategy(label);
511 return this;
512 }
513
514 /**
515 * Specify a label and icon as the tab indicator.
516 */
517 public TabSpec setIndicator(CharSequence label, Drawable icon) {
518 mIndicatorStrategy = new LabelAndIconIndicatorStrategy(label, icon);
519 return this;
520 }
521
522 /**
Jack Veenstra53175142009-06-01 21:27:01 -0700523 * Specify a view as the tab indicator.
524 */
525 public TabSpec setIndicator(View view) {
526 mIndicatorStrategy = new ViewIndicatorStrategy(view);
527 return this;
528 }
529
530 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800531 * Specify the id of the view that should be used as the content
532 * of the tab.
533 */
534 public TabSpec setContent(int viewId) {
535 mContentStrategy = new ViewIdContentStrategy(viewId);
536 return this;
537 }
538
539 /**
540 * Specify a {@link android.widget.TabHost.TabContentFactory} to use to
541 * create the content of the tab.
542 */
543 public TabSpec setContent(TabContentFactory contentFactory) {
544 mContentStrategy = new FactoryContentStrategy(mTag, contentFactory);
545 return this;
546 }
547
548 /**
549 * Specify an intent to use to launch an activity as the tab content.
550 */
551 public TabSpec setContent(Intent intent) {
552 mContentStrategy = new IntentContentStrategy(mTag, intent);
553 return this;
554 }
555
556
Jack Veenstra53175142009-06-01 21:27:01 -0700557 public String getTag() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800558 return mTag;
559 }
560 }
561
562 /**
563 * Specifies what you do to create a tab indicator.
564 */
565 private static interface IndicatorStrategy {
566
567 /**
568 * Return the view for the indicator.
569 */
570 View createIndicatorView();
571 }
572
573 /**
574 * Specifies what you do to manage the tab content.
575 */
576 private static interface ContentStrategy {
577
578 /**
579 * Return the content view. The view should may be cached locally.
580 */
581 View getContentView();
582
583 /**
584 * Perhaps do something when the tab associated with this content has
585 * been closed (i.e make it invisible, or remove it).
586 */
587 void tabClosed();
588 }
589
590 /**
591 * How to create a tab indicator that just has a label.
592 */
593 private class LabelIndicatorStrategy implements IndicatorStrategy {
594
595 private final CharSequence mLabel;
596
597 private LabelIndicatorStrategy(CharSequence label) {
598 mLabel = label;
599 }
600
601 public View createIndicatorView() {
Mike Cleron76097642009-09-25 17:53:56 -0700602 final Context context = getContext();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800603 LayoutInflater inflater =
Mike Cleron76097642009-09-25 17:53:56 -0700604 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Gilles Debunne44c14732010-10-19 11:56:59 -0700605 View tabIndicator = inflater.inflate(mTabLayoutId,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800606 mTabWidget, // tab widget is the parent
607 false); // no inflate params
608
609 final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
610 tv.setText(mLabel);
611
Mike Cleron76097642009-09-25 17:53:56 -0700612 if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
613 // Donut apps get old color scheme
614 tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);
615 tv.setTextColor(context.getResources().getColorStateList(R.color.tab_indicator_text_v4));
616 }
Gilles Debunne44c14732010-10-19 11:56:59 -0700617
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800618 return tabIndicator;
619 }
620 }
621
622 /**
623 * How we create a tab indicator that has a label and an icon
624 */
625 private class LabelAndIconIndicatorStrategy implements IndicatorStrategy {
626
627 private final CharSequence mLabel;
628 private final Drawable mIcon;
629
630 private LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon) {
631 mLabel = label;
632 mIcon = icon;
633 }
634
635 public View createIndicatorView() {
Mike Cleron76097642009-09-25 17:53:56 -0700636 final Context context = getContext();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800637 LayoutInflater inflater =
Mike Cleron76097642009-09-25 17:53:56 -0700638 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Gilles Debunne44c14732010-10-19 11:56:59 -0700639 View tabIndicator = inflater.inflate(mTabLayoutId,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800640 mTabWidget, // tab widget is the parent
641 false); // no inflate params
642
643 final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700644 final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.icon);
645
646 // when icon is gone by default, we're in exclusive mode
647 final boolean exclusive = iconView.getVisibility() == View.GONE;
648 final boolean bindIcon = !exclusive || TextUtils.isEmpty(mLabel);
649
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800650 tv.setText(mLabel);
651
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700652 if (bindIcon && mIcon != null) {
Gilles Debunne44c14732010-10-19 11:56:59 -0700653 iconView.setImageDrawable(mIcon);
654 iconView.setVisibility(VISIBLE);
655 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800656
Mike Cleron76097642009-09-25 17:53:56 -0700657 if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
658 // Donut apps get old color scheme
659 tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);
660 tv.setTextColor(context.getResources().getColorStateList(R.color.tab_indicator_text_v4));
661 }
Gilles Debunne44c14732010-10-19 11:56:59 -0700662
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800663 return tabIndicator;
664 }
665 }
666
667 /**
Jack Veenstra53175142009-06-01 21:27:01 -0700668 * How to create a tab indicator by specifying a view.
669 */
670 private class ViewIndicatorStrategy implements IndicatorStrategy {
671
672 private final View mView;
673
674 private ViewIndicatorStrategy(View view) {
675 mView = view;
676 }
677
678 public View createIndicatorView() {
679 return mView;
680 }
681 }
682
683 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800684 * How to create the tab content via a view id.
685 */
686 private class ViewIdContentStrategy implements ContentStrategy {
687
688 private final View mView;
689
690 private ViewIdContentStrategy(int viewId) {
691 mView = mTabContent.findViewById(viewId);
692 if (mView != null) {
693 mView.setVisibility(View.GONE);
694 } else {
695 throw new RuntimeException("Could not create tab content because " +
696 "could not find view with id " + viewId);
697 }
698 }
699
700 public View getContentView() {
701 mView.setVisibility(View.VISIBLE);
702 return mView;
703 }
704
705 public void tabClosed() {
706 mView.setVisibility(View.GONE);
707 }
708 }
709
710 /**
711 * How tab content is managed using {@link TabContentFactory}.
712 */
713 private class FactoryContentStrategy implements ContentStrategy {
714 private View mTabContent;
715 private final CharSequence mTag;
716 private TabContentFactory mFactory;
717
718 public FactoryContentStrategy(CharSequence tag, TabContentFactory factory) {
719 mTag = tag;
720 mFactory = factory;
721 }
722
723 public View getContentView() {
724 if (mTabContent == null) {
725 mTabContent = mFactory.createTabContent(mTag.toString());
726 }
727 mTabContent.setVisibility(View.VISIBLE);
728 return mTabContent;
729 }
730
731 public void tabClosed() {
Gilles Debunnece2baf02010-02-10 15:19:14 -0800732 mTabContent.setVisibility(View.GONE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800733 }
734 }
735
736 /**
737 * How tab content is managed via an {@link Intent}: the content view is the
738 * decorview of the launched activity.
739 */
740 private class IntentContentStrategy implements ContentStrategy {
741
742 private final String mTag;
743 private final Intent mIntent;
744
745 private View mLaunchedView;
746
747 private IntentContentStrategy(String tag, Intent intent) {
748 mTag = tag;
749 mIntent = intent;
750 }
751
752 public View getContentView() {
753 if (mLocalActivityManager == null) {
754 throw new IllegalStateException("Did you forget to call 'public void setup(LocalActivityManager activityGroup)'?");
755 }
756 final Window w = mLocalActivityManager.startActivity(
757 mTag, mIntent);
758 final View wd = w != null ? w.getDecorView() : null;
759 if (mLaunchedView != wd && mLaunchedView != null) {
760 if (mLaunchedView.getParent() != null) {
761 mTabContent.removeView(mLaunchedView);
762 }
763 }
764 mLaunchedView = wd;
Evan Millar3730bb12009-08-21 13:58:41 -0700765
Jack Veenstra982be3b2009-05-27 15:07:59 -0700766 // XXX Set FOCUS_AFTER_DESCENDANTS on embedded activities for now so they can get
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800767 // focus if none of their children have it. They need focus to be able to
768 // display menu items.
769 //
770 // Replace this with something better when Bug 628886 is fixed...
771 //
772 if (mLaunchedView != null) {
773 mLaunchedView.setVisibility(View.VISIBLE);
774 mLaunchedView.setFocusableInTouchMode(true);
775 ((ViewGroup) mLaunchedView).setDescendantFocusability(
776 FOCUS_AFTER_DESCENDANTS);
777 }
778 return mLaunchedView;
779 }
780
781 public void tabClosed() {
782 if (mLaunchedView != null) {
783 mLaunchedView.setVisibility(View.GONE);
784 }
785 }
786 }
787
788}