blob: aa7168c617f0855a9720639e1552aea9d34e6934 [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
Romain Guy61c9d4b2010-03-01 14:12:10 -080019import android.R;
Tor Norbye7b9c9122013-05-30 16:48:33 -070020import android.annotation.DrawableRes;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.Context;
22import android.content.res.TypedArray;
23import android.graphics.Canvas;
24import android.graphics.Rect;
25import android.graphics.drawable.Drawable;
Mike Cleron76097642009-09-25 17:53:56 -070026import android.os.Build;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.util.AttributeSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.view.View;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.view.View.OnFocusChangeListener;
Gilles Debunne97dfd342010-10-20 16:31:15 -070030import android.view.ViewGroup;
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -070031import android.view.accessibility.AccessibilityEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033/**
Evan Millar3730bb12009-08-21 13:58:41 -070034 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035 * Displays a list of tab labels representing each page in the parent's tab
36 * collection. The container object for this widget is
37 * {@link android.widget.TabHost TabHost}. When the user selects a tab, this
38 * object sends a message to the parent container, TabHost, to tell it to switch
39 * the displayed page. You typically won't use many methods directly on this
40 * object. The container TabHost is used to add labels, add the callback
41 * handler, and manage callbacks. You might call this object to iterate the list
42 * of tabs, or to tweak the layout of the tab list, but most methods should be
43 * called on the containing TabHost object.
Romain Guy7883c972010-03-01 16:39:17 -080044 *
45 * @attr ref android.R.styleable#TabWidget_divider
Romain Guy6b1e6962010-03-29 14:38:41 -070046 * @attr ref android.R.styleable#TabWidget_tabStripEnabled
47 * @attr ref android.R.styleable#TabWidget_tabStripLeft
48 * @attr ref android.R.styleable#TabWidget_tabStripRight
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049 */
50public class TabWidget extends LinearLayout implements OnFocusChangeListener {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051 private OnTabSelectionChanged mSelectionChangedListener;
Romain Guy61c9d4b2010-03-01 14:12:10 -080052
Gilles Debunne97dfd342010-10-20 16:31:15 -070053 // This value will be set to 0 as soon as the first tab is added to TabHost.
54 private int mSelectedTab = -1;
Romain Guy61c9d4b2010-03-01 14:12:10 -080055
Romain Guy65fe2c082010-03-29 12:27:30 -070056 private Drawable mLeftStrip;
57 private Drawable mRightStrip;
Romain Guy61c9d4b2010-03-01 14:12:10 -080058
Jack Veenstra53175142009-06-01 21:27:01 -070059 private boolean mDrawBottomStrips = true;
Romain Guy61c9d4b2010-03-01 14:12:10 -080060 private boolean mStripMoved;
61
Romain Guy61c9d4b2010-03-01 14:12:10 -080062 private final Rect mBounds = new Rect();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063
Gilles Debunnec4d3f752011-01-25 20:11:45 -080064 // When positive, the widths and heights of tabs will be imposed so that they fit in parent
65 private int mImposedTabsHeight = -1;
66 private int[] mImposedTabWidths;
67
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068 public TabWidget(Context context) {
69 this(context, null);
70 }
71
72 public TabWidget(Context context, AttributeSet attrs) {
73 this(context, attrs, com.android.internal.R.attr.tabWidgetStyle);
74 }
75
Alan Viverette617feb92013-09-09 18:09:13 -070076 public TabWidget(Context context, AttributeSet attrs, int defStyleAttr) {
77 this(context, attrs, defStyleAttr, 0);
78 }
79
80 public TabWidget(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
81 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082
Jeff Sharkey11f4a482011-08-08 21:05:40 -070083 final TypedArray a = context.obtainStyledAttributes(
Alan Viverette617feb92013-09-09 18:09:13 -070084 attrs, com.android.internal.R.styleable.TabWidget, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080085
Jeff Sharkey11f4a482011-08-08 21:05:40 -070086 setStripEnabled(a.getBoolean(R.styleable.TabWidget_tabStripEnabled, true));
87 setLeftStripDrawable(a.getDrawable(R.styleable.TabWidget_tabStripLeft));
88 setRightStripDrawable(a.getDrawable(R.styleable.TabWidget_tabStripRight));
Romain Guy61c9d4b2010-03-01 14:12:10 -080089
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090 a.recycle();
Romain Guy61c9d4b2010-03-01 14:12:10 -080091
92 initTabWidget();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080093 }
Evan Millar3730bb12009-08-21 13:58:41 -070094
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095 @Override
96 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
97 mStripMoved = true;
98 super.onSizeChanged(w, h, oldw, oldh);
99 }
100
Evan Millar3730bb12009-08-21 13:58:41 -0700101 @Override
102 protected int getChildDrawingOrder(int childCount, int i) {
Gilles Debunne97dfd342010-10-20 16:31:15 -0700103 if (mSelectedTab == -1) {
Evan Millar3730bb12009-08-21 13:58:41 -0700104 return i;
Gilles Debunne97dfd342010-10-20 16:31:15 -0700105 } else {
106 // Always draw the selected tab last, so that drop shadows are drawn
107 // in the correct z-order.
108 if (i == childCount - 1) {
109 return mSelectedTab;
110 } else if (i >= mSelectedTab) {
111 return i + 1;
112 } else {
113 return i;
114 }
Evan Millar3730bb12009-08-21 13:58:41 -0700115 }
116 }
117
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118 private void initTabWidget() {
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700119 setChildrenDrawingOrderEnabled(true);
Evan Millar3730bb12009-08-21 13:58:41 -0700120
Mike Cleron76097642009-09-25 17:53:56 -0700121 final Context context = mContext;
Gilles Debunne44c14732010-10-19 11:56:59 -0700122
123 // Tests the target Sdk version, as set in the Manifest. Could not be set using styles.xml
124 // in a values-v? directory which targets the current platform Sdk version instead.
Mike Cleron76097642009-09-25 17:53:56 -0700125 if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
126 // Donut apps get old color scheme
Romain Guy65fe2c082010-03-29 12:27:30 -0700127 if (mLeftStrip == null) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800128 mLeftStrip = context.getDrawable(
Romain Guy61c9d4b2010-03-01 14:12:10 -0800129 com.android.internal.R.drawable.tab_bottom_left_v4);
130 }
Romain Guy65fe2c082010-03-29 12:27:30 -0700131 if (mRightStrip == null) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800132 mRightStrip = context.getDrawable(
Romain Guy61c9d4b2010-03-01 14:12:10 -0800133 com.android.internal.R.drawable.tab_bottom_right_v4);
134 }
Dianne Hackborn1ec7e202011-01-28 14:11:17 -0800135 } else {
136 // Use modern color scheme for Eclair and beyond
137 if (mLeftStrip == null) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800138 mLeftStrip = context.getDrawable(
Dianne Hackborn1ec7e202011-01-28 14:11:17 -0800139 com.android.internal.R.drawable.tab_bottom_left);
140 }
141 if (mRightStrip == null) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800142 mRightStrip = context.getDrawable(
Dianne Hackborn1ec7e202011-01-28 14:11:17 -0800143 com.android.internal.R.drawable.tab_bottom_right);
144 }
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700145 }
Mike Cleron76097642009-09-25 17:53:56 -0700146
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800147 // Deal with focus, as we don't want the focus to go by default
148 // to a tab other than the current tab
149 setFocusable(true);
150 setOnFocusChangeListener(this);
151 }
152
Gilles Debunne6741c942011-01-25 20:11:45 -0800153 @Override
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800154 void measureChildBeforeLayout(View child, int childIndex,
155 int widthMeasureSpec, int totalWidth,
156 int heightMeasureSpec, int totalHeight) {
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700157 if (!isMeasureWithLargestChildEnabled() && mImposedTabsHeight >= 0) {
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800158 widthMeasureSpec = MeasureSpec.makeMeasureSpec(
159 totalWidth + mImposedTabWidths[childIndex], MeasureSpec.EXACTLY);
160 heightMeasureSpec = MeasureSpec.makeMeasureSpec(mImposedTabsHeight,
161 MeasureSpec.EXACTLY);
Gilles Debunne6741c942011-01-25 20:11:45 -0800162 }
163
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800164 super.measureChildBeforeLayout(child, childIndex,
165 widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight);
166 }
167
168 @Override
169 void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
Gilles Debunne52a5e6582011-02-28 17:58:35 -0800170 if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) {
171 super.measureHorizontal(widthMeasureSpec, heightMeasureSpec);
172 return;
173 }
174
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800175 // First, measure with no constraint
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -0700176 final int width = MeasureSpec.getSize(widthMeasureSpec);
177 final int unspecifiedWidth = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800178 mImposedTabsHeight = -1;
Gilles Debunnecd59feb2011-02-25 14:34:20 -0800179 super.measureHorizontal(unspecifiedWidth, heightMeasureSpec);
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800180
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -0700181 int extraWidth = getMeasuredWidth() - width;
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800182 if (extraWidth > 0) {
183 final int count = getChildCount();
184
185 int childCount = 0;
Gilles Debunne6741c942011-01-25 20:11:45 -0800186 for (int i = 0; i < count; i++) {
187 final View child = getChildAt(i);
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800188 if (child.getVisibility() == GONE) continue;
189 childCount++;
Gilles Debunne6741c942011-01-25 20:11:45 -0800190 }
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800191
192 if (childCount > 0) {
193 if (mImposedTabWidths == null || mImposedTabWidths.length != count) {
194 mImposedTabWidths = new int[count];
195 }
196 for (int i = 0; i < count; i++) {
197 final View child = getChildAt(i);
198 if (child.getVisibility() == GONE) continue;
199 final int childWidth = child.getMeasuredWidth();
200 final int delta = extraWidth / childCount;
201 final int newWidth = Math.max(0, childWidth - delta);
202 mImposedTabWidths[i] = newWidth;
203 // Make sure the extra width is evenly distributed, no int division remainder
204 extraWidth -= childWidth - newWidth; // delta may have been clamped
205 childCount--;
206 mImposedTabsHeight = Math.max(mImposedTabsHeight, child.getMeasuredHeight());
207 }
208 }
209 }
210
211 // Measure again, this time with imposed tab widths and respecting initial spec request
Gilles Debunne52a5e6582011-02-28 17:58:35 -0800212 super.measureHorizontal(widthMeasureSpec, heightMeasureSpec);
Gilles Debunne6741c942011-01-25 20:11:45 -0800213 }
214
215 /**
Jack Veenstra53175142009-06-01 21:27:01 -0700216 * Returns the tab indicator view at the given index.
217 *
218 * @param index the zero-based index of the tab indicator view to return
219 * @return the tab indicator view at the given index
220 */
221 public View getChildTabViewAt(int index) {
Jack Veenstra53175142009-06-01 21:27:01 -0700222 return getChildAt(index);
223 }
224
225 /**
226 * Returns the number of tab indicator views.
227 * @return the number of tab indicator views.
228 */
229 public int getTabCount() {
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700230 return getChildCount();
Jack Veenstra53175142009-06-01 21:27:01 -0700231 }
232
233 /**
234 * Sets the drawable to use as a divider between the tab indicators.
235 * @param drawable the divider drawable
236 */
Gilles Debunne6741c942011-01-25 20:11:45 -0800237 @Override
Jack Veenstra53175142009-06-01 21:27:01 -0700238 public void setDividerDrawable(Drawable drawable) {
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700239 super.setDividerDrawable(drawable);
Jack Veenstra53175142009-06-01 21:27:01 -0700240 }
241
242 /**
243 * Sets the drawable to use as a divider between the tab indicators.
244 * @param resId the resource identifier of the drawable to use as a
245 * divider.
246 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700247 public void setDividerDrawable(@DrawableRes int resId) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800248 setDividerDrawable(mContext.getDrawable(resId));
Jack Veenstra53175142009-06-01 21:27:01 -0700249 }
Romain Guy61c9d4b2010-03-01 14:12:10 -0800250
251 /**
252 * Sets the drawable to use as the left part of the strip below the
253 * tab indicators.
254 * @param drawable the left strip drawable
255 */
256 public void setLeftStripDrawable(Drawable drawable) {
Romain Guy65fe2c082010-03-29 12:27:30 -0700257 mLeftStrip = drawable;
Romain Guy42c79882010-03-01 17:20:57 -0800258 requestLayout();
259 invalidate();
Romain Guy61c9d4b2010-03-01 14:12:10 -0800260 }
Jack Veenstra53175142009-06-01 21:27:01 -0700261
262 /**
Romain Guy61c9d4b2010-03-01 14:12:10 -0800263 * Sets the drawable to use as the left part of the strip below the
264 * tab indicators.
265 * @param resId the resource identifier of the drawable to use as the
266 * left strip drawable
267 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700268 public void setLeftStripDrawable(@DrawableRes int resId) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800269 setLeftStripDrawable(mContext.getDrawable(resId));
Romain Guy61c9d4b2010-03-01 14:12:10 -0800270 }
271
272 /**
Marco Nelissen189f65c2010-03-22 10:59:00 -0700273 * Sets the drawable to use as the right part of the strip below the
Romain Guy61c9d4b2010-03-01 14:12:10 -0800274 * tab indicators.
Marco Nelissen189f65c2010-03-22 10:59:00 -0700275 * @param drawable the right strip drawable
Romain Guy61c9d4b2010-03-01 14:12:10 -0800276 */
277 public void setRightStripDrawable(Drawable drawable) {
Romain Guy65fe2c082010-03-29 12:27:30 -0700278 mRightStrip = drawable;
Romain Guy42c79882010-03-01 17:20:57 -0800279 requestLayout();
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700280 invalidate();
281 }
Romain Guy61c9d4b2010-03-01 14:12:10 -0800282
283 /**
Marco Nelissen189f65c2010-03-22 10:59:00 -0700284 * Sets the drawable to use as the right part of the strip below the
Romain Guy61c9d4b2010-03-01 14:12:10 -0800285 * tab indicators.
286 * @param resId the resource identifier of the drawable to use as the
Marco Nelissen189f65c2010-03-22 10:59:00 -0700287 * right strip drawable
Romain Guy61c9d4b2010-03-01 14:12:10 -0800288 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700289 public void setRightStripDrawable(@DrawableRes int resId) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800290 setRightStripDrawable(mContext.getDrawable(resId));
Romain Guy61c9d4b2010-03-01 14:12:10 -0800291 }
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700292
Romain Guy61c9d4b2010-03-01 14:12:10 -0800293 /**
Jack Veenstra53175142009-06-01 21:27:01 -0700294 * Controls whether the bottom strips on the tab indicators are drawn or
295 * not. The default is to draw them. If the user specifies a custom
296 * view for the tab indicators, then the TabHost class calls this method
297 * to disable drawing of the bottom strips.
Romain Guy61c9d4b2010-03-01 14:12:10 -0800298 * @param stripEnabled true if the bottom strips should be drawn.
Jack Veenstra53175142009-06-01 21:27:01 -0700299 */
Romain Guy61c9d4b2010-03-01 14:12:10 -0800300 public void setStripEnabled(boolean stripEnabled) {
301 mDrawBottomStrips = stripEnabled;
Romain Guy42c79882010-03-01 17:20:57 -0800302 invalidate();
Romain Guy61c9d4b2010-03-01 14:12:10 -0800303 }
304
305 /**
306 * Indicates whether the bottom strips on the tab indicators are drawn
307 * or not.
308 */
309 public boolean isStripEnabled() {
310 return mDrawBottomStrips;
Jack Veenstra53175142009-06-01 21:27:01 -0700311 }
312
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800313 @Override
314 public void childDrawableStateChanged(View child) {
Bjorn Bringertacdef592009-12-10 15:58:49 +0000315 if (getTabCount() > 0 && child == getChildTabViewAt(mSelectedTab)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800316 // To make sure that the bottom strip is redrawn
317 invalidate();
318 }
319 super.childDrawableStateChanged(child);
320 }
Evan Millar3730bb12009-08-21 13:58:41 -0700321
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800322 @Override
323 public void dispatchDraw(Canvas canvas) {
324 super.dispatchDraw(canvas);
325
Bjorn Bringertacdef592009-12-10 15:58:49 +0000326 // Do nothing if there are no tabs.
327 if (getTabCount() == 0) return;
328
Jack Veenstra53175142009-06-01 21:27:01 -0700329 // If the user specified a custom view for the tab indicators, then
330 // do not draw the bottom strips.
331 if (!mDrawBottomStrips) {
332 // Skip drawing the bottom strips.
333 return;
334 }
335
Romain Guy61c9d4b2010-03-01 14:12:10 -0800336 final View selectedChild = getChildTabViewAt(mSelectedTab);
Evan Millar3730bb12009-08-21 13:58:41 -0700337
Romain Guy65fe2c082010-03-29 12:27:30 -0700338 final Drawable leftStrip = mLeftStrip;
339 final Drawable rightStrip = mRightStrip;
Romain Guy61c9d4b2010-03-01 14:12:10 -0800340
341 leftStrip.setState(selectedChild.getDrawableState());
342 rightStrip.setState(selectedChild.getDrawableState());
Evan Millar3730bb12009-08-21 13:58:41 -0700343
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800344 if (mStripMoved) {
Romain Guy61c9d4b2010-03-01 14:12:10 -0800345 final Rect bounds = mBounds;
346 bounds.left = selectedChild.getLeft();
347 bounds.right = selectedChild.getRight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800348 final int myHeight = getHeight();
Romain Guy61c9d4b2010-03-01 14:12:10 -0800349 leftStrip.setBounds(Math.min(0, bounds.left - leftStrip.getIntrinsicWidth()),
350 myHeight - leftStrip.getIntrinsicHeight(), bounds.left, myHeight);
351 rightStrip.setBounds(bounds.right, myHeight - rightStrip.getIntrinsicHeight(),
352 Math.max(getWidth(), bounds.right + rightStrip.getIntrinsicWidth()), myHeight);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800353 mStripMoved = false;
354 }
Evan Millar3730bb12009-08-21 13:58:41 -0700355
Romain Guy61c9d4b2010-03-01 14:12:10 -0800356 leftStrip.draw(canvas);
357 rightStrip.draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800358 }
359
360 /**
361 * Sets the current tab.
362 * This method is used to bring a tab to the front of the Widget,
363 * and is used to post to the rest of the UI that a different tab
364 * has been brought to the foreground.
Evan Millar3730bb12009-08-21 13:58:41 -0700365 *
366 * Note, this is separate from the traditional "focus" that is
367 * employed from the view logic.
368 *
369 * For instance, if we have a list in a tabbed view, a user may be
370 * navigating up and down the list, moving the UI focus (orange
371 * highlighting) through the list items. The cursor movement does
372 * not effect the "selected" tab though, because what is being
373 * scrolled through is all on the same tab. The selected tab only
374 * changes when we navigate between tabs (moving from the list view
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800375 * to the next tabbed view, in this example).
Evan Millar3730bb12009-08-21 13:58:41 -0700376 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 * To move both the focus AND the selected tab at once, please use
Evan Millar3730bb12009-08-21 13:58:41 -0700378 * {@link #setCurrentTab}. Normally, the view logic takes care of
379 * adjusting the focus, so unless you're circumventing the UI,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800380 * you'll probably just focus your interest here.
Evan Millar3730bb12009-08-21 13:58:41 -0700381 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382 * @param index The tab that you want to indicate as the selected
383 * tab (tab brought to the front of the widget)
Evan Millar3730bb12009-08-21 13:58:41 -0700384 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800385 * @see #focusCurrentTab
386 */
387 public void setCurrentTab(int index) {
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700388 if (index < 0 || index >= getTabCount() || index == mSelectedTab) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800389 return;
390 }
391
Gilles Debunne97dfd342010-10-20 16:31:15 -0700392 if (mSelectedTab != -1) {
393 getChildTabViewAt(mSelectedTab).setSelected(false);
394 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800395 mSelectedTab = index;
Jack Veenstra53175142009-06-01 21:27:01 -0700396 getChildTabViewAt(mSelectedTab).setSelected(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397 mStripMoved = true;
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700398
399 if (isShown()) {
400 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
401 }
402 }
403
Alan Viverettea54956a2015-01-07 16:05:02 -0800404 /** @hide */
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700405 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800406 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700407 onPopulateAccessibilityEvent(event);
408 // Dispatch only to the selected tab.
409 if (mSelectedTab != -1) {
Svetoslav Ganov0b0a41d2011-09-07 18:06:03 -0700410 View tabView = getChildTabViewAt(mSelectedTab);
411 if (tabView != null && tabView.getVisibility() == VISIBLE) {
412 return tabView.dispatchPopulateAccessibilityEvent(event);
413 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700414 }
415 return false;
416 }
417
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800418 @Override
419 public CharSequence getAccessibilityClassName() {
420 return TabWidget.class.getName();
421 }
422
Alan Viverettea54956a2015-01-07 16:05:02 -0800423 /** @hide */
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700424 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800425 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
426 super.onInitializeAccessibilityEventInternal(event);
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700427 event.setItemCount(getTabCount());
428 event.setCurrentItemIndex(mSelectedTab);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800429 }
Evan Millar3730bb12009-08-21 13:58:41 -0700430
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800431
Alan Viverettea54956a2015-01-07 16:05:02 -0800432 /** @hide */
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800433 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800434 public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800435 // this class fires events only when tabs are focused or selected
436 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && isFocused()) {
437 event.recycle();
438 return;
439 }
Alan Viverettea54956a2015-01-07 16:05:02 -0800440 super.sendAccessibilityEventUncheckedInternal(event);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800441 }
442
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800443 /**
444 * Sets the current tab and focuses the UI on it.
Evan Millar3730bb12009-08-21 13:58:41 -0700445 * This method makes sure that the focused tab matches the selected
446 * tab, normally at {@link #setCurrentTab}. Normally this would not
447 * be an issue if we go through the UI, since the UI is responsible
448 * for calling TabWidget.onFocusChanged(), but in the case where we
449 * are selecting the tab programmatically, we'll need to make sure
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800450 * focus keeps up.
Evan Millar3730bb12009-08-21 13:58:41 -0700451 *
452 * @param index The tab that you want focused (highlighted in orange)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453 * and selected (tab brought to the front of the widget)
Evan Millar3730bb12009-08-21 13:58:41 -0700454 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455 * @see #setCurrentTab
456 */
457 public void focusCurrentTab(int index) {
458 final int oldTab = mSelectedTab;
459
460 // set the tab
461 setCurrentTab(index);
Evan Millar3730bb12009-08-21 13:58:41 -0700462
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800463 // change the focus if applicable.
464 if (oldTab != index) {
Jack Veenstra53175142009-06-01 21:27:01 -0700465 getChildTabViewAt(index).requestFocus();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800466 }
467 }
Evan Millar3730bb12009-08-21 13:58:41 -0700468
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800469 @Override
470 public void setEnabled(boolean enabled) {
471 super.setEnabled(enabled);
Evan Millar3730bb12009-08-21 13:58:41 -0700472
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700473 final int count = getTabCount();
Jack Veenstra53175142009-06-01 21:27:01 -0700474 for (int i = 0; i < count; i++) {
475 View child = getChildTabViewAt(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800476 child.setEnabled(enabled);
477 }
478 }
479
480 @Override
481 public void addView(View child) {
482 if (child.getLayoutParams() == null) {
483 final LinearLayout.LayoutParams lp = new LayoutParams(
484 0,
Romain Guy980a9382010-01-08 15:06:28 -0800485 ViewGroup.LayoutParams.MATCH_PARENT, 1.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800486 lp.setMargins(0, 0, 0, 0);
487 child.setLayoutParams(lp);
488 }
489
490 // Ensure you can navigate to the tab with the keyboard, and you can touch it
491 child.setFocusable(true);
492 child.setClickable(true);
493
494 super.addView(child);
495
496 // TODO: detect this via geometry with a tabwidget listener rather
497 // than potentially interfere with the view's listener
Jack Veenstra53175142009-06-01 21:27:01 -0700498 child.setOnClickListener(new TabClickListener(getTabCount() - 1));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800499 child.setOnFocusChangeListener(this);
500 }
501
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700502 @Override
Jeff Sharkeycd2ca402011-06-10 15:14:07 -0700503 public void removeAllViews() {
504 super.removeAllViews();
505 mSelectedTab = -1;
506 }
507
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800508 /**
509 * Provides a way for {@link TabHost} to be notified that the user clicked on a tab indicator.
510 */
511 void setTabSelectionListener(OnTabSelectionChanged listener) {
512 mSelectionChangedListener = listener;
513 }
514
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700515 /** {@inheritDoc} */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800516 public void onFocusChange(View v, boolean hasFocus) {
Bjorn Bringertacdef592009-12-10 15:58:49 +0000517 if (v == this && hasFocus && getTabCount() > 0) {
Jack Veenstra53175142009-06-01 21:27:01 -0700518 getChildTabViewAt(mSelectedTab).requestFocus();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519 return;
520 }
Evan Millar3730bb12009-08-21 13:58:41 -0700521
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 if (hasFocus) {
523 int i = 0;
Jack Veenstra53175142009-06-01 21:27:01 -0700524 int numTabs = getTabCount();
525 while (i < numTabs) {
526 if (getChildTabViewAt(i) == v) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800527 setCurrentTab(i);
528 mSelectionChangedListener.onTabSelectionChanged(i, false);
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700529 if (isShown()) {
530 // a tab is focused so send an event to announce the tab widget state
531 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
532 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800533 break;
534 }
535 i++;
536 }
537 }
538 }
539
540 // registered with each tab indicator so we can notify tab host
541 private class TabClickListener implements OnClickListener {
542
543 private final int mTabIndex;
544
545 private TabClickListener(int tabIndex) {
546 mTabIndex = tabIndex;
547 }
548
549 public void onClick(View v) {
550 mSelectionChangedListener.onTabSelectionChanged(mTabIndex, true);
551 }
552 }
553
554 /**
555 * Let {@link TabHost} know that the user clicked on a tab indicator.
556 */
557 static interface OnTabSelectionChanged {
558 /**
559 * Informs the TabHost which tab was selected. It also indicates
560 * if the tab was clicked/pressed or just focused into.
Evan Millar3730bb12009-08-21 13:58:41 -0700561 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800562 * @param tabIndex index of the tab that was selected
563 * @param clicked whether the selection changed due to a touch/click
564 * or due to focus entering the tab through navigation. Pass true
565 * if it was due to a press/click and false otherwise.
566 */
567 void onTabSelectionChanged(int tabIndex, boolean clicked);
568 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569}