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