• 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.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.LocalActivityManager;
22 import android.compat.annotation.UnsupportedAppUsage;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.res.TypedArray;
26 import android.graphics.drawable.Drawable;
27 import android.os.Build;
28 import android.text.TextUtils;
29 import android.util.AttributeSet;
30 import android.view.KeyEvent;
31 import android.view.LayoutInflater;
32 import android.view.SoundEffectConstants;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.view.ViewTreeObserver;
36 import android.view.Window;
37 
38 import com.android.internal.R;
39 
40 import java.util.ArrayList;
41 import 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.
48  *
49  * @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.
52  */
53 @Deprecated
54 public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchModeChangeListener {
55 
56     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;
60     private TabWidget mTabWidget;
61     private FrameLayout mTabContent;
62     @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>")
67     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      */
72     @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>")
77     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;
84     @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>")
89     private OnTabChangeListener mOnTabChangeListener;
90     private OnKeyListener mTabKeyListener;
91 
92     private int mTabLayoutId;
93 
TabHost(Context context)94     public TabHost(Context context) {
95         super(context);
96         initTabHost();
97     }
98 
TabHost(Context context, AttributeSet attrs)99     public TabHost(Context context, AttributeSet attrs) {
100         this(context, attrs, com.android.internal.R.attr.tabWidgetStyle);
101     }
102 
TabHost(Context context, AttributeSet attrs, int defStyleAttr)103     public TabHost(Context context, AttributeSet attrs, int defStyleAttr) {
104         this(context, attrs, defStyleAttr, 0);
105     }
106 
TabHost(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)107     public TabHost(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
108         super(context, attrs);
109 
110         final TypedArray a = context.obtainStyledAttributes(
111                 attrs, com.android.internal.R.styleable.TabWidget, defStyleAttr, defStyleRes);
112         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TabWidget,
113                 attrs, a, defStyleAttr, defStyleRes);
114 
115         mTabLayoutId = a.getResourceId(R.styleable.TabWidget_tabLayout, 0);
116         a.recycle();
117 
118         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 
124         initTabHost();
125     }
126 
initTabHost()127     private void initTabHost() {
128         setFocusableInTouchMode(true);
129         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
130 
131         mCurrentTab = -1;
132         mCurrentView = null;
133     }
134 
135     /**
136      * Creates a new {@link TabSpec} associated with this tab host.
137      *
138      * @param tag tag for the tab specification, must be non-null
139      * @throws IllegalArgumentException If the passed tag is null
140      */
141     @NonNull
newTabSpec(@onNull String tag)142     public TabSpec newTabSpec(@NonNull String tag) {
143         if (tag == null) {
144             throw new IllegalArgumentException("tag must be non-null");
145         }
146         return new TabSpec(tag);
147     }
148 
149 
150 
151     /**
152       * <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}.
155       * Example:</p>
156 <pre>mTabHost = (TabHost)findViewById(R.id.tabhost);
157 mTabHost.setup();
158 mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
159       */
setup()160     public void setup() {
161         mTabWidget = findViewById(com.android.internal.R.id.tabs);
162         if (mTabWidget == null) {
163             throw new RuntimeException(
164                     "Your TabHost must have a TabWidget whose id attribute is 'android.R.id.tabs'");
165         }
166 
167         // KeyListener to attach to all tabs. Detects non-navigation keys
168         // and relays them to the tab content.
169         mTabKeyListener = new OnKeyListener() {
170             public boolean onKey(View v, int keyCode, KeyEvent event) {
171                 if (KeyEvent.isModifierKey(keyCode)) {
172                     return false;
173                 }
174                 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:
180                     case KeyEvent.KEYCODE_TAB:
181                     case KeyEvent.KEYCODE_SPACE:
182                     case KeyEvent.KEYCODE_ENTER:
183                         return false;
184 
185                 }
186                 mTabContent.requestFocus(View.FOCUS_FORWARD);
187                 return mTabContent.dispatchKeyEvent(event);
188             }
189 
190         };
191 
192         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 
201         mTabContent = findViewById(com.android.internal.R.id.tabcontent);
202         if (mTabContent == null) {
203             throw new RuntimeException(
204                     "Your TabHost must have a FrameLayout whose id attribute is "
205                             + "'android.R.id.tabcontent'");
206         }
207     }
208 
209     /** @hide */
210     @Override
sendAccessibilityEventInternal(int eventType)211     public void sendAccessibilityEventInternal(int eventType) {
212         /* avoid super class behavior - TabWidget sends the right events */
213     }
214 
215     /**
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      */
setup(LocalActivityManager activityGroup)222     public void setup(LocalActivityManager activityGroup) {
223         setup();
224         mLocalActivityManager = activityGroup;
225     }
226 
227     @Override
onTouchModeChanged(boolean isInTouchMode)228     public void onTouchModeChanged(boolean isInTouchMode) {
229         // No longer used, but kept to maintain API compatibility.
230     }
231 
232     /**
233      * Add a tab.
234      * @param tabSpec Specifies how to create the indicator and content.
235      * @throws IllegalArgumentException If the passed tab spec has null indicator strategy and / or
236      *      null content strategy.
237      */
addTab(TabSpec tabSpec)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);
249 
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) {
253             mTabWidget.setStripEnabled(false);
254         }
255 
256         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      */
clearAllTabs()268     public void clearAllTabs() {
269         mTabWidget.removeAllViews();
270         initTabHost();
271         mTabContent.removeAllViews();
272         mTabSpecs.clear();
273         requestLayout();
274         invalidate();
275     }
276 
getTabWidget()277     public TabWidget getTabWidget() {
278         return mTabWidget;
279     }
280 
281     /**
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
getCurrentTab()287     public int getCurrentTab() {
288         return mCurrentTab;
289     }
290 
291     /**
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
getCurrentTabTag()298     public String getCurrentTabTag() {
299         if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
300             return mTabSpecs.get(mCurrentTab).getTag();
301         }
302         return null;
303     }
304 
305     /**
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
getCurrentTabView()312     public View getCurrentTabView() {
313         if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
314             return mTabWidget.getChildTabViewAt(mCurrentTab);
315         }
316         return null;
317     }
318 
getCurrentView()319     public View getCurrentView() {
320         return mCurrentView;
321     }
322 
323     /**
324      * Sets the current tab based on its tag.
325      *
326      * @param tag the tag for the tab to set as current
327      */
setCurrentTabByTag(String tag)328     public void setCurrentTabByTag(String tag) {
329         for (int i = 0, count = mTabSpecs.size(); i < count; i++) {
330             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      */
getTabContentView()340     public FrameLayout getTabContentView() {
341         return mTabContent;
342     }
343 
344     /**
345      * Get the location of the TabWidget.
346      *
347      * @return The TabWidget location.
348      */
getTabWidgetLocation()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 
366     @Override
dispatchKeyEvent(KeyEvent event)367     public boolean dispatchKeyEvent(KeyEvent event) {
368         final boolean handled = super.dispatchKeyEvent(event);
369 
370         // 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
373         if (!handled
374                 && (event.getAction() == KeyEvent.ACTION_DOWN)
375                 && (mCurrentView != null)
376                 && (mCurrentView.isRootNamespace())
377                 && (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             }
411         }
412         return handled;
413     }
414 
415 
416     @Override
dispatchWindowFocusChanged(boolean hasFocus)417     public void dispatchWindowFocusChanged(boolean hasFocus) {
418         if (mCurrentView != null){
419             mCurrentView.dispatchWindowFocusChanged(hasFocus);
420         }
421     }
422 
423     @Override
getAccessibilityClassName()424     public CharSequence getAccessibilityClassName() {
425         return TabHost.class.getName();
426     }
427 
setCurrentTab(int index)428     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);
448 
449         // tab content
450         mCurrentView = spec.mContentStrategy.getContentView();
451 
452         if (mCurrentView.getParent() == null) {
453             mTabContent
454                     .addView(
455                             mCurrentView,
456                             new ViewGroup.LayoutParams(
457                                     ViewGroup.LayoutParams.MATCH_PARENT,
458                                     ViewGroup.LayoutParams.MATCH_PARENT));
459         }
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      */
setOnTabChangedListener(OnTabChangeListener l)477     public void setOnTabChangedListener(OnTabChangeListener l) {
478         mOnTabChangeListener = l;
479     }
480 
invokeOnTabChangeListener()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 {
onTabChanged(String tabId)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
503          *
504          * @param tag
505          *            Which tab was selected.
506          * @return The view to display the contents of the selected tab.
507          */
createTabContent(String tag)508         View createTabContent(String tag);
509     }
510 
511 
512     /**
513      * A tab has a tab indicator, content, and a tag that is used to keep
514      * 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 
527         private final @NonNull String mTag;
528 
529         @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>")
534         private IndicatorStrategy mIndicatorStrategy;
535         @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>")
540         private ContentStrategy mContentStrategy;
541 
542         /**
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          */
TabSpec(@onNull String tag)547         private TabSpec(@NonNull String tag) {
548             mTag = tag;
549         }
550 
551         /**
552          * Specify a label as the tab indicator.
553          */
setIndicator(CharSequence label)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          */
setIndicator(CharSequence label, Drawable icon)562         public TabSpec setIndicator(CharSequence label, Drawable icon) {
563             mIndicatorStrategy = new LabelAndIconIndicatorStrategy(label, icon);
564             return this;
565         }
566 
567         /**
568          * Specify a view as the tab indicator.
569          */
setIndicator(View view)570         public TabSpec setIndicator(View view) {
571             mIndicatorStrategy = new ViewIndicatorStrategy(view);
572             return this;
573         }
574 
575         /**
576          * Specify the id of the view that should be used as the content
577          * of the tab.
578          */
setContent(int viewId)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          */
setContent(TabContentFactory contentFactory)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          */
setContent(Intent intent)596         public TabSpec setContent(Intent intent) {
597             mContentStrategy = new IntentContentStrategy(mTag, intent);
598             return this;
599         }
600 
601         /**
602          * Returns the tag for this tab specification.
603          *
604          * @return the tag for this tab specification
605          */
606         @NonNull
getTag()607         public String getTag() {
608             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          */
createIndicatorView()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          */
getContentView()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          */
tabClosed()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 
LabelIndicatorStrategy(CharSequence label)647         private LabelIndicatorStrategy(CharSequence label) {
648             mLabel = label;
649         }
650 
createIndicatorView()651         public View createIndicatorView() {
652             final Context context = getContext();
653             LayoutInflater inflater =
654                     (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
655             View tabIndicator = inflater.inflate(mTabLayoutId,
656                     mTabWidget, // tab widget is the parent
657                     false); // no inflate params
658 
659             final TextView tv = tabIndicator.findViewById(R.id.title);
660             tv.setText(mLabel);
661 
662             if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
663                 // Donut apps get old color scheme
664                 tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);
665                 tv.setTextColor(context.getColorStateList(R.color.tab_indicator_text_v4));
666             }
667 
668             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 
LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon)680         private LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon) {
681             mLabel = label;
682             mIcon = icon;
683         }
684 
createIndicatorView()685         public View createIndicatorView() {
686             final Context context = getContext();
687             LayoutInflater inflater =
688                     (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
689             View tabIndicator = inflater.inflate(mTabLayoutId,
690                     mTabWidget, // tab widget is the parent
691                     false); // no inflate params
692 
693             final TextView tv = tabIndicator.findViewById(R.id.title);
694             final ImageView iconView = tabIndicator.findViewById(R.id.icon);
695 
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 
700             tv.setText(mLabel);
701 
702             if (bindIcon && mIcon != null) {
703                 iconView.setImageDrawable(mIcon);
704                 iconView.setVisibility(VISIBLE);
705             }
706 
707             if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
708                 // Donut apps get old color scheme
709                 tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);
710                 tv.setTextColor(context.getColorStateList(R.color.tab_indicator_text_v4));
711             }
712 
713             return tabIndicator;
714         }
715     }
716 
717     /**
718      * How to create a tab indicator by specifying a view.
719      */
720     private class ViewIndicatorStrategy implements IndicatorStrategy {
721 
722         private final View mView;
723 
ViewIndicatorStrategy(View view)724         private ViewIndicatorStrategy(View view) {
725             mView = view;
726         }
727 
createIndicatorView()728         public View createIndicatorView() {
729             return mView;
730         }
731     }
732 
733     /**
734      * How to create the tab content via a view id.
735      */
736     private class ViewIdContentStrategy implements ContentStrategy {
737 
738         private final View mView;
739 
ViewIdContentStrategy(int viewId)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 
getContentView()750         public View getContentView() {
751             mView.setVisibility(View.VISIBLE);
752             return mView;
753         }
754 
tabClosed()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 
FactoryContentStrategy(CharSequence tag, TabContentFactory factory)768         public FactoryContentStrategy(CharSequence tag, TabContentFactory factory) {
769             mTag = tag;
770             mFactory = factory;
771         }
772 
getContentView()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 
tabClosed()781         public void tabClosed() {
782             mTabContent.setVisibility(View.GONE);
783         }
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 
IntentContentStrategy(String tag, Intent intent)797         private IntentContentStrategy(String tag, Intent intent) {
798             mTag = tag;
799             mIntent = intent;
800         }
801 
802         @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>")
getContentView()807         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;
820 
821             // XXX Set FOCUS_AFTER_DESCENDANTS on embedded activities for now so they can get
822             // 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 
836         @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>")
tabClosed()841         public void tabClosed() {
842             if (mLaunchedView != null) {
843                 mLaunchedView.setVisibility(View.GONE);
844             }
845         }
846     }
847 
848 }
849