blob: 36abf3a327875b938bd4a8d0e9aa3f7bb6f3eb1b [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
Tor Norbye7b9c9122013-05-30 16:48:33 -070019import android.annotation.DrawableRes;
Alan Viverette7eb09d12015-08-14 11:36:01 -040020import android.annotation.Nullable;
Artur Satayeved5a6ae2019-12-10 17:47:54 +000021import android.compat.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.content.Context;
23import android.content.res.TypedArray;
24import android.graphics.Canvas;
25import android.graphics.Rect;
26import android.graphics.drawable.Drawable;
Mike Cleron76097642009-09-25 17:53:56 -070027import android.os.Build;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.util.AttributeSet;
Aurimas Liutikas99441c52016-10-11 16:48:32 -070029import android.view.MotionEvent;
30import android.view.PointerIcon;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.view.View;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.view.View.OnFocusChangeListener;
Gilles Debunne97dfd342010-10-20 16:31:15 -070033import android.view.ViewGroup;
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -070034import android.view.accessibility.AccessibilityEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035
Aurimas Liutikas99441c52016-10-11 16:48:32 -070036import com.android.internal.R;
37
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038/**
Evan Millar3730bb12009-08-21 13:58:41 -070039 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040 * Displays a list of tab labels representing each page in the parent's tab
Alan Viverette7eb09d12015-08-14 11:36:01 -040041 * collection.
42 * <p>
43 * The container object for this widget is {@link android.widget.TabHost TabHost}.
44 * When the user selects a tab, this object sends a message to the parent
45 * container, TabHost, to tell it to switch the displayed page. You typically
46 * won't use many methods directly on this object. The container TabHost is
47 * used to add labels, add the callback handler, and manage callbacks. You
48 * might call this object to iterate the list of tabs, or to tweak the layout
49 * of the tab list, but most methods should be called on the containing TabHost
50 * object.
51 *
Romain Guy7883c972010-03-01 16:39:17 -080052 * @attr ref android.R.styleable#TabWidget_divider
Romain Guy6b1e6962010-03-29 14:38:41 -070053 * @attr ref android.R.styleable#TabWidget_tabStripEnabled
54 * @attr ref android.R.styleable#TabWidget_tabStripLeft
55 * @attr ref android.R.styleable#TabWidget_tabStripRight
Charles Chen28a0e472019-07-24 19:45:16 +080056 *
57 * @deprecated new applications should use fragment APIs instead of this class:
58 * Use <a href="{@docRoot}guide/navigation/navigation-swipe-view">TabLayout and ViewPager</a>
59 * instead.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060 */
Charles Chen28a0e472019-07-24 19:45:16 +080061@Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062public class TabWidget extends LinearLayout implements OnFocusChangeListener {
Alan Viverette7eb09d12015-08-14 11:36:01 -040063 private final Rect mBounds = new Rect();
64
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065 private OnTabSelectionChanged mSelectionChangedListener;
Romain Guy61c9d4b2010-03-01 14:12:10 -080066
Gilles Debunne97dfd342010-10-20 16:31:15 -070067 // This value will be set to 0 as soon as the first tab is added to TabHost.
Charles Chen5c0943d2019-07-30 17:21:47 +080068 @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q,
69 publicAlternatives = "Use {@code androidx.viewpager.widget.ViewPager} and "
70 + "{@code com.google.android.material.tabs.TabLayout} instead.\n"
71 + "See <a href=\"{@docRoot}guide/navigation/navigation-swipe-view"
72 + "\">TabLayout and ViewPager</a>")
Gilles Debunne97dfd342010-10-20 16:31:15 -070073 private int mSelectedTab = -1;
Romain Guy61c9d4b2010-03-01 14:12:10 -080074
Alan Viverette00e94012017-09-15 16:04:48 -040075 @Nullable
Romain Guy65fe2c082010-03-29 12:27:30 -070076 private Drawable mLeftStrip;
Alan Viverette00e94012017-09-15 16:04:48 -040077
78 @Nullable
Romain Guy65fe2c082010-03-29 12:27:30 -070079 private Drawable mRightStrip;
Romain Guy61c9d4b2010-03-01 14:12:10 -080080
Charles Chen5c0943d2019-07-30 17:21:47 +080081 @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q,
82 publicAlternatives = "Use {@code androidx.viewpager.widget.ViewPager} and "
83 + "{@code com.google.android.material.tabs.TabLayout} instead.\n"
84 + "See <a href=\"{@docRoot}guide/navigation/navigation-swipe-view"
85 + "\">TabLayout and ViewPager</a>")
Jack Veenstra53175142009-06-01 21:27:01 -070086 private boolean mDrawBottomStrips = true;
Romain Guy61c9d4b2010-03-01 14:12:10 -080087 private boolean mStripMoved;
88
Alan Viverette7eb09d12015-08-14 11:36:01 -040089 // When positive, the widths and heights of tabs will be imposed so that
90 // they fit in parent.
Gilles Debunnec4d3f752011-01-25 20:11:45 -080091 private int mImposedTabsHeight = -1;
92 private int[] mImposedTabWidths;
93
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080094 public TabWidget(Context context) {
95 this(context, null);
96 }
97
98 public TabWidget(Context context, AttributeSet attrs) {
99 this(context, attrs, com.android.internal.R.attr.tabWidgetStyle);
100 }
101
Alan Viverette617feb92013-09-09 18:09:13 -0700102 public TabWidget(Context context, AttributeSet attrs, int defStyleAttr) {
103 this(context, attrs, defStyleAttr, 0);
104 }
105
106 public TabWidget(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
107 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700109 final TypedArray a = context.obtainStyledAttributes(
Alan Viverette7eb09d12015-08-14 11:36:01 -0400110 attrs, R.styleable.TabWidget, defStyleAttr, defStyleRes);
Aurimas Liutikasab324cf2019-02-07 16:46:38 -0800111 saveAttributeDataForStyleable(context, R.styleable.TabWidget,
112 attrs, a, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113
Alan Viverette7eb09d12015-08-14 11:36:01 -0400114 mDrawBottomStrips = a.getBoolean(R.styleable.TabWidget_tabStripEnabled, mDrawBottomStrips);
115
116 // Tests the target SDK version, as set in the Manifest. Could not be
117 // set using styles.xml in a values-v? directory which targets the
118 // current platform SDK version instead.
119 final boolean isTargetSdkDonutOrLower =
120 context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT;
121
122 final boolean hasExplicitLeft = a.hasValueOrEmpty(R.styleable.TabWidget_tabStripLeft);
123 if (hasExplicitLeft) {
124 mLeftStrip = a.getDrawable(R.styleable.TabWidget_tabStripLeft);
125 } else if (isTargetSdkDonutOrLower) {
126 mLeftStrip = context.getDrawable(R.drawable.tab_bottom_left_v4);
127 } else {
128 mLeftStrip = context.getDrawable(R.drawable.tab_bottom_left);
129 }
130
131 final boolean hasExplicitRight = a.hasValueOrEmpty(R.styleable.TabWidget_tabStripRight);
132 if (hasExplicitRight) {
133 mRightStrip = a.getDrawable(R.styleable.TabWidget_tabStripRight);
134 } else if (isTargetSdkDonutOrLower) {
135 mRightStrip = context.getDrawable(R.drawable.tab_bottom_right_v4);
136 } else {
137 mRightStrip = context.getDrawable(R.drawable.tab_bottom_right);
138 }
Romain Guy61c9d4b2010-03-01 14:12:10 -0800139
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 a.recycle();
Romain Guy61c9d4b2010-03-01 14:12:10 -0800141
Alan Viverette7eb09d12015-08-14 11:36:01 -0400142 setChildrenDrawingOrderEnabled(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143 }
Evan Millar3730bb12009-08-21 13:58:41 -0700144
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145 @Override
146 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
147 mStripMoved = true;
Alan Viverette7eb09d12015-08-14 11:36:01 -0400148
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800149 super.onSizeChanged(w, h, oldw, oldh);
150 }
151
Evan Millar3730bb12009-08-21 13:58:41 -0700152 @Override
153 protected int getChildDrawingOrder(int childCount, int i) {
Gilles Debunne97dfd342010-10-20 16:31:15 -0700154 if (mSelectedTab == -1) {
Evan Millar3730bb12009-08-21 13:58:41 -0700155 return i;
Gilles Debunne97dfd342010-10-20 16:31:15 -0700156 } else {
157 // Always draw the selected tab last, so that drop shadows are drawn
158 // in the correct z-order.
159 if (i == childCount - 1) {
160 return mSelectedTab;
161 } else if (i >= mSelectedTab) {
162 return i + 1;
163 } else {
164 return i;
165 }
Evan Millar3730bb12009-08-21 13:58:41 -0700166 }
167 }
168
Gilles Debunne6741c942011-01-25 20:11:45 -0800169 @Override
Alan Viverette7eb09d12015-08-14 11:36:01 -0400170 void measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth,
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800171 int heightMeasureSpec, int totalHeight) {
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700172 if (!isMeasureWithLargestChildEnabled() && mImposedTabsHeight >= 0) {
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800173 widthMeasureSpec = MeasureSpec.makeMeasureSpec(
174 totalWidth + mImposedTabWidths[childIndex], MeasureSpec.EXACTLY);
175 heightMeasureSpec = MeasureSpec.makeMeasureSpec(mImposedTabsHeight,
176 MeasureSpec.EXACTLY);
Gilles Debunne6741c942011-01-25 20:11:45 -0800177 }
178
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800179 super.measureChildBeforeLayout(child, childIndex,
180 widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight);
181 }
182
183 @Override
184 void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
Gilles Debunne52a5e6582011-02-28 17:58:35 -0800185 if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) {
186 super.measureHorizontal(widthMeasureSpec, heightMeasureSpec);
187 return;
188 }
189
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800190 // First, measure with no constraint
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -0700191 final int width = MeasureSpec.getSize(widthMeasureSpec);
Adam Powelld5dbf4b2015-06-11 13:19:24 -0700192 final int unspecifiedWidth = MeasureSpec.makeSafeMeasureSpec(width,
193 MeasureSpec.UNSPECIFIED);
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800194 mImposedTabsHeight = -1;
Gilles Debunnecd59feb2011-02-25 14:34:20 -0800195 super.measureHorizontal(unspecifiedWidth, heightMeasureSpec);
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800196
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -0700197 int extraWidth = getMeasuredWidth() - width;
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800198 if (extraWidth > 0) {
199 final int count = getChildCount();
200
201 int childCount = 0;
Gilles Debunne6741c942011-01-25 20:11:45 -0800202 for (int i = 0; i < count; i++) {
203 final View child = getChildAt(i);
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800204 if (child.getVisibility() == GONE) continue;
205 childCount++;
Gilles Debunne6741c942011-01-25 20:11:45 -0800206 }
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800207
208 if (childCount > 0) {
209 if (mImposedTabWidths == null || mImposedTabWidths.length != count) {
210 mImposedTabWidths = new int[count];
211 }
212 for (int i = 0; i < count; i++) {
213 final View child = getChildAt(i);
214 if (child.getVisibility() == GONE) continue;
215 final int childWidth = child.getMeasuredWidth();
216 final int delta = extraWidth / childCount;
217 final int newWidth = Math.max(0, childWidth - delta);
218 mImposedTabWidths[i] = newWidth;
219 // Make sure the extra width is evenly distributed, no int division remainder
220 extraWidth -= childWidth - newWidth; // delta may have been clamped
221 childCount--;
222 mImposedTabsHeight = Math.max(mImposedTabsHeight, child.getMeasuredHeight());
223 }
224 }
225 }
226
Alan Viverette7eb09d12015-08-14 11:36:01 -0400227 // Measure again, this time with imposed tab widths and respecting
228 // initial spec request.
Gilles Debunne52a5e6582011-02-28 17:58:35 -0800229 super.measureHorizontal(widthMeasureSpec, heightMeasureSpec);
Gilles Debunne6741c942011-01-25 20:11:45 -0800230 }
231
232 /**
Jack Veenstra53175142009-06-01 21:27:01 -0700233 * Returns the tab indicator view at the given index.
234 *
235 * @param index the zero-based index of the tab indicator view to return
236 * @return the tab indicator view at the given index
237 */
238 public View getChildTabViewAt(int index) {
Jack Veenstra53175142009-06-01 21:27:01 -0700239 return getChildAt(index);
240 }
241
242 /**
243 * Returns the number of tab indicator views.
Alan Viverette7eb09d12015-08-14 11:36:01 -0400244 *
245 * @return the number of tab indicator views
Jack Veenstra53175142009-06-01 21:27:01 -0700246 */
247 public int getTabCount() {
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700248 return getChildCount();
Jack Veenstra53175142009-06-01 21:27:01 -0700249 }
250
251 /**
252 * Sets the drawable to use as a divider between the tab indicators.
Alan Viverette7eb09d12015-08-14 11:36:01 -0400253 *
Jack Veenstra53175142009-06-01 21:27:01 -0700254 * @param drawable the divider drawable
Alan Viverette7eb09d12015-08-14 11:36:01 -0400255 * @attr ref android.R.styleable#TabWidget_divider
Jack Veenstra53175142009-06-01 21:27:01 -0700256 */
Gilles Debunne6741c942011-01-25 20:11:45 -0800257 @Override
Alan Viverette7eb09d12015-08-14 11:36:01 -0400258 public void setDividerDrawable(@Nullable Drawable drawable) {
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700259 super.setDividerDrawable(drawable);
Jack Veenstra53175142009-06-01 21:27:01 -0700260 }
261
262 /**
263 * Sets the drawable to use as a divider between the tab indicators.
Alan Viverette7eb09d12015-08-14 11:36:01 -0400264 *
265 * @param resId the resource identifier of the drawable to use as a divider
266 * @attr ref android.R.styleable#TabWidget_divider
Jack Veenstra53175142009-06-01 21:27:01 -0700267 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700268 public void setDividerDrawable(@DrawableRes int resId) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800269 setDividerDrawable(mContext.getDrawable(resId));
Jack Veenstra53175142009-06-01 21:27:01 -0700270 }
Alan Viverette7eb09d12015-08-14 11:36:01 -0400271
Romain Guy61c9d4b2010-03-01 14:12:10 -0800272 /**
Alan Viverette7eb09d12015-08-14 11:36:01 -0400273 * Sets the drawable to use as the left part of the strip below the tab
274 * indicators.
275 *
Romain Guy61c9d4b2010-03-01 14:12:10 -0800276 * @param drawable the left strip drawable
Alan Viveretted78036b2015-08-14 13:53:56 -0400277 * @see #getLeftStripDrawable()
Alan Viverette7eb09d12015-08-14 11:36:01 -0400278 * @attr ref android.R.styleable#TabWidget_tabStripLeft
Romain Guy61c9d4b2010-03-01 14:12:10 -0800279 */
Alan Viverette7eb09d12015-08-14 11:36:01 -0400280 public void setLeftStripDrawable(@Nullable Drawable drawable) {
Romain Guy65fe2c082010-03-29 12:27:30 -0700281 mLeftStrip = drawable;
Romain Guy42c79882010-03-01 17:20:57 -0800282 requestLayout();
283 invalidate();
Romain Guy61c9d4b2010-03-01 14:12:10 -0800284 }
Jack Veenstra53175142009-06-01 21:27:01 -0700285
286 /**
Alan Viverette7eb09d12015-08-14 11:36:01 -0400287 * Sets the drawable to use as the left part of the strip below the tab
288 * indicators.
289 *
290 * @param resId the resource identifier of the drawable to use as the left
291 * strip drawable
Alan Viveretted78036b2015-08-14 13:53:56 -0400292 * @see #getLeftStripDrawable()
Alan Viverette7eb09d12015-08-14 11:36:01 -0400293 * @attr ref android.R.styleable#TabWidget_tabStripLeft
Romain Guy61c9d4b2010-03-01 14:12:10 -0800294 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700295 public void setLeftStripDrawable(@DrawableRes int resId) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800296 setLeftStripDrawable(mContext.getDrawable(resId));
Romain Guy61c9d4b2010-03-01 14:12:10 -0800297 }
298
299 /**
Alan Viveretted78036b2015-08-14 13:53:56 -0400300 * @return the drawable used as the left part of the strip below the tab
301 * indicators, may be {@code null}
302 * @see #setLeftStripDrawable(int)
303 * @see #setLeftStripDrawable(Drawable)
304 * @attr ref android.R.styleable#TabWidget_tabStripLeft
305 */
306 @Nullable
307 public Drawable getLeftStripDrawable() {
308 return mLeftStrip;
309 }
310
311 /**
Alan Viverette7eb09d12015-08-14 11:36:01 -0400312 * Sets the drawable to use as the right part of the strip below the tab
313 * indicators.
314 *
Marco Nelissen189f65c2010-03-22 10:59:00 -0700315 * @param drawable the right strip drawable
Alan Viveretted78036b2015-08-14 13:53:56 -0400316 * @see #getRightStripDrawable()
Alan Viverette7eb09d12015-08-14 11:36:01 -0400317 * @attr ref android.R.styleable#TabWidget_tabStripRight
Romain Guy61c9d4b2010-03-01 14:12:10 -0800318 */
Alan Viverette7eb09d12015-08-14 11:36:01 -0400319 public void setRightStripDrawable(@Nullable Drawable drawable) {
Romain Guy65fe2c082010-03-29 12:27:30 -0700320 mRightStrip = drawable;
Romain Guy42c79882010-03-01 17:20:57 -0800321 requestLayout();
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700322 invalidate();
323 }
Romain Guy61c9d4b2010-03-01 14:12:10 -0800324
325 /**
Alan Viverette7eb09d12015-08-14 11:36:01 -0400326 * Sets the drawable to use as the right part of the strip below the tab
327 * indicators.
328 *
329 * @param resId the resource identifier of the drawable to use as the right
330 * strip drawable
Alan Viveretted78036b2015-08-14 13:53:56 -0400331 * @see #getRightStripDrawable()
Alan Viverette7eb09d12015-08-14 11:36:01 -0400332 * @attr ref android.R.styleable#TabWidget_tabStripRight
Romain Guy61c9d4b2010-03-01 14:12:10 -0800333 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700334 public void setRightStripDrawable(@DrawableRes int resId) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800335 setRightStripDrawable(mContext.getDrawable(resId));
Romain Guy61c9d4b2010-03-01 14:12:10 -0800336 }
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700337
Romain Guy61c9d4b2010-03-01 14:12:10 -0800338 /**
Alan Viveretted78036b2015-08-14 13:53:56 -0400339 * @return the drawable used as the right part of the strip below the tab
340 * indicators, may be {@code null}
341 * @see #setRightStripDrawable(int)
342 * @see #setRightStripDrawable(Drawable)
343 * @attr ref android.R.styleable#TabWidget_tabStripRight
344 */
345 @Nullable
346 public Drawable getRightStripDrawable() {
347 return mRightStrip;
348 }
349
350 /**
Jack Veenstra53175142009-06-01 21:27:01 -0700351 * Controls whether the bottom strips on the tab indicators are drawn or
352 * not. The default is to draw them. If the user specifies a custom
353 * view for the tab indicators, then the TabHost class calls this method
354 * to disable drawing of the bottom strips.
Romain Guy61c9d4b2010-03-01 14:12:10 -0800355 * @param stripEnabled true if the bottom strips should be drawn.
Jack Veenstra53175142009-06-01 21:27:01 -0700356 */
Romain Guy61c9d4b2010-03-01 14:12:10 -0800357 public void setStripEnabled(boolean stripEnabled) {
358 mDrawBottomStrips = stripEnabled;
Romain Guy42c79882010-03-01 17:20:57 -0800359 invalidate();
Romain Guy61c9d4b2010-03-01 14:12:10 -0800360 }
361
362 /**
363 * Indicates whether the bottom strips on the tab indicators are drawn
364 * or not.
365 */
366 public boolean isStripEnabled() {
367 return mDrawBottomStrips;
Jack Veenstra53175142009-06-01 21:27:01 -0700368 }
369
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370 @Override
371 public void childDrawableStateChanged(View child) {
Bjorn Bringertacdef592009-12-10 15:58:49 +0000372 if (getTabCount() > 0 && child == getChildTabViewAt(mSelectedTab)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800373 // To make sure that the bottom strip is redrawn
374 invalidate();
375 }
376 super.childDrawableStateChanged(child);
377 }
Evan Millar3730bb12009-08-21 13:58:41 -0700378
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 @Override
380 public void dispatchDraw(Canvas canvas) {
381 super.dispatchDraw(canvas);
382
Bjorn Bringertacdef592009-12-10 15:58:49 +0000383 // Do nothing if there are no tabs.
384 if (getTabCount() == 0) return;
385
Jack Veenstra53175142009-06-01 21:27:01 -0700386 // If the user specified a custom view for the tab indicators, then
387 // do not draw the bottom strips.
388 if (!mDrawBottomStrips) {
389 // Skip drawing the bottom strips.
390 return;
391 }
392
Romain Guy61c9d4b2010-03-01 14:12:10 -0800393 final View selectedChild = getChildTabViewAt(mSelectedTab);
Evan Millar3730bb12009-08-21 13:58:41 -0700394
Romain Guy65fe2c082010-03-29 12:27:30 -0700395 final Drawable leftStrip = mLeftStrip;
396 final Drawable rightStrip = mRightStrip;
Romain Guy61c9d4b2010-03-01 14:12:10 -0800397
Alan Viverette00e94012017-09-15 16:04:48 -0400398 if (leftStrip != null) {
399 leftStrip.setState(selectedChild.getDrawableState());
400 }
401 if (rightStrip != null) {
402 rightStrip.setState(selectedChild.getDrawableState());
403 }
Evan Millar3730bb12009-08-21 13:58:41 -0700404
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800405 if (mStripMoved) {
Romain Guy61c9d4b2010-03-01 14:12:10 -0800406 final Rect bounds = mBounds;
407 bounds.left = selectedChild.getLeft();
408 bounds.right = selectedChild.getRight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409 final int myHeight = getHeight();
Alan Viverette00e94012017-09-15 16:04:48 -0400410 if (leftStrip != null) {
411 leftStrip.setBounds(Math.min(0, bounds.left - leftStrip.getIntrinsicWidth()),
412 myHeight - leftStrip.getIntrinsicHeight(), bounds.left, myHeight);
413 }
414 if (rightStrip != null) {
415 rightStrip.setBounds(bounds.right, myHeight - rightStrip.getIntrinsicHeight(),
416 Math.max(getWidth(), bounds.right + rightStrip.getIntrinsicWidth()),
417 myHeight);
418 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 mStripMoved = false;
420 }
Evan Millar3730bb12009-08-21 13:58:41 -0700421
Alan Viverette00e94012017-09-15 16:04:48 -0400422 if (leftStrip != null) {
423 leftStrip.draw(canvas);
424 }
425 if (rightStrip != null) {
426 rightStrip.draw(canvas);
427 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800428 }
429
430 /**
431 * Sets the current tab.
Alan Viverette7eb09d12015-08-14 11:36:01 -0400432 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800433 * This method is used to bring a tab to the front of the Widget,
434 * and is used to post to the rest of the UI that a different tab
435 * has been brought to the foreground.
Alan Viverette7eb09d12015-08-14 11:36:01 -0400436 * <p>
Evan Millar3730bb12009-08-21 13:58:41 -0700437 * Note, this is separate from the traditional "focus" that is
438 * employed from the view logic.
Alan Viverette7eb09d12015-08-14 11:36:01 -0400439 * <p>
Evan Millar3730bb12009-08-21 13:58:41 -0700440 * For instance, if we have a list in a tabbed view, a user may be
441 * navigating up and down the list, moving the UI focus (orange
442 * highlighting) through the list items. The cursor movement does
443 * not effect the "selected" tab though, because what is being
444 * scrolled through is all on the same tab. The selected tab only
445 * changes when we navigate between tabs (moving from the list view
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800446 * to the next tabbed view, in this example).
Alan Viverette7eb09d12015-08-14 11:36:01 -0400447 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 * To move both the focus AND the selected tab at once, please use
Kevin Hufnaglee71346f2019-07-18 12:50:31 -0700449 * {@link #focusCurrentTab}. Normally, the view logic takes care of
Evan Millar3730bb12009-08-21 13:58:41 -0700450 * adjusting the focus, so unless you're circumventing the UI,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800451 * you'll probably just focus your interest here.
Evan Millar3730bb12009-08-21 13:58:41 -0700452 *
Alan Viverette7eb09d12015-08-14 11:36:01 -0400453 * @param index the index of the tab that you want to indicate as the
454 * selected tab (tab brought to the front of the widget)
455 * @see #focusCurrentTab
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800456 */
457 public void setCurrentTab(int index) {
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700458 if (index < 0 || index >= getTabCount() || index == mSelectedTab) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800459 return;
460 }
461
Gilles Debunne97dfd342010-10-20 16:31:15 -0700462 if (mSelectedTab != -1) {
463 getChildTabViewAt(mSelectedTab).setSelected(false);
464 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800465 mSelectedTab = index;
Jack Veenstra53175142009-06-01 21:27:01 -0700466 getChildTabViewAt(mSelectedTab).setSelected(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800467 mStripMoved = true;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700468 }
469
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800470 @Override
471 public CharSequence getAccessibilityClassName() {
472 return TabWidget.class.getName();
473 }
474
Alan Viverettea54956a2015-01-07 16:05:02 -0800475 /** @hide */
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700476 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800477 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
478 super.onInitializeAccessibilityEventInternal(event);
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700479 event.setItemCount(getTabCount());
480 event.setCurrentItemIndex(mSelectedTab);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800481 }
Evan Millar3730bb12009-08-21 13:58:41 -0700482
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800483 /**
484 * Sets the current tab and focuses the UI on it.
Evan Millar3730bb12009-08-21 13:58:41 -0700485 * This method makes sure that the focused tab matches the selected
486 * tab, normally at {@link #setCurrentTab}. Normally this would not
487 * be an issue if we go through the UI, since the UI is responsible
488 * for calling TabWidget.onFocusChanged(), but in the case where we
489 * are selecting the tab programmatically, we'll need to make sure
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800490 * focus keeps up.
Evan Millar3730bb12009-08-21 13:58:41 -0700491 *
492 * @param index The tab that you want focused (highlighted in orange)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493 * and selected (tab brought to the front of the widget)
Evan Millar3730bb12009-08-21 13:58:41 -0700494 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 * @see #setCurrentTab
496 */
497 public void focusCurrentTab(int index) {
498 final int oldTab = mSelectedTab;
499
500 // set the tab
501 setCurrentTab(index);
Evan Millar3730bb12009-08-21 13:58:41 -0700502
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800503 // change the focus if applicable.
504 if (oldTab != index) {
Jack Veenstra53175142009-06-01 21:27:01 -0700505 getChildTabViewAt(index).requestFocus();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800506 }
507 }
Evan Millar3730bb12009-08-21 13:58:41 -0700508
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800509 @Override
510 public void setEnabled(boolean enabled) {
511 super.setEnabled(enabled);
Evan Millar3730bb12009-08-21 13:58:41 -0700512
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700513 final int count = getTabCount();
Jack Veenstra53175142009-06-01 21:27:01 -0700514 for (int i = 0; i < count; i++) {
Alan Viverette7eb09d12015-08-14 11:36:01 -0400515 final View child = getChildTabViewAt(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800516 child.setEnabled(enabled);
517 }
518 }
519
520 @Override
521 public void addView(View child) {
522 if (child.getLayoutParams() == null) {
523 final LinearLayout.LayoutParams lp = new LayoutParams(
Alan Viverette7eb09d12015-08-14 11:36:01 -0400524 0, ViewGroup.LayoutParams.MATCH_PARENT, 1.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800525 lp.setMargins(0, 0, 0, 0);
526 child.setLayoutParams(lp);
527 }
528
529 // Ensure you can navigate to the tab with the keyboard, and you can touch it
530 child.setFocusable(true);
531 child.setClickable(true);
532
Vladislav Kaznacheev2a848ff2016-09-23 10:16:16 -0700533 if (child.getPointerIcon() == null) {
534 child.setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND));
535 }
536
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800537 super.addView(child);
538
539 // TODO: detect this via geometry with a tabwidget listener rather
540 // than potentially interfere with the view's listener
Jack Veenstra53175142009-06-01 21:27:01 -0700541 child.setOnClickListener(new TabClickListener(getTabCount() - 1));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542 }
543
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700544 @Override
Jeff Sharkeycd2ca402011-06-10 15:14:07 -0700545 public void removeAllViews() {
546 super.removeAllViews();
547 mSelectedTab = -1;
548 }
549
Vladislav Kaznacheev2a848ff2016-09-23 10:16:16 -0700550 @Override
551 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
552 if (!isEnabled()) {
553 return null;
554 }
555 return super.onResolvePointerIcon(event, pointerIndex);
556 }
557
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800558 /**
Alan Viverette7eb09d12015-08-14 11:36:01 -0400559 * Provides a way for {@link TabHost} to be notified that the user clicked
560 * on a tab indicator.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800561 */
Charles Chen5c0943d2019-07-30 17:21:47 +0800562 @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q,
563 publicAlternatives = "Use {@code androidx.viewpager.widget.ViewPager} and "
564 + "{@code com.google.android.material.tabs.TabLayout} instead.\n"
565 + "See <a href=\"{@docRoot}guide/navigation/navigation-swipe-view"
566 + "\">TabLayout and ViewPager</a>")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800567 void setTabSelectionListener(OnTabSelectionChanged listener) {
568 mSelectionChangedListener = listener;
569 }
570
Alan Viverette1352a362015-08-17 14:22:22 -0400571 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800572 public void onFocusChange(View v, boolean hasFocus) {
Alan Viverette1352a362015-08-17 14:22:22 -0400573 // No-op. Tab selection is separate from keyboard focus.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800574 }
575
576 // registered with each tab indicator so we can notify tab host
577 private class TabClickListener implements OnClickListener {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578 private final int mTabIndex;
579
580 private TabClickListener(int tabIndex) {
581 mTabIndex = tabIndex;
582 }
583
584 public void onClick(View v) {
585 mSelectionChangedListener.onTabSelectionChanged(mTabIndex, true);
586 }
587 }
588
589 /**
Alan Viverette7eb09d12015-08-14 11:36:01 -0400590 * Lets {@link TabHost} know that the user clicked on a tab indicator.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800591 */
Alan Viverette7eb09d12015-08-14 11:36:01 -0400592 interface OnTabSelectionChanged {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800593 /**
594 * Informs the TabHost which tab was selected. It also indicates
595 * if the tab was clicked/pressed or just focused into.
Evan Millar3730bb12009-08-21 13:58:41 -0700596 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800597 * @param tabIndex index of the tab that was selected
Alan Viverette7eb09d12015-08-14 11:36:01 -0400598 * @param clicked whether the selection changed due to a touch/click or
599 * due to focus entering the tab through navigation.
600 * {@code true} if it was due to a press/click and
601 * {@code false} otherwise.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800602 */
603 void onTabSelectionChanged(int tabIndex, boolean clicked);
604 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800605}