blob: 6f76dd0d2b757c41895148e0028473a6106cc587 [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;
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.
Scott Main41ec6532010-08-19 16:57:07 -070044 *
45 * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-tabwidget.html">Tab Layout
46 * tutorial</a>.</p>
Romain Guy7883c972010-03-01 16:39:17 -080047 *
48 * @attr ref android.R.styleable#TabWidget_divider
Romain Guy6b1e6962010-03-29 14:38:41 -070049 * @attr ref android.R.styleable#TabWidget_tabStripEnabled
50 * @attr ref android.R.styleable#TabWidget_tabStripLeft
51 * @attr ref android.R.styleable#TabWidget_tabStripRight
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052 */
53public class TabWidget extends LinearLayout implements OnFocusChangeListener {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054 private OnTabSelectionChanged mSelectionChangedListener;
Romain Guy61c9d4b2010-03-01 14:12:10 -080055
Gilles Debunne97dfd342010-10-20 16:31:15 -070056 // This value will be set to 0 as soon as the first tab is added to TabHost.
57 private int mSelectedTab = -1;
Romain Guy61c9d4b2010-03-01 14:12:10 -080058
Romain Guy65fe2c082010-03-29 12:27:30 -070059 private Drawable mLeftStrip;
60 private Drawable mRightStrip;
Romain Guy61c9d4b2010-03-01 14:12:10 -080061
Jack Veenstra53175142009-06-01 21:27:01 -070062 private boolean mDrawBottomStrips = true;
Romain Guy61c9d4b2010-03-01 14:12:10 -080063 private boolean mStripMoved;
64
65 private Drawable mDividerDrawable;
66
67 private final Rect mBounds = new Rect();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068
Gilles Debunnec4d3f752011-01-25 20:11:45 -080069 // When positive, the widths and heights of tabs will be imposed so that they fit in parent
70 private int mImposedTabsHeight = -1;
71 private int[] mImposedTabWidths;
72
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073 public TabWidget(Context context) {
74 this(context, null);
75 }
76
77 public TabWidget(Context context, AttributeSet attrs) {
78 this(context, attrs, com.android.internal.R.attr.tabWidgetStyle);
79 }
80
81 public TabWidget(Context context, AttributeSet attrs, int defStyle) {
82 super(context, attrs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083
Evan Millar3730bb12009-08-21 13:58:41 -070084 TypedArray a =
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080085 context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TabWidget,
86 defStyle, 0);
87
Romain Guy6b1e6962010-03-29 14:38:41 -070088 mDrawBottomStrips = a.getBoolean(R.styleable.TabWidget_tabStripEnabled, true);
Romain Guy61c9d4b2010-03-01 14:12:10 -080089 mDividerDrawable = a.getDrawable(R.styleable.TabWidget_divider);
Romain Guy6b1e6962010-03-29 14:38:41 -070090 mLeftStrip = a.getDrawable(R.styleable.TabWidget_tabStripLeft);
91 mRightStrip = a.getDrawable(R.styleable.TabWidget_tabStripRight);
Romain Guy61c9d4b2010-03-01 14:12:10 -080092
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080093 a.recycle();
Romain Guy61c9d4b2010-03-01 14:12:10 -080094
95 initTabWidget();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096 }
Evan Millar3730bb12009-08-21 13:58:41 -070097
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080098 @Override
99 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
100 mStripMoved = true;
101 super.onSizeChanged(w, h, oldw, oldh);
102 }
103
Evan Millar3730bb12009-08-21 13:58:41 -0700104 @Override
105 protected int getChildDrawingOrder(int childCount, int i) {
Gilles Debunne97dfd342010-10-20 16:31:15 -0700106 if (mSelectedTab == -1) {
Evan Millar3730bb12009-08-21 13:58:41 -0700107 return i;
Gilles Debunne97dfd342010-10-20 16:31:15 -0700108 } else {
109 // Always draw the selected tab last, so that drop shadows are drawn
110 // in the correct z-order.
111 if (i == childCount - 1) {
112 return mSelectedTab;
113 } else if (i >= mSelectedTab) {
114 return i + 1;
115 } else {
116 return i;
117 }
Evan Millar3730bb12009-08-21 13:58:41 -0700118 }
119 }
120
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800121 private void initTabWidget() {
Evan Millar3730bb12009-08-21 13:58:41 -0700122 mGroupFlags |= FLAG_USE_CHILD_DRAWING_ORDER;
123
Mike Cleron76097642009-09-25 17:53:56 -0700124 final Context context = mContext;
125 final Resources resources = context.getResources();
Gilles Debunne44c14732010-10-19 11:56:59 -0700126
127 // Tests the target Sdk version, as set in the Manifest. Could not be set using styles.xml
128 // in a values-v? directory which targets the current platform Sdk version instead.
Mike Cleron76097642009-09-25 17:53:56 -0700129 if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
130 // Donut apps get old color scheme
Romain Guy65fe2c082010-03-29 12:27:30 -0700131 if (mLeftStrip == null) {
132 mLeftStrip = resources.getDrawable(
Romain Guy61c9d4b2010-03-01 14:12:10 -0800133 com.android.internal.R.drawable.tab_bottom_left_v4);
134 }
Romain Guy65fe2c082010-03-29 12:27:30 -0700135 if (mRightStrip == null) {
136 mRightStrip = resources.getDrawable(
Romain Guy61c9d4b2010-03-01 14:12:10 -0800137 com.android.internal.R.drawable.tab_bottom_right_v4);
138 }
Dianne Hackborn1ec7e202011-01-28 14:11:17 -0800139 } else {
140 // Use modern color scheme for Eclair and beyond
141 if (mLeftStrip == null) {
142 mLeftStrip = resources.getDrawable(
143 com.android.internal.R.drawable.tab_bottom_left);
144 }
145 if (mRightStrip == null) {
146 mRightStrip = resources.getDrawable(
147 com.android.internal.R.drawable.tab_bottom_right);
148 }
149 }
Mike Cleron76097642009-09-25 17:53:56 -0700150
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800151 // Deal with focus, as we don't want the focus to go by default
152 // to a tab other than the current tab
153 setFocusable(true);
154 setOnFocusChangeListener(this);
155 }
156
Gilles Debunne6741c942011-01-25 20:11:45 -0800157 @Override
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800158 void measureChildBeforeLayout(View child, int childIndex,
159 int widthMeasureSpec, int totalWidth,
160 int heightMeasureSpec, int totalHeight) {
Gilles Debunne6741c942011-01-25 20:11:45 -0800161
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800162 if (mImposedTabsHeight >= 0) {
163 widthMeasureSpec = MeasureSpec.makeMeasureSpec(
164 totalWidth + mImposedTabWidths[childIndex], MeasureSpec.EXACTLY);
165 heightMeasureSpec = MeasureSpec.makeMeasureSpec(mImposedTabsHeight,
166 MeasureSpec.EXACTLY);
Gilles Debunne6741c942011-01-25 20:11:45 -0800167 }
168
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800169 super.measureChildBeforeLayout(child, childIndex,
170 widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight);
171 }
172
173 @Override
174 void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
Gilles Debunne52a5e6582011-02-28 17:58:35 -0800175 if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) {
176 super.measureHorizontal(widthMeasureSpec, heightMeasureSpec);
177 return;
178 }
179
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800180 // First, measure with no constraint
181 final int unspecifiedWidth = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800182 mImposedTabsHeight = -1;
Gilles Debunnecd59feb2011-02-25 14:34:20 -0800183 super.measureHorizontal(unspecifiedWidth, heightMeasureSpec);
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800184
185 int extraWidth = getMeasuredWidth() - MeasureSpec.getSize(widthMeasureSpec);
186 if (extraWidth > 0) {
187 final int count = getChildCount();
188
189 int childCount = 0;
Gilles Debunne6741c942011-01-25 20:11:45 -0800190 for (int i = 0; i < count; i++) {
191 final View child = getChildAt(i);
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800192 if (child.getVisibility() == GONE) continue;
193 childCount++;
Gilles Debunne6741c942011-01-25 20:11:45 -0800194 }
Gilles Debunnec4d3f752011-01-25 20:11:45 -0800195
196 if (childCount > 0) {
197 if (mImposedTabWidths == null || mImposedTabWidths.length != count) {
198 mImposedTabWidths = new int[count];
199 }
200 for (int i = 0; i < count; i++) {
201 final View child = getChildAt(i);
202 if (child.getVisibility() == GONE) continue;
203 final int childWidth = child.getMeasuredWidth();
204 final int delta = extraWidth / childCount;
205 final int newWidth = Math.max(0, childWidth - delta);
206 mImposedTabWidths[i] = newWidth;
207 // Make sure the extra width is evenly distributed, no int division remainder
208 extraWidth -= childWidth - newWidth; // delta may have been clamped
209 childCount--;
210 mImposedTabsHeight = Math.max(mImposedTabsHeight, child.getMeasuredHeight());
211 }
212 }
213 }
214
215 // Measure again, this time with imposed tab widths and respecting initial spec request
Gilles Debunne52a5e6582011-02-28 17:58:35 -0800216 super.measureHorizontal(widthMeasureSpec, heightMeasureSpec);
Gilles Debunne6741c942011-01-25 20:11:45 -0800217 }
218
219 /**
Jack Veenstra53175142009-06-01 21:27:01 -0700220 * Returns the tab indicator view at the given index.
221 *
222 * @param index the zero-based index of the tab indicator view to return
223 * @return the tab indicator view at the given index
224 */
225 public View getChildTabViewAt(int index) {
226 // If we are using dividers, then instead of tab views at 0, 1, 2, ...
227 // we have tab views at 0, 2, 4, ...
228 if (mDividerDrawable != null) {
229 index *= 2;
230 }
231 return getChildAt(index);
232 }
233
234 /**
235 * Returns the number of tab indicator views.
236 * @return the number of tab indicator views.
237 */
238 public int getTabCount() {
239 int children = getChildCount();
240
241 // If we have dividers, then we will always have an odd number of
242 // children: 1, 3, 5, ... and we want to convert that sequence to
243 // this: 1, 2, 3, ...
244 if (mDividerDrawable != null) {
245 children = (children + 1) / 2;
246 }
247 return children;
248 }
249
250 /**
251 * Sets the drawable to use as a divider between the tab indicators.
252 * @param drawable the divider drawable
253 */
Gilles Debunne6741c942011-01-25 20:11:45 -0800254 @Override
Jack Veenstra53175142009-06-01 21:27:01 -0700255 public void setDividerDrawable(Drawable drawable) {
256 mDividerDrawable = drawable;
Romain Guy42c79882010-03-01 17:20:57 -0800257 requestLayout();
258 invalidate();
Jack Veenstra53175142009-06-01 21:27:01 -0700259 }
260
261 /**
262 * Sets the drawable to use as a divider between the tab indicators.
263 * @param resId the resource identifier of the drawable to use as a
264 * divider.
265 */
266 public void setDividerDrawable(int resId) {
267 mDividerDrawable = mContext.getResources().getDrawable(resId);
Romain Guy42c79882010-03-01 17:20:57 -0800268 requestLayout();
269 invalidate();
Jack Veenstra53175142009-06-01 21:27:01 -0700270 }
Romain Guy61c9d4b2010-03-01 14:12:10 -0800271
272 /**
273 * Sets the drawable to use as the left part of the strip below the
274 * tab indicators.
275 * @param drawable the left strip drawable
276 */
277 public void setLeftStripDrawable(Drawable drawable) {
Romain Guy65fe2c082010-03-29 12:27:30 -0700278 mLeftStrip = drawable;
Romain Guy42c79882010-03-01 17:20:57 -0800279 requestLayout();
280 invalidate();
Romain Guy61c9d4b2010-03-01 14:12:10 -0800281 }
Jack Veenstra53175142009-06-01 21:27:01 -0700282
283 /**
Romain Guy61c9d4b2010-03-01 14:12:10 -0800284 * Sets the drawable to use as the left part of the strip below the
285 * tab indicators.
286 * @param resId the resource identifier of the drawable to use as the
287 * left strip drawable
288 */
289 public void setLeftStripDrawable(int resId) {
Romain Guy65fe2c082010-03-29 12:27:30 -0700290 mLeftStrip = mContext.getResources().getDrawable(resId);
Romain Guy42c79882010-03-01 17:20:57 -0800291 requestLayout();
292 invalidate();
Romain Guy61c9d4b2010-03-01 14:12:10 -0800293 }
294
295 /**
Marco Nelissen189f65c2010-03-22 10:59:00 -0700296 * Sets the drawable to use as the right part of the strip below the
Romain Guy61c9d4b2010-03-01 14:12:10 -0800297 * tab indicators.
Marco Nelissen189f65c2010-03-22 10:59:00 -0700298 * @param drawable the right strip drawable
Romain Guy61c9d4b2010-03-01 14:12:10 -0800299 */
300 public void setRightStripDrawable(Drawable drawable) {
Romain Guy65fe2c082010-03-29 12:27:30 -0700301 mRightStrip = drawable;
Romain Guy42c79882010-03-01 17:20:57 -0800302 requestLayout();
303 invalidate(); }
Romain Guy61c9d4b2010-03-01 14:12:10 -0800304
305 /**
Marco Nelissen189f65c2010-03-22 10:59:00 -0700306 * Sets the drawable to use as the right part of the strip below the
Romain Guy61c9d4b2010-03-01 14:12:10 -0800307 * tab indicators.
308 * @param resId the resource identifier of the drawable to use as the
Marco Nelissen189f65c2010-03-22 10:59:00 -0700309 * right strip drawable
Romain Guy61c9d4b2010-03-01 14:12:10 -0800310 */
311 public void setRightStripDrawable(int resId) {
Romain Guy65fe2c082010-03-29 12:27:30 -0700312 mRightStrip = mContext.getResources().getDrawable(resId);
Romain Guy42c79882010-03-01 17:20:57 -0800313 requestLayout();
314 invalidate();
Romain Guy61c9d4b2010-03-01 14:12:10 -0800315 }
316
317 /**
Jack Veenstra53175142009-06-01 21:27:01 -0700318 * Controls whether the bottom strips on the tab indicators are drawn or
319 * not. The default is to draw them. If the user specifies a custom
320 * view for the tab indicators, then the TabHost class calls this method
321 * to disable drawing of the bottom strips.
Romain Guy61c9d4b2010-03-01 14:12:10 -0800322 * @param stripEnabled true if the bottom strips should be drawn.
Jack Veenstra53175142009-06-01 21:27:01 -0700323 */
Romain Guy61c9d4b2010-03-01 14:12:10 -0800324 public void setStripEnabled(boolean stripEnabled) {
325 mDrawBottomStrips = stripEnabled;
Romain Guy42c79882010-03-01 17:20:57 -0800326 invalidate();
Romain Guy61c9d4b2010-03-01 14:12:10 -0800327 }
328
329 /**
330 * Indicates whether the bottom strips on the tab indicators are drawn
331 * or not.
332 */
333 public boolean isStripEnabled() {
334 return mDrawBottomStrips;
Jack Veenstra53175142009-06-01 21:27:01 -0700335 }
336
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800337 @Override
338 public void childDrawableStateChanged(View child) {
Bjorn Bringertacdef592009-12-10 15:58:49 +0000339 if (getTabCount() > 0 && child == getChildTabViewAt(mSelectedTab)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800340 // To make sure that the bottom strip is redrawn
341 invalidate();
342 }
343 super.childDrawableStateChanged(child);
344 }
Evan Millar3730bb12009-08-21 13:58:41 -0700345
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800346 @Override
347 public void dispatchDraw(Canvas canvas) {
348 super.dispatchDraw(canvas);
349
Bjorn Bringertacdef592009-12-10 15:58:49 +0000350 // Do nothing if there are no tabs.
351 if (getTabCount() == 0) return;
352
Jack Veenstra53175142009-06-01 21:27:01 -0700353 // If the user specified a custom view for the tab indicators, then
354 // do not draw the bottom strips.
355 if (!mDrawBottomStrips) {
356 // Skip drawing the bottom strips.
357 return;
358 }
359
Romain Guy61c9d4b2010-03-01 14:12:10 -0800360 final View selectedChild = getChildTabViewAt(mSelectedTab);
Evan Millar3730bb12009-08-21 13:58:41 -0700361
Romain Guy65fe2c082010-03-29 12:27:30 -0700362 final Drawable leftStrip = mLeftStrip;
363 final Drawable rightStrip = mRightStrip;
Romain Guy61c9d4b2010-03-01 14:12:10 -0800364
365 leftStrip.setState(selectedChild.getDrawableState());
366 rightStrip.setState(selectedChild.getDrawableState());
Evan Millar3730bb12009-08-21 13:58:41 -0700367
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368 if (mStripMoved) {
Romain Guy61c9d4b2010-03-01 14:12:10 -0800369 final Rect bounds = mBounds;
370 bounds.left = selectedChild.getLeft();
371 bounds.right = selectedChild.getRight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800372 final int myHeight = getHeight();
Romain Guy61c9d4b2010-03-01 14:12:10 -0800373 leftStrip.setBounds(Math.min(0, bounds.left - leftStrip.getIntrinsicWidth()),
374 myHeight - leftStrip.getIntrinsicHeight(), bounds.left, myHeight);
375 rightStrip.setBounds(bounds.right, myHeight - rightStrip.getIntrinsicHeight(),
376 Math.max(getWidth(), bounds.right + rightStrip.getIntrinsicWidth()), myHeight);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 mStripMoved = false;
378 }
Evan Millar3730bb12009-08-21 13:58:41 -0700379
Romain Guy61c9d4b2010-03-01 14:12:10 -0800380 leftStrip.draw(canvas);
381 rightStrip.draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382 }
383
384 /**
385 * Sets the current tab.
386 * This method is used to bring a tab to the front of the Widget,
387 * and is used to post to the rest of the UI that a different tab
388 * has been brought to the foreground.
Evan Millar3730bb12009-08-21 13:58:41 -0700389 *
390 * Note, this is separate from the traditional "focus" that is
391 * employed from the view logic.
392 *
393 * For instance, if we have a list in a tabbed view, a user may be
394 * navigating up and down the list, moving the UI focus (orange
395 * highlighting) through the list items. The cursor movement does
396 * not effect the "selected" tab though, because what is being
397 * scrolled through is all on the same tab. The selected tab only
398 * changes when we navigate between tabs (moving from the list view
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800399 * to the next tabbed view, in this example).
Evan Millar3730bb12009-08-21 13:58:41 -0700400 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800401 * To move both the focus AND the selected tab at once, please use
Evan Millar3730bb12009-08-21 13:58:41 -0700402 * {@link #setCurrentTab}. Normally, the view logic takes care of
403 * adjusting the focus, so unless you're circumventing the UI,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800404 * you'll probably just focus your interest here.
Evan Millar3730bb12009-08-21 13:58:41 -0700405 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800406 * @param index The tab that you want to indicate as the selected
407 * tab (tab brought to the front of the widget)
Evan Millar3730bb12009-08-21 13:58:41 -0700408 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409 * @see #focusCurrentTab
410 */
411 public void setCurrentTab(int index) {
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700412 if (index < 0 || index >= getTabCount() || index == mSelectedTab) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800413 return;
414 }
415
Gilles Debunne97dfd342010-10-20 16:31:15 -0700416 if (mSelectedTab != -1) {
417 getChildTabViewAt(mSelectedTab).setSelected(false);
418 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 mSelectedTab = index;
Jack Veenstra53175142009-06-01 21:27:01 -0700420 getChildTabViewAt(mSelectedTab).setSelected(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800421 mStripMoved = true;
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700422
423 if (isShown()) {
424 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
425 }
426 }
427
428 @Override
429 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
430 event.setItemCount(getTabCount());
431 event.setCurrentItemIndex(mSelectedTab);
Gilles Debunne97dfd342010-10-20 16:31:15 -0700432 if (mSelectedTab != -1) {
433 getChildTabViewAt(mSelectedTab).dispatchPopulateAccessibilityEvent(event);
434 }
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700435 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800436 }
Evan Millar3730bb12009-08-21 13:58:41 -0700437
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800438 /**
439 * Sets the current tab and focuses the UI on it.
Evan Millar3730bb12009-08-21 13:58:41 -0700440 * This method makes sure that the focused tab matches the selected
441 * tab, normally at {@link #setCurrentTab}. Normally this would not
442 * be an issue if we go through the UI, since the UI is responsible
443 * for calling TabWidget.onFocusChanged(), but in the case where we
444 * are selecting the tab programmatically, we'll need to make sure
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 * focus keeps up.
Evan Millar3730bb12009-08-21 13:58:41 -0700446 *
447 * @param index The tab that you want focused (highlighted in orange)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 * and selected (tab brought to the front of the widget)
Evan Millar3730bb12009-08-21 13:58:41 -0700449 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800450 * @see #setCurrentTab
451 */
452 public void focusCurrentTab(int index) {
453 final int oldTab = mSelectedTab;
454
455 // set the tab
456 setCurrentTab(index);
Evan Millar3730bb12009-08-21 13:58:41 -0700457
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800458 // change the focus if applicable.
459 if (oldTab != index) {
Jack Veenstra53175142009-06-01 21:27:01 -0700460 getChildTabViewAt(index).requestFocus();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800461 }
462 }
Evan Millar3730bb12009-08-21 13:58:41 -0700463
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800464 @Override
465 public void setEnabled(boolean enabled) {
466 super.setEnabled(enabled);
Jack Veenstra53175142009-06-01 21:27:01 -0700467 int count = getTabCount();
Evan Millar3730bb12009-08-21 13:58:41 -0700468
Jack Veenstra53175142009-06-01 21:27:01 -0700469 for (int i = 0; i < count; i++) {
470 View child = getChildTabViewAt(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800471 child.setEnabled(enabled);
472 }
473 }
474
475 @Override
476 public void addView(View child) {
477 if (child.getLayoutParams() == null) {
478 final LinearLayout.LayoutParams lp = new LayoutParams(
479 0,
Romain Guy980a9382010-01-08 15:06:28 -0800480 ViewGroup.LayoutParams.MATCH_PARENT, 1.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800481 lp.setMargins(0, 0, 0, 0);
482 child.setLayoutParams(lp);
483 }
484
485 // Ensure you can navigate to the tab with the keyboard, and you can touch it
486 child.setFocusable(true);
487 child.setClickable(true);
488
Jack Veenstra53175142009-06-01 21:27:01 -0700489 // If we have dividers between the tabs and we already have at least one
490 // tab, then add a divider before adding the next tab.
491 if (mDividerDrawable != null && getTabCount() > 0) {
Jack Veenstra5d5cd172009-07-29 11:00:10 -0700492 ImageView divider = new ImageView(mContext);
Jack Veenstra53175142009-06-01 21:27:01 -0700493 final LinearLayout.LayoutParams lp = new LayoutParams(
494 mDividerDrawable.getIntrinsicWidth(),
Romain Guy980a9382010-01-08 15:06:28 -0800495 LayoutParams.MATCH_PARENT);
Jack Veenstra53175142009-06-01 21:27:01 -0700496 lp.setMargins(0, 0, 0, 0);
497 divider.setLayoutParams(lp);
498 divider.setBackgroundDrawable(mDividerDrawable);
499 super.addView(divider);
500 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800501 super.addView(child);
502
503 // TODO: detect this via geometry with a tabwidget listener rather
504 // than potentially interfere with the view's listener
Jack Veenstra53175142009-06-01 21:27:01 -0700505 child.setOnClickListener(new TabClickListener(getTabCount() - 1));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800506 child.setOnFocusChangeListener(this);
507 }
508
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700509 @Override
510 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
511 // this class fires events only when tabs are focused or selected
512 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && isFocused()) {
513 return;
514 }
515 super.sendAccessibilityEventUnchecked(event);
516 }
517
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800518 /**
519 * Provides a way for {@link TabHost} to be notified that the user clicked on a tab indicator.
520 */
521 void setTabSelectionListener(OnTabSelectionChanged listener) {
522 mSelectionChangedListener = listener;
523 }
524
525 public void onFocusChange(View v, boolean hasFocus) {
Bjorn Bringertacdef592009-12-10 15:58:49 +0000526 if (v == this && hasFocus && getTabCount() > 0) {
Jack Veenstra53175142009-06-01 21:27:01 -0700527 getChildTabViewAt(mSelectedTab).requestFocus();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800528 return;
529 }
Evan Millar3730bb12009-08-21 13:58:41 -0700530
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800531 if (hasFocus) {
532 int i = 0;
Jack Veenstra53175142009-06-01 21:27:01 -0700533 int numTabs = getTabCount();
534 while (i < numTabs) {
535 if (getChildTabViewAt(i) == v) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800536 setCurrentTab(i);
537 mSelectionChangedListener.onTabSelectionChanged(i, false);
Svetoslav Ganov5ac413a2010-10-05 15:59:25 -0700538 if (isShown()) {
539 // a tab is focused so send an event to announce the tab widget state
540 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
541 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542 break;
543 }
544 i++;
545 }
546 }
547 }
548
549 // registered with each tab indicator so we can notify tab host
550 private class TabClickListener implements OnClickListener {
551
552 private final int mTabIndex;
553
554 private TabClickListener(int tabIndex) {
555 mTabIndex = tabIndex;
556 }
557
558 public void onClick(View v) {
559 mSelectionChangedListener.onTabSelectionChanged(mTabIndex, true);
560 }
561 }
562
563 /**
564 * Let {@link TabHost} know that the user clicked on a tab indicator.
565 */
566 static interface OnTabSelectionChanged {
567 /**
568 * Informs the TabHost which tab was selected. It also indicates
569 * if the tab was clicked/pressed or just focused into.
Evan Millar3730bb12009-08-21 13:58:41 -0700570 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800571 * @param tabIndex index of the tab that was selected
572 * @param clicked whether the selection changed due to a touch/click
573 * or due to focus entering the tab through navigation. Pass true
574 * if it was due to a press/click and false otherwise.
575 */
576 void onTabSelectionChanged(int tabIndex, boolean clicked);
577 }
578
579}
580