blob: a9a351157e570669afd92e01e005e2a6f28b0f1e [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
Alan Viverette47565832016-07-21 14:47:22 -040019import android.annotation.NonNull;
20import android.annotation.Nullable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.app.LocalActivityManager;
Artur Satayeved5a6ae2019-12-10 17:47:54 +000022import android.compat.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.content.Context;
24import android.content.Intent;
Gilles Debunne44c14732010-10-19 11:56:59 -070025import android.content.res.TypedArray;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.graphics.drawable.Drawable;
Mike Cleron76097642009-09-25 17:53:56 -070027import android.os.Build;
Jeff Sharkey11f4a482011-08-08 21:05:40 -070028import android.text.TextUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.util.AttributeSet;
30import android.view.KeyEvent;
31import android.view.LayoutInflater;
32import android.view.SoundEffectConstants;
33import android.view.View;
34import android.view.ViewGroup;
35import android.view.ViewTreeObserver;
36import android.view.Window;
Aurimas Liutikas99441c52016-10-11 16:48:32 -070037
38import com.android.internal.R;
39
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import java.util.ArrayList;
41import java.util.List;
42
43/**
44 * Container for a tabbed window view. This object holds two children: a set of tab labels that the
45 * user clicks to select a specific tab, and a FrameLayout object that displays the contents of that
46 * page. The individual elements are typically controlled using this container object, rather than
47 * setting values on the child elements themselves.
Scott Main41ec6532010-08-19 16:57:07 -070048 *
Charles Chen28a0e472019-07-24 19:45:16 +080049 * @deprecated new applications should use fragment APIs instead of this class:
50 * Use <a href="{@docRoot}guide/navigation/navigation-swipe-view">TabLayout and ViewPager</a>
51 * instead.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052 */
Charles Chen28a0e472019-07-24 19:45:16 +080053@Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchModeChangeListener {
55
Roger Olsson54ed87c2011-01-24 11:19:11 +010056 private static final int TABWIDGET_LOCATION_LEFT = 0;
57 private static final int TABWIDGET_LOCATION_TOP = 1;
58 private static final int TABWIDGET_LOCATION_RIGHT = 2;
59 private static final int TABWIDGET_LOCATION_BOTTOM = 3;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060 private TabWidget mTabWidget;
61 private FrameLayout mTabContent;
Charles Chen5c0943d2019-07-30 17:21:47 +080062 @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q,
63 publicAlternatives = "Use {@code androidx.viewpager.widget.ViewPager} and "
64 + "{@code com.google.android.material.tabs.TabLayout} instead.\n"
65 + "See <a href=\"{@docRoot}guide/navigation/navigation-swipe-view"
66 + "\">TabLayout and ViewPager</a>")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067 private List<TabSpec> mTabSpecs = new ArrayList<TabSpec>(2);
68 /**
69 * This field should be made private, so it is hidden from the SDK.
70 * {@hide}
71 */
Charles Chen5c0943d2019-07-30 17:21:47 +080072 @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q,
73 publicAlternatives = "Use {@code androidx.viewpager.widget.ViewPager} and "
74 + "{@code com.google.android.material.tabs.TabLayout} instead.\n"
75 + "See <a href=\"{@docRoot}guide/navigation/navigation-swipe-view"
76 + "\">TabLayout and ViewPager</a>")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077 protected int mCurrentTab = -1;
78 private View mCurrentView = null;
79 /**
80 * This field should be made private, so it is hidden from the SDK.
81 * {@hide}
82 */
83 protected LocalActivityManager mLocalActivityManager = null;
Charles Chen5c0943d2019-07-30 17:21:47 +080084 @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q,
85 publicAlternatives = "Use {@code androidx.viewpager.widget.ViewPager} and "
86 + "{@code com.google.android.material.tabs.TabLayout} instead.\n"
87 + "See <a href=\"{@docRoot}guide/navigation/navigation-swipe-view"
88 + "\">TabLayout and ViewPager</a>")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089 private OnTabChangeListener mOnTabChangeListener;
90 private OnKeyListener mTabKeyListener;
91
Gilles Debunne44c14732010-10-19 11:56:59 -070092 private int mTabLayoutId;
93
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080094 public TabHost(Context context) {
95 super(context);
96 initTabHost();
97 }
98
99 public TabHost(Context context, AttributeSet attrs) {
Alan Viverette617feb92013-09-09 18:09:13 -0700100 this(context, attrs, com.android.internal.R.attr.tabWidgetStyle);
101 }
102
103 public TabHost(Context context, AttributeSet attrs, int defStyleAttr) {
104 this(context, attrs, defStyleAttr, 0);
105 }
106
107 public TabHost(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108 super(context, attrs);
Gilles Debunne44c14732010-10-19 11:56:59 -0700109
Alan Viverette617feb92013-09-09 18:09:13 -0700110 final TypedArray a = context.obtainStyledAttributes(
111 attrs, com.android.internal.R.styleable.TabWidget, defStyleAttr, defStyleRes);
Aurimas Liutikasab324cf2019-02-07 16:46:38 -0800112 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TabWidget,
113 attrs, a, defStyleAttr, defStyleRes);
Gilles Debunne44c14732010-10-19 11:56:59 -0700114
115 mTabLayoutId = a.getResourceId(R.styleable.TabWidget_tabLayout, 0);
Gilles Debunne44c14732010-10-19 11:56:59 -0700116 a.recycle();
117
Gilles Debunne0e23ab72010-12-14 19:26:37 -0800118 if (mTabLayoutId == 0) {
119 // In case the tabWidgetStyle does not inherit from Widget.TabWidget and tabLayout is
120 // not defined.
121 mTabLayoutId = R.layout.tab_indicator_holo;
122 }
123
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124 initTabHost();
125 }
126
Romain Guy7237c562009-08-18 17:38:14 -0700127 private void initTabHost() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128 setFocusableInTouchMode(true);
129 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
130
131 mCurrentTab = -1;
132 mCurrentView = null;
133 }
134
135 /**
Alan Viverette47565832016-07-21 14:47:22 -0400136 * Creates a new {@link TabSpec} associated with this tab host.
137 *
138 * @param tag tag for the tab specification, must be non-null
Kirill Grouchnikov90ac2ac2016-08-02 11:43:28 -0400139 * @throws IllegalArgumentException If the passed tag is null
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 */
Alan Viverette47565832016-07-21 14:47:22 -0400141 @NonNull
142 public TabSpec newTabSpec(@NonNull String tag) {
143 if (tag == null) {
144 throw new IllegalArgumentException("tag must be non-null");
145 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 return new TabSpec(tag);
147 }
148
149
150
151 /**
Jack Veenstra982be3b2009-05-27 15:07:59 -0700152 * <p>Call setup() before adding tabs if loading TabHost using findViewById().
153 * <i><b>However</i></b>: You do not need to call setup() after getTabHost()
154 * in {@link android.app.TabActivity TabActivity}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800155 * Example:</p>
156<pre>mTabHost = (TabHost)findViewById(R.id.tabhost);
157mTabHost.setup();
158mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
159 */
160 public void setup() {
Alan Viverette51efddb2017-04-05 10:00:01 -0400161 mTabWidget = findViewById(com.android.internal.R.id.tabs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800162 if (mTabWidget == null) {
163 throw new RuntimeException(
164 "Your TabHost must have a TabWidget whose id attribute is 'android.R.id.tabs'");
165 }
Evan Millar3730bb12009-08-21 13:58:41 -0700166
167 // KeyListener to attach to all tabs. Detects non-navigation keys
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168 // and relays them to the tab content.
169 mTabKeyListener = new OnKeyListener() {
170 public boolean onKey(View v, int keyCode, KeyEvent event) {
Evan Rosky815fb1f2017-07-10 16:14:15 -0700171 if (KeyEvent.isModifierKey(keyCode)) {
172 return false;
173 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800174 switch (keyCode) {
175 case KeyEvent.KEYCODE_DPAD_CENTER:
176 case KeyEvent.KEYCODE_DPAD_LEFT:
177 case KeyEvent.KEYCODE_DPAD_RIGHT:
178 case KeyEvent.KEYCODE_DPAD_UP:
179 case KeyEvent.KEYCODE_DPAD_DOWN:
Evan Rosky815fb1f2017-07-10 16:14:15 -0700180 case KeyEvent.KEYCODE_TAB:
181 case KeyEvent.KEYCODE_SPACE:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800182 case KeyEvent.KEYCODE_ENTER:
183 return false;
Evan Millar3730bb12009-08-21 13:58:41 -0700184
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800185 }
186 mTabContent.requestFocus(View.FOCUS_FORWARD);
187 return mTabContent.dispatchKeyEvent(event);
188 }
Evan Millar3730bb12009-08-21 13:58:41 -0700189
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800190 };
Evan Millar3730bb12009-08-21 13:58:41 -0700191
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800192 mTabWidget.setTabSelectionListener(new TabWidget.OnTabSelectionChanged() {
193 public void onTabSelectionChanged(int tabIndex, boolean clicked) {
194 setCurrentTab(tabIndex);
195 if (clicked) {
196 mTabContent.requestFocus(View.FOCUS_FORWARD);
197 }
198 }
199 });
200
Alan Viverette51efddb2017-04-05 10:00:01 -0400201 mTabContent = findViewById(com.android.internal.R.id.tabcontent);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800202 if (mTabContent == null) {
203 throw new RuntimeException(
Evan Millar3730bb12009-08-21 13:58:41 -0700204 "Your TabHost must have a FrameLayout whose id attribute is "
Romain Guy7237c562009-08-18 17:38:14 -0700205 + "'android.R.id.tabcontent'");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800206 }
207 }
208
Alan Viverettea54956a2015-01-07 16:05:02 -0800209 /** @hide */
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700210 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800211 public void sendAccessibilityEventInternal(int eventType) {
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700212 /* avoid super class behavior - TabWidget sends the right events */
213 }
214
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800215 /**
216 * If you are using {@link TabSpec#setContent(android.content.Intent)}, this
217 * must be called since the activityGroup is needed to launch the local activity.
218 *
219 * This is done for you if you extend {@link android.app.TabActivity}.
220 * @param activityGroup Used to launch activities for tab content.
221 */
222 public void setup(LocalActivityManager activityGroup) {
223 setup();
224 mLocalActivityManager = activityGroup;
225 }
226
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 public void onTouchModeChanged(boolean isInTouchMode) {
James Cookd8b7d2c2015-06-02 13:27:28 -0700229 // No longer used, but kept to maintain API compatibility.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 }
231
232 /**
233 * Add a tab.
234 * @param tabSpec Specifies how to create the indicator and content.
Kirill Grouchnikov90ac2ac2016-08-02 11:43:28 -0400235 * @throws IllegalArgumentException If the passed tab spec has null indicator strategy and / or
236 * null content strategy.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 */
238 public void addTab(TabSpec tabSpec) {
239
240 if (tabSpec.mIndicatorStrategy == null) {
241 throw new IllegalArgumentException("you must specify a way to create the tab indicator.");
242 }
243
244 if (tabSpec.mContentStrategy == null) {
245 throw new IllegalArgumentException("you must specify a way to create the tab content");
246 }
247 View tabIndicator = tabSpec.mIndicatorStrategy.createIndicatorView();
248 tabIndicator.setOnKeyListener(mTabKeyListener);
Jack Veenstra53175142009-06-01 21:27:01 -0700249
250 // If this is a custom view, then do not draw the bottom strips for
251 // the tab indicators.
252 if (tabSpec.mIndicatorStrategy instanceof ViewIndicatorStrategy) {
Romain Guy61c9d4b2010-03-01 14:12:10 -0800253 mTabWidget.setStripEnabled(false);
Jack Veenstra53175142009-06-01 21:27:01 -0700254 }
Gilles Debunne44c14732010-10-19 11:56:59 -0700255
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800256 mTabWidget.addView(tabIndicator);
257 mTabSpecs.add(tabSpec);
258
259 if (mCurrentTab == -1) {
260 setCurrentTab(0);
261 }
262 }
263
264
265 /**
266 * Removes all tabs from the tab widget associated with this tab host.
267 */
268 public void clearAllTabs() {
269 mTabWidget.removeAllViews();
270 initTabHost();
271 mTabContent.removeAllViews();
272 mTabSpecs.clear();
273 requestLayout();
274 invalidate();
275 }
276
277 public TabWidget getTabWidget() {
278 return mTabWidget;
279 }
280
Alan Viverette47565832016-07-21 14:47:22 -0400281 /**
282 * Returns the current tab.
283 *
284 * @return the current tab, may be {@code null} if no tab is set as current
285 */
286 @Nullable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800287 public int getCurrentTab() {
288 return mCurrentTab;
289 }
290
Alan Viverette47565832016-07-21 14:47:22 -0400291 /**
292 * Returns the tag for the current tab.
293 *
294 * @return the tag for the current tab, may be {@code null} if no tab is
295 * set as current
296 */
297 @Nullable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800298 public String getCurrentTabTag() {
299 if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
300 return mTabSpecs.get(mCurrentTab).getTag();
301 }
302 return null;
303 }
304
Alan Viverette47565832016-07-21 14:47:22 -0400305 /**
306 * Returns the view for the current tab.
307 *
308 * @return the view for the current tab, may be {@code null} if no tab is
309 * set as current
310 */
311 @Nullable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800312 public View getCurrentTabView() {
313 if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
Jack Veenstra53175142009-06-01 21:27:01 -0700314 return mTabWidget.getChildTabViewAt(mCurrentTab);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800315 }
316 return null;
317 }
318
319 public View getCurrentView() {
320 return mCurrentView;
321 }
322
Alan Viverette47565832016-07-21 14:47:22 -0400323 /**
324 * Sets the current tab based on its tag.
325 *
326 * @param tag the tag for the tab to set as current
327 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800328 public void setCurrentTabByTag(String tag) {
Alan Viverette47565832016-07-21 14:47:22 -0400329 for (int i = 0, count = mTabSpecs.size(); i < count; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800330 if (mTabSpecs.get(i).getTag().equals(tag)) {
331 setCurrentTab(i);
332 break;
333 }
334 }
335 }
336
337 /**
338 * Get the FrameLayout which holds tab content
339 */
340 public FrameLayout getTabContentView() {
341 return mTabContent;
342 }
343
Roger Olsson54ed87c2011-01-24 11:19:11 +0100344 /**
345 * Get the location of the TabWidget.
346 *
347 * @return The TabWidget location.
348 */
349 private int getTabWidgetLocation() {
350 int location = TABWIDGET_LOCATION_TOP;
351
352 switch (mTabWidget.getOrientation()) {
353 case LinearLayout.VERTICAL:
354 location = (mTabContent.getLeft() < mTabWidget.getLeft()) ? TABWIDGET_LOCATION_RIGHT
355 : TABWIDGET_LOCATION_LEFT;
356 break;
357 case LinearLayout.HORIZONTAL:
358 default:
359 location = (mTabContent.getTop() < mTabWidget.getTop()) ? TABWIDGET_LOCATION_BOTTOM
360 : TABWIDGET_LOCATION_TOP;
361 break;
362 }
363 return location;
364 }
365
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366 @Override
367 public boolean dispatchKeyEvent(KeyEvent event) {
368 final boolean handled = super.dispatchKeyEvent(event);
369
Roger Olsson54ed87c2011-01-24 11:19:11 +0100370 // unhandled key events change focus to tab indicator for embedded
371 // activities when there is nothing that will take focus from default
372 // focus searching
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800373 if (!handled
374 && (event.getAction() == KeyEvent.ACTION_DOWN)
Bjorn Bringertacdef592009-12-10 15:58:49 +0000375 && (mCurrentView != null)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800376 && (mCurrentView.isRootNamespace())
Roger Olsson54ed87c2011-01-24 11:19:11 +0100377 && (mCurrentView.hasFocus())) {
378 int keyCodeShouldChangeFocus = KeyEvent.KEYCODE_DPAD_UP;
379 int directionShouldChangeFocus = View.FOCUS_UP;
380 int soundEffect = SoundEffectConstants.NAVIGATION_UP;
381
382 switch (getTabWidgetLocation()) {
383 case TABWIDGET_LOCATION_LEFT:
384 keyCodeShouldChangeFocus = KeyEvent.KEYCODE_DPAD_LEFT;
385 directionShouldChangeFocus = View.FOCUS_LEFT;
386 soundEffect = SoundEffectConstants.NAVIGATION_LEFT;
387 break;
388 case TABWIDGET_LOCATION_RIGHT:
389 keyCodeShouldChangeFocus = KeyEvent.KEYCODE_DPAD_RIGHT;
390 directionShouldChangeFocus = View.FOCUS_RIGHT;
391 soundEffect = SoundEffectConstants.NAVIGATION_RIGHT;
392 break;
393 case TABWIDGET_LOCATION_BOTTOM:
394 keyCodeShouldChangeFocus = KeyEvent.KEYCODE_DPAD_DOWN;
395 directionShouldChangeFocus = View.FOCUS_DOWN;
396 soundEffect = SoundEffectConstants.NAVIGATION_DOWN;
397 break;
398 case TABWIDGET_LOCATION_TOP:
399 default:
400 keyCodeShouldChangeFocus = KeyEvent.KEYCODE_DPAD_UP;
401 directionShouldChangeFocus = View.FOCUS_UP;
402 soundEffect = SoundEffectConstants.NAVIGATION_UP;
403 break;
404 }
405 if (event.getKeyCode() == keyCodeShouldChangeFocus
406 && mCurrentView.findFocus().focusSearch(directionShouldChangeFocus) == null) {
407 mTabWidget.getChildTabViewAt(mCurrentTab).requestFocus();
408 playSoundEffect(soundEffect);
409 return true;
410 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800411 }
Evan Millar3730bb12009-08-21 13:58:41 -0700412 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800413 }
414
415
416 @Override
417 public void dispatchWindowFocusChanged(boolean hasFocus) {
Bjorn Bringertacdef592009-12-10 15:58:49 +0000418 if (mCurrentView != null){
419 mCurrentView.dispatchWindowFocusChanged(hasFocus);
420 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800421 }
422
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800423 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800424 public CharSequence getAccessibilityClassName() {
425 return TabHost.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800426 }
427
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800428 public void setCurrentTab(int index) {
429 if (index < 0 || index >= mTabSpecs.size()) {
430 return;
431 }
432
433 if (index == mCurrentTab) {
434 return;
435 }
436
437 // notify old tab content
438 if (mCurrentTab != -1) {
439 mTabSpecs.get(mCurrentTab).mContentStrategy.tabClosed();
440 }
441
442 mCurrentTab = index;
443 final TabHost.TabSpec spec = mTabSpecs.get(index);
444
445 // Call the tab widget's focusCurrentTab(), instead of just
446 // selecting the tab.
447 mTabWidget.focusCurrentTab(mCurrentTab);
Evan Millar3730bb12009-08-21 13:58:41 -0700448
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449 // tab content
450 mCurrentView = spec.mContentStrategy.getContentView();
451
452 if (mCurrentView.getParent() == null) {
453 mTabContent
454 .addView(
455 mCurrentView,
456 new ViewGroup.LayoutParams(
Romain Guy980a9382010-01-08 15:06:28 -0800457 ViewGroup.LayoutParams.MATCH_PARENT,
458 ViewGroup.LayoutParams.MATCH_PARENT));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800459 }
460
461 if (!mTabWidget.hasFocus()) {
462 // if the tab widget didn't take focus (likely because we're in touch mode)
463 // give the current tab content view a shot
464 mCurrentView.requestFocus();
465 }
466
467 //mTabContent.requestFocus(View.FOCUS_FORWARD);
468 invokeOnTabChangeListener();
469 }
470
471 /**
472 * Register a callback to be invoked when the selected state of any of the items
473 * in this list changes
474 * @param l
475 * The callback that will run
476 */
477 public void setOnTabChangedListener(OnTabChangeListener l) {
478 mOnTabChangeListener = l;
479 }
480
481 private void invokeOnTabChangeListener() {
482 if (mOnTabChangeListener != null) {
483 mOnTabChangeListener.onTabChanged(getCurrentTabTag());
484 }
485 }
486
487 /**
488 * Interface definition for a callback to be invoked when tab changed
489 */
490 public interface OnTabChangeListener {
491 void onTabChanged(String tabId);
492 }
493
494
495 /**
496 * Makes the content of a tab when it is selected. Use this if your tab
497 * content needs to be created on demand, i.e. you are not showing an
498 * existing view or starting an activity.
499 */
500 public interface TabContentFactory {
501 /**
502 * Callback to make the tab contents
Evan Millar3730bb12009-08-21 13:58:41 -0700503 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800504 * @param tag
505 * Which tab was selected.
Jack Veenstra982be3b2009-05-27 15:07:59 -0700506 * @return The view to display the contents of the selected tab.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800507 */
508 View createTabContent(String tag);
509 }
510
511
512 /**
Jack Veenstra982be3b2009-05-27 15:07:59 -0700513 * 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 -0800514 * track of it. This builder helps choose among these options.
515 *
516 * For the tab indicator, your choices are:
517 * 1) set a label
518 * 2) set a label and an icon
519 *
520 * For the tab content, your choices are:
521 * 1) the id of a {@link View}
522 * 2) a {@link TabContentFactory} that creates the {@link View} content.
523 * 3) an {@link Intent} that launches an {@link android.app.Activity}.
524 */
525 public class TabSpec {
526
Alan Viverette47565832016-07-21 14:47:22 -0400527 private final @NonNull String mTag;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800528
Charles Chen5c0943d2019-07-30 17:21:47 +0800529 @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q,
530 publicAlternatives = "Use {@code androidx.viewpager.widget.ViewPager} and "
531 + "{@code com.google.android.material.tabs.TabLayout} instead.\n"
532 + "See <a href=\"{@docRoot}guide/navigation/navigation-swipe-view"
533 + "\">TabLayout and ViewPager</a>")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800534 private IndicatorStrategy mIndicatorStrategy;
Charles Chen5c0943d2019-07-30 17:21:47 +0800535 @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q,
536 publicAlternatives = "Use {@code androidx.viewpager.widget.ViewPager} and "
537 + "{@code com.google.android.material.tabs.TabLayout} instead.\n"
538 + "See <a href=\"{@docRoot}guide/navigation/navigation-swipe-view"
539 + "\">TabLayout and ViewPager</a>")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800540 private ContentStrategy mContentStrategy;
541
Alan Viverette47565832016-07-21 14:47:22 -0400542 /**
543 * Constructs a new tab specification with the specified tag.
544 *
545 * @param tag the tag for the tag specification, must be non-null
546 */
547 private TabSpec(@NonNull String tag) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800548 mTag = tag;
549 }
550
551 /**
552 * Specify a label as the tab indicator.
553 */
554 public TabSpec setIndicator(CharSequence label) {
555 mIndicatorStrategy = new LabelIndicatorStrategy(label);
556 return this;
557 }
558
559 /**
560 * Specify a label and icon as the tab indicator.
561 */
562 public TabSpec setIndicator(CharSequence label, Drawable icon) {
563 mIndicatorStrategy = new LabelAndIconIndicatorStrategy(label, icon);
564 return this;
565 }
566
567 /**
Jack Veenstra53175142009-06-01 21:27:01 -0700568 * Specify a view as the tab indicator.
569 */
570 public TabSpec setIndicator(View view) {
571 mIndicatorStrategy = new ViewIndicatorStrategy(view);
572 return this;
573 }
574
575 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800576 * Specify the id of the view that should be used as the content
577 * of the tab.
578 */
579 public TabSpec setContent(int viewId) {
580 mContentStrategy = new ViewIdContentStrategy(viewId);
581 return this;
582 }
583
584 /**
585 * Specify a {@link android.widget.TabHost.TabContentFactory} to use to
586 * create the content of the tab.
587 */
588 public TabSpec setContent(TabContentFactory contentFactory) {
589 mContentStrategy = new FactoryContentStrategy(mTag, contentFactory);
590 return this;
591 }
592
593 /**
594 * Specify an intent to use to launch an activity as the tab content.
595 */
596 public TabSpec setContent(Intent intent) {
597 mContentStrategy = new IntentContentStrategy(mTag, intent);
598 return this;
599 }
600
Alan Viverette47565832016-07-21 14:47:22 -0400601 /**
602 * Returns the tag for this tab specification.
603 *
604 * @return the tag for this tab specification
605 */
606 @NonNull
Jack Veenstra53175142009-06-01 21:27:01 -0700607 public String getTag() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800608 return mTag;
609 }
610 }
611
612 /**
613 * Specifies what you do to create a tab indicator.
614 */
615 private static interface IndicatorStrategy {
616
617 /**
618 * Return the view for the indicator.
619 */
620 View createIndicatorView();
621 }
622
623 /**
624 * Specifies what you do to manage the tab content.
625 */
626 private static interface ContentStrategy {
627
628 /**
629 * Return the content view. The view should may be cached locally.
630 */
631 View getContentView();
632
633 /**
634 * Perhaps do something when the tab associated with this content has
635 * been closed (i.e make it invisible, or remove it).
636 */
637 void tabClosed();
638 }
639
640 /**
641 * How to create a tab indicator that just has a label.
642 */
643 private class LabelIndicatorStrategy implements IndicatorStrategy {
644
645 private final CharSequence mLabel;
646
647 private LabelIndicatorStrategy(CharSequence label) {
648 mLabel = label;
649 }
650
651 public View createIndicatorView() {
Mike Cleron76097642009-09-25 17:53:56 -0700652 final Context context = getContext();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800653 LayoutInflater inflater =
Mike Cleron76097642009-09-25 17:53:56 -0700654 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Gilles Debunne44c14732010-10-19 11:56:59 -0700655 View tabIndicator = inflater.inflate(mTabLayoutId,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800656 mTabWidget, // tab widget is the parent
657 false); // no inflate params
658
Alan Viverette8e1a7292017-02-27 10:57:58 -0500659 final TextView tv = tabIndicator.findViewById(R.id.title);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800660 tv.setText(mLabel);
661
Mike Cleron76097642009-09-25 17:53:56 -0700662 if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
663 // Donut apps get old color scheme
664 tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);
Alan Viverette4a357cd2015-03-18 18:37:18 -0700665 tv.setTextColor(context.getColorStateList(R.color.tab_indicator_text_v4));
Mike Cleron76097642009-09-25 17:53:56 -0700666 }
Gilles Debunne44c14732010-10-19 11:56:59 -0700667
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800668 return tabIndicator;
669 }
670 }
671
672 /**
673 * How we create a tab indicator that has a label and an icon
674 */
675 private class LabelAndIconIndicatorStrategy implements IndicatorStrategy {
676
677 private final CharSequence mLabel;
678 private final Drawable mIcon;
679
680 private LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon) {
681 mLabel = label;
682 mIcon = icon;
683 }
684
685 public View createIndicatorView() {
Mike Cleron76097642009-09-25 17:53:56 -0700686 final Context context = getContext();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800687 LayoutInflater inflater =
Mike Cleron76097642009-09-25 17:53:56 -0700688 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Gilles Debunne44c14732010-10-19 11:56:59 -0700689 View tabIndicator = inflater.inflate(mTabLayoutId,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800690 mTabWidget, // tab widget is the parent
691 false); // no inflate params
692
Alan Viverette8e1a7292017-02-27 10:57:58 -0500693 final TextView tv = tabIndicator.findViewById(R.id.title);
694 final ImageView iconView = tabIndicator.findViewById(R.id.icon);
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700695
696 // when icon is gone by default, we're in exclusive mode
697 final boolean exclusive = iconView.getVisibility() == View.GONE;
698 final boolean bindIcon = !exclusive || TextUtils.isEmpty(mLabel);
699
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800700 tv.setText(mLabel);
701
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700702 if (bindIcon && mIcon != null) {
Gilles Debunne44c14732010-10-19 11:56:59 -0700703 iconView.setImageDrawable(mIcon);
704 iconView.setVisibility(VISIBLE);
705 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800706
Mike Cleron76097642009-09-25 17:53:56 -0700707 if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
708 // Donut apps get old color scheme
709 tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);
Alan Viverette4a357cd2015-03-18 18:37:18 -0700710 tv.setTextColor(context.getColorStateList(R.color.tab_indicator_text_v4));
Mike Cleron76097642009-09-25 17:53:56 -0700711 }
Gilles Debunne44c14732010-10-19 11:56:59 -0700712
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800713 return tabIndicator;
714 }
715 }
716
717 /**
Jack Veenstra53175142009-06-01 21:27:01 -0700718 * How to create a tab indicator by specifying a view.
719 */
720 private class ViewIndicatorStrategy implements IndicatorStrategy {
721
722 private final View mView;
723
724 private ViewIndicatorStrategy(View view) {
725 mView = view;
726 }
727
728 public View createIndicatorView() {
729 return mView;
730 }
731 }
732
733 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800734 * How to create the tab content via a view id.
735 */
736 private class ViewIdContentStrategy implements ContentStrategy {
737
738 private final View mView;
739
740 private ViewIdContentStrategy(int viewId) {
741 mView = mTabContent.findViewById(viewId);
742 if (mView != null) {
743 mView.setVisibility(View.GONE);
744 } else {
745 throw new RuntimeException("Could not create tab content because " +
746 "could not find view with id " + viewId);
747 }
748 }
749
750 public View getContentView() {
751 mView.setVisibility(View.VISIBLE);
752 return mView;
753 }
754
755 public void tabClosed() {
756 mView.setVisibility(View.GONE);
757 }
758 }
759
760 /**
761 * How tab content is managed using {@link TabContentFactory}.
762 */
763 private class FactoryContentStrategy implements ContentStrategy {
764 private View mTabContent;
765 private final CharSequence mTag;
766 private TabContentFactory mFactory;
767
768 public FactoryContentStrategy(CharSequence tag, TabContentFactory factory) {
769 mTag = tag;
770 mFactory = factory;
771 }
772
773 public View getContentView() {
774 if (mTabContent == null) {
775 mTabContent = mFactory.createTabContent(mTag.toString());
776 }
777 mTabContent.setVisibility(View.VISIBLE);
778 return mTabContent;
779 }
780
781 public void tabClosed() {
Gilles Debunnece2baf02010-02-10 15:19:14 -0800782 mTabContent.setVisibility(View.GONE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800783 }
784 }
785
786 /**
787 * How tab content is managed via an {@link Intent}: the content view is the
788 * decorview of the launched activity.
789 */
790 private class IntentContentStrategy implements ContentStrategy {
791
792 private final String mTag;
793 private final Intent mIntent;
794
795 private View mLaunchedView;
796
797 private IntentContentStrategy(String tag, Intent intent) {
798 mTag = tag;
799 mIntent = intent;
800 }
801
Charles Chen5c0943d2019-07-30 17:21:47 +0800802 @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q,
803 publicAlternatives = "Use {@code androidx.viewpager.widget.ViewPager} and "
804 + "{@code com.google.android.material.tabs.TabLayout} instead.\n"
805 + "See <a href=\"{@docRoot}guide/navigation/navigation-swipe-view"
806 + "\">TabLayout and ViewPager</a>")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800807 public View getContentView() {
808 if (mLocalActivityManager == null) {
809 throw new IllegalStateException("Did you forget to call 'public void setup(LocalActivityManager activityGroup)'?");
810 }
811 final Window w = mLocalActivityManager.startActivity(
812 mTag, mIntent);
813 final View wd = w != null ? w.getDecorView() : null;
814 if (mLaunchedView != wd && mLaunchedView != null) {
815 if (mLaunchedView.getParent() != null) {
816 mTabContent.removeView(mLaunchedView);
817 }
818 }
819 mLaunchedView = wd;
Evan Millar3730bb12009-08-21 13:58:41 -0700820
Jack Veenstra982be3b2009-05-27 15:07:59 -0700821 // XXX Set FOCUS_AFTER_DESCENDANTS on embedded activities for now so they can get
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800822 // focus if none of their children have it. They need focus to be able to
823 // display menu items.
824 //
825 // Replace this with something better when Bug 628886 is fixed...
826 //
827 if (mLaunchedView != null) {
828 mLaunchedView.setVisibility(View.VISIBLE);
829 mLaunchedView.setFocusableInTouchMode(true);
830 ((ViewGroup) mLaunchedView).setDescendantFocusability(
831 FOCUS_AFTER_DESCENDANTS);
832 }
833 return mLaunchedView;
834 }
835
Charles Chen5c0943d2019-07-30 17:21:47 +0800836 @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q,
837 publicAlternatives = "Use {@code androidx.viewpager.widget.ViewPager} and "
838 + "{@code com.google.android.material.tabs.TabLayout} instead.\n"
839 + "See <a href=\"{@docRoot}guide/navigation/navigation-swipe-view"
840 + "\">TabLayout and ViewPager</a>")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800841 public void tabClosed() {
842 if (mLaunchedView != null) {
843 mLaunchedView.setVisibility(View.GONE);
844 }
845 }
846 }
847
848}