blob: 47a5449e752480a1a5e3a756b1603908d6908f41 [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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.content.Context;
Mike Cleron76097642009-09-25 17:53:56 -070021import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import 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;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080032import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034/**
Evan Millar3730bb12009-08-21 13:58:41 -070035 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036 * Displays a list of tab labels representing each page in the parent's tab
37 * collection. The container object for this widget is
38 * {@link android.widget.TabHost TabHost}. When the user selects a tab, this
39 * object sends a message to the parent container, TabHost, to tell it to switch
40 * the displayed page. You typically won't use many methods directly on this
41 * object. The container TabHost is used to add labels, add the callback
42 * handler, and manage callbacks. You might call this object to iterate the list
43 * of tabs, or to tweak the layout of the tab list, but most methods should be
44 * called on the containing TabHost object.
Romain Guy7883c972010-03-01 16:39:17 -080045 *
46 * @attr ref android.R.styleable#TabWidget_divider
Romain Guy6b1e6962010-03-29 14:38:41 -070047 * @attr ref android.R.styleable#TabWidget_tabStripEnabled
48 * @attr ref android.R.styleable#TabWidget_tabStripLeft
49 * @attr ref android.R.styleable#TabWidget_tabStripRight
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050 */
51public class TabWidget extends LinearLayout implements OnFocusChangeListener {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052 private OnTabSelectionChanged mSelectionChangedListener;
Romain Guy61c9d4b2010-03-01 14:12:10 -080053
Gilles Debunne97dfd342010-10-20 16:31:15 -070054 // This value will be set to 0 as soon as the first tab is added to TabHost.
55 private int mSelectedTab = -1;
Romain Guy61c9d4b2010-03-01 14:12:10 -080056
Romain Guy65fe2c02010-03-29 12:27:30 -070057 private Drawable mLeftStrip;
58 private Drawable mRightStrip;
Romain Guy61c9d4b2010-03-01 14:12:10 -080059
Jack Veenstra53175142009-06-01 21:27:01 -070060 private boolean mDrawBottomStrips = true;
Romain Guy61c9d4b2010-03-01 14:12:10 -080061 private boolean mStripMoved;
62
Romain Guy61c9d4b2010-03-01 14:12:10 -080063 private final Rect mBounds = new Rect();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064
Gilles Debunnec4d3f752011-01-25 20:11:45 -080065 // When positive, the widths and heights of tabs will be imposed so that they fit in parent
66 private int mImposedTabsHeight = -1;
67 private int[] mImposedTabWidths;
68
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069 public TabWidget(Context context) {
70 this(context, null);
71 }
72
73 public TabWidget(Context context, AttributeSet attrs) {
74 this(context, attrs, com.android.internal.R.attr.tabWidgetStyle);
75 }
76
Alan Viverette617feb92013-09-09 18:09:13 -070077 public TabWidget(Context context, AttributeSet attrs, int defStyleAttr) {
78 this(context, attrs, defStyleAttr, 0);
79 }
80
81 public TabWidget(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
82 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083
Jeff Sharkey11f4a482011-08-08 21:05:40 -070084 final TypedArray a = context.obtainStyledAttributes(
Alan Viverette617feb92013-09-09 18:09:13 -070085 attrs, com.android.internal.R.styleable.TabWidget, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086
Jeff Sharkey11f4a482011-08-08 21:05:40 -070087 setStripEnabled(a.getBoolean(R.styleable.TabWidget_tabStripEnabled, true));
88 setLeftStripDrawable(a.getDrawable(R.styleable.TabWidget_tabStripLeft));
89 setRightStripDrawable(a.getDrawable(R.styleable.TabWidget_tabStripRight));
Romain Guy61c9d4b2010-03-01 14:12:10 -080090
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091 a.recycle();
Romain Guy61c9d4b2010-03-01 14:12:10 -080092
93 initTabWidget();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080094 }
Evan Millar3730bb12009-08-21 13:58:41 -070095
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096 @Override
97 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
98 mStripMoved = true;
99 super.onSizeChanged(w, h, oldw, oldh);
100 }
101
Evan Millar3730bb12009-08-21 13:58:41 -0700102 @Override
103 protected int getChildDrawingOrder(int childCount, int i) {
Gilles Debunne97dfd342010-10-20 16:31:15 -0700104 if (mSelectedTab == -1) {
Evan Millar3730bb12009-08-21 13:58:41 -0700105 return i;
Gilles Debunne97dfd342010-10-20 16:31:15 -0700106 } else {
107 // Always draw the selected tab last, so that drop shadows are drawn
108 // in the correct z-order.
109 if (i == childCount - 1) {
110 return mSelectedTab;
111 } else if (i >= mSelectedTab) {
112 return i + 1;
113 } else {
114 return i;
115 }
Evan Millar3730bb12009-08-21 13:58:41 -0700116 }
117 }
118
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119 private void initTabWidget() {
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700120 setChildrenDrawingOrderEnabled(true);
Evan Millar3730bb12009-08-21 13:58:41 -0700121
Mike Cleron76097642009-09-25 17:53:56 -0700122 final Context context = mContext;
Gilles Debunne44c14732010-10-19 11:56:59 -0700123
124 // Tests the target Sdk version, as set in the Manifest. Could not be set using styles.xml
125 // in a values-v? directory which targets the current platform Sdk version instead.
Mike Cleron76097642009-09-25 17:53:56 -0700126 if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
127 // Donut apps get old color scheme
Romain Guy65fe2c02010-03-29 12:27:30 -0700128 if (mLeftStrip == null) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800129 mLeftStrip = context.getDrawable(
Romain Guy61c9d4b2010-03-01 14:12:10 -0800130 com.android.internal.R.drawable.tab_bottom_left_v4);
131 }
Romain Guy65fe2c02010-03-29 12:27:30 -0700132 if (mRightStrip == null) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800133 mRightStrip = context.getDrawable(
Romain Guy61c9d4b2010-03-01 14:12:10 -0800134 com.android.internal.R.drawable.tab_bottom_right_v4);
135 }
Dianne Hackborn1ec7e202011-01-28 14:11:17 -0800136 } else {
137 // Use modern color scheme for Eclair and beyond
138 if (mLeftStrip == null) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800139 mLeftStrip = context.getDrawable(
Dianne Hackborn1ec7e202011-01-28 14:11:17 -0800140 com.android.internal.R.drawable.tab_bottom_left);
141 }
142 if (mRightStrip == null) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800143 mRightStrip = context.getDrawable(
Dianne Hackborn1ec7e202011-01-28 14:11:17 -0800144 com.android.internal.R.drawable.tab_bottom_right);
145 }
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700146 }
Mike Cleron76097642009-09-25 17:53:56 -0700147
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800148 // Deal with focus, as we don't want the focus to go by default
149 // to a tab other than the current tab
150 setFocusable(true);
151 setOnFocusChangeListener(this);
152 }
153
Gilles Debunne6741c942011-01-25 20:11:45 -0800154 @Override
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800155 void measureChildBeforeLayout(View child, int childIndex,
156 int widthMeasureSpec, int totalWidth,
157 int heightMeasureSpec, int totalHeight) {
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700158 if (!isMeasureWithLargestChildEnabled() && mImposedTabsHeight >= 0) {
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800159 widthMeasureSpec = MeasureSpec.makeMeasureSpec(
160 totalWidth + mImposedTabWidths[childIndex], MeasureSpec.EXACTLY);
161 heightMeasureSpec = MeasureSpec.makeMeasureSpec(mImposedTabsHeight,
162 MeasureSpec.EXACTLY);
Gilles Debunne6741c942011-01-25 20:11:45 -0800163 }
164
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800165 super.measureChildBeforeLayout(child, childIndex,
166 widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight);
167 }
168
169 @Override
170 void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
Gilles Debunne52a5e6582011-02-28 17:58:35 -0800171 if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) {
172 super.measureHorizontal(widthMeasureSpec, heightMeasureSpec);
173 return;
174 }
175
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800176 // First, measure with no constraint
177 final int unspecifiedWidth = MeasureSpec.makeMeasureSpec(0, 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
181 int extraWidth = getMeasuredWidth() - MeasureSpec.getSize(widthMeasureSpec);
182 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 */
247 public void setDividerDrawable(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 Guy65fe2c02010-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 */
268 public void setLeftStripDrawable(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 Guy65fe2c02010-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 */
289 public void setRightStripDrawable(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 Guy65fe2c02010-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
404 @Override
405 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700406 onPopulateAccessibilityEvent(event);
407 // Dispatch only to the selected tab.
408 if (mSelectedTab != -1) {
Svetoslav Ganov0b0a41d2011-09-07 18:06:03 -0700409 View tabView = getChildTabViewAt(mSelectedTab);
410 if (tabView != null && tabView.getVisibility() == VISIBLE) {
411 return tabView.dispatchPopulateAccessibilityEvent(event);
412 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700413 }
414 return false;
415 }
416
417 @Override
Svetoslav Ganov30401322011-05-12 18:53:45 -0700418 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
419 super.onInitializeAccessibilityEvent(event);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800420 event.setClassName(TabWidget.class.getName());
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700421 event.setItemCount(getTabCount());
422 event.setCurrentItemIndex(mSelectedTab);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800423 }
Evan Millar3730bb12009-08-21 13:58:41 -0700424
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800425
426 @Override
427 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
428 // this class fires events only when tabs are focused or selected
429 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && isFocused()) {
430 event.recycle();
431 return;
432 }
433 super.sendAccessibilityEventUnchecked(event);
434 }
435
436 @Override
437 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
438 super.onInitializeAccessibilityNodeInfo(info);
439 info.setClassName(TabWidget.class.getName());
440 }
441
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800442 /**
443 * Sets the current tab and focuses the UI on it.
Evan Millar3730bb12009-08-21 13:58:41 -0700444 * This method makes sure that the focused tab matches the selected
445 * tab, normally at {@link #setCurrentTab}. Normally this would not
446 * be an issue if we go through the UI, since the UI is responsible
447 * for calling TabWidget.onFocusChanged(), but in the case where we
448 * are selecting the tab programmatically, we'll need to make sure
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449 * focus keeps up.
Evan Millar3730bb12009-08-21 13:58:41 -0700450 *
451 * @param index The tab that you want focused (highlighted in orange)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800452 * and selected (tab brought to the front of the widget)
Evan Millar3730bb12009-08-21 13:58:41 -0700453 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800454 * @see #setCurrentTab
455 */
456 public void focusCurrentTab(int index) {
457 final int oldTab = mSelectedTab;
458
459 // set the tab
460 setCurrentTab(index);
Evan Millar3730bb12009-08-21 13:58:41 -0700461
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800462 // change the focus if applicable.
463 if (oldTab != index) {
Jack Veenstra53175142009-06-01 21:27:01 -0700464 getChildTabViewAt(index).requestFocus();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800465 }
466 }
Evan Millar3730bb12009-08-21 13:58:41 -0700467
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800468 @Override
469 public void setEnabled(boolean enabled) {
470 super.setEnabled(enabled);
Evan Millar3730bb12009-08-21 13:58:41 -0700471
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700472 final int count = getTabCount();
Jack Veenstra53175142009-06-01 21:27:01 -0700473 for (int i = 0; i < count; i++) {
474 View child = getChildTabViewAt(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800475 child.setEnabled(enabled);
476 }
477 }
478
479 @Override
480 public void addView(View child) {
481 if (child.getLayoutParams() == null) {
482 final LinearLayout.LayoutParams lp = new LayoutParams(
483 0,
Romain Guy980a9382010-01-08 15:06:28 -0800484 ViewGroup.LayoutParams.MATCH_PARENT, 1.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800485 lp.setMargins(0, 0, 0, 0);
486 child.setLayoutParams(lp);
487 }
488
489 // Ensure you can navigate to the tab with the keyboard, and you can touch it
490 child.setFocusable(true);
491 child.setClickable(true);
492
493 super.addView(child);
494
495 // TODO: detect this via geometry with a tabwidget listener rather
496 // than potentially interfere with the view's listener
Jack Veenstra53175142009-06-01 21:27:01 -0700497 child.setOnClickListener(new TabClickListener(getTabCount() - 1));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800498 child.setOnFocusChangeListener(this);
499 }
500
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700501 @Override
Jeff Sharkeycd2ca402011-06-10 15:14:07 -0700502 public void removeAllViews() {
503 super.removeAllViews();
504 mSelectedTab = -1;
505 }
506
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800507 /**
508 * Provides a way for {@link TabHost} to be notified that the user clicked on a tab indicator.
509 */
510 void setTabSelectionListener(OnTabSelectionChanged listener) {
511 mSelectionChangedListener = listener;
512 }
513
Jeff Sharkey11f4a482011-08-08 21:05:40 -0700514 /** {@inheritDoc} */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800515 public void onFocusChange(View v, boolean hasFocus) {
Bjorn Bringertacdef592009-12-10 15:58:49 +0000516 if (v == this && hasFocus && getTabCount() > 0) {
Jack Veenstra53175142009-06-01 21:27:01 -0700517 getChildTabViewAt(mSelectedTab).requestFocus();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800518 return;
519 }
Evan Millar3730bb12009-08-21 13:58:41 -0700520
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800521 if (hasFocus) {
522 int i = 0;
Jack Veenstra53175142009-06-01 21:27:01 -0700523 int numTabs = getTabCount();
524 while (i < numTabs) {
525 if (getChildTabViewAt(i) == v) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800526 setCurrentTab(i);
527 mSelectionChangedListener.onTabSelectionChanged(i, false);
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700528 if (isShown()) {
529 // a tab is focused so send an event to announce the tab widget state
530 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
531 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800532 break;
533 }
534 i++;
535 }
536 }
537 }
538
539 // registered with each tab indicator so we can notify tab host
540 private class TabClickListener implements OnClickListener {
541
542 private final int mTabIndex;
543
544 private TabClickListener(int tabIndex) {
545 mTabIndex = tabIndex;
546 }
547
548 public void onClick(View v) {
549 mSelectionChangedListener.onTabSelectionChanged(mTabIndex, true);
550 }
551 }
552
553 /**
554 * Let {@link TabHost} know that the user clicked on a tab indicator.
555 */
556 static interface OnTabSelectionChanged {
557 /**
558 * Informs the TabHost which tab was selected. It also indicates
559 * if the tab was clicked/pressed or just focused into.
Evan Millar3730bb12009-08-21 13:58:41 -0700560 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800561 * @param tabIndex index of the tab that was selected
562 * @param clicked whether the selection changed due to a touch/click
563 * or due to focus entering the tab through navigation. Pass true
564 * if it was due to a press/click and false otherwise.
565 */
566 void onTabSelectionChanged(int tabIndex, boolean clicked);
567 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800568}