TabWidget.java revision 3730bb1cd59bcfa9f5cad8361997b84113ed5923
1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.widget; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.graphics.Canvas; 22import android.graphics.Rect; 23import android.graphics.drawable.Drawable; 24import android.util.AttributeSet; 25import android.util.Log; 26import android.view.View; 27import android.view.ViewGroup; 28import android.view.View.OnFocusChangeListener; 29 30 31 32/** 33 * 34 * Displays a list of tab labels representing each page in the parent's tab 35 * collection. The container object for this widget is 36 * {@link android.widget.TabHost TabHost}. When the user selects a tab, this 37 * object sends a message to the parent container, TabHost, to tell it to switch 38 * the displayed page. You typically won't use many methods directly on this 39 * object. The container TabHost is used to add labels, add the callback 40 * handler, and manage callbacks. You might call this object to iterate the list 41 * of tabs, or to tweak the layout of the tab list, but most methods should be 42 * called on the containing TabHost object. 43 */ 44public class TabWidget extends LinearLayout implements OnFocusChangeListener { 45 46 47 private OnTabSelectionChanged mSelectionChangedListener; 48 private int mSelectedTab = 0; 49 private Drawable mBottomLeftStrip; 50 private Drawable mBottomRightStrip; 51 private boolean mStripMoved; 52 private Drawable mDividerDrawable; 53 private boolean mDrawBottomStrips = true; 54 55 public TabWidget(Context context) { 56 this(context, null); 57 } 58 59 public TabWidget(Context context, AttributeSet attrs) { 60 this(context, attrs, com.android.internal.R.attr.tabWidgetStyle); 61 } 62 63 public TabWidget(Context context, AttributeSet attrs, int defStyle) { 64 super(context, attrs); 65 initTabWidget(); 66 67 TypedArray a = 68 context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TabWidget, 69 defStyle, 0); 70 71 a.recycle(); 72 } 73 74 @Override 75 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 76 mStripMoved = true; 77 super.onSizeChanged(w, h, oldw, oldh); 78 } 79 80 @Override 81 protected int getChildDrawingOrder(int childCount, int i) { 82 // Always draw the selected tab last, so that drop shadows are drawn 83 // in the correct z-order. 84 if (i == childCount - 1) { 85 return mSelectedTab; 86 } else if (i >= mSelectedTab) { 87 return i + 1; 88 } else { 89 return i; 90 } 91 } 92 93 private void initTabWidget() { 94 setOrientation(LinearLayout.HORIZONTAL); 95 mGroupFlags |= FLAG_USE_CHILD_DRAWING_ORDER; 96 97 mBottomLeftStrip = mContext.getResources().getDrawable( 98 com.android.internal.R.drawable.tab_bottom_left); 99 mBottomRightStrip = mContext.getResources().getDrawable( 100 com.android.internal.R.drawable.tab_bottom_right); 101 // Deal with focus, as we don't want the focus to go by default 102 // to a tab other than the current tab 103 setFocusable(true); 104 setOnFocusChangeListener(this); 105 } 106 107 /** 108 * Returns the tab indicator view at the given index. 109 * 110 * @param index the zero-based index of the tab indicator view to return 111 * @return the tab indicator view at the given index 112 */ 113 public View getChildTabViewAt(int index) { 114 // If we are using dividers, then instead of tab views at 0, 1, 2, ... 115 // we have tab views at 0, 2, 4, ... 116 if (mDividerDrawable != null) { 117 index *= 2; 118 } 119 return getChildAt(index); 120 } 121 122 /** 123 * Returns the number of tab indicator views. 124 * @return the number of tab indicator views. 125 */ 126 public int getTabCount() { 127 int children = getChildCount(); 128 129 // If we have dividers, then we will always have an odd number of 130 // children: 1, 3, 5, ... and we want to convert that sequence to 131 // this: 1, 2, 3, ... 132 if (mDividerDrawable != null) { 133 children = (children + 1) / 2; 134 } 135 return children; 136 } 137 138 /** 139 * Sets the drawable to use as a divider between the tab indicators. 140 * @param drawable the divider drawable 141 */ 142 public void setDividerDrawable(Drawable drawable) { 143 mDividerDrawable = drawable; 144 } 145 146 /** 147 * Sets the drawable to use as a divider between the tab indicators. 148 * @param resId the resource identifier of the drawable to use as a 149 * divider. 150 */ 151 public void setDividerDrawable(int resId) { 152 mDividerDrawable = mContext.getResources().getDrawable(resId); 153 } 154 155 /** 156 * Controls whether the bottom strips on the tab indicators are drawn or 157 * not. The default is to draw them. If the user specifies a custom 158 * view for the tab indicators, then the TabHost class calls this method 159 * to disable drawing of the bottom strips. 160 * @param drawBottomStrips true if the bottom strips should be drawn. 161 */ 162 void setDrawBottomStrips(boolean drawBottomStrips) { 163 mDrawBottomStrips = drawBottomStrips; 164 } 165 166 @Override 167 public void childDrawableStateChanged(View child) { 168 if (child == getChildTabViewAt(mSelectedTab)) { 169 // To make sure that the bottom strip is redrawn 170 invalidate(); 171 } 172 super.childDrawableStateChanged(child); 173 } 174 175 @Override 176 public void dispatchDraw(Canvas canvas) { 177 super.dispatchDraw(canvas); 178 179 // If the user specified a custom view for the tab indicators, then 180 // do not draw the bottom strips. 181 if (!mDrawBottomStrips) { 182 // Skip drawing the bottom strips. 183 return; 184 } 185 186 View selectedChild = getChildTabViewAt(mSelectedTab); 187 188 mBottomLeftStrip.setState(selectedChild.getDrawableState()); 189 mBottomRightStrip.setState(selectedChild.getDrawableState()); 190 191 if (mStripMoved) { 192 Rect selBounds = new Rect(); // Bounds of the selected tab indicator 193 selBounds.left = selectedChild.getLeft(); 194 selBounds.right = selectedChild.getRight(); 195 final int myHeight = getHeight(); 196 mBottomLeftStrip.setBounds( 197 Math.min(0, selBounds.left 198 - mBottomLeftStrip.getIntrinsicWidth()), 199 myHeight - mBottomLeftStrip.getIntrinsicHeight(), 200 selBounds.left, 201 getHeight()); 202 mBottomRightStrip.setBounds( 203 selBounds.right, 204 myHeight - mBottomRightStrip.getIntrinsicHeight(), 205 Math.max(getWidth(), 206 selBounds.right + mBottomRightStrip.getIntrinsicWidth()), 207 myHeight); 208 mStripMoved = false; 209 } 210 211 mBottomLeftStrip.draw(canvas); 212 mBottomRightStrip.draw(canvas); 213 } 214 215 /** 216 * Sets the current tab. 217 * This method is used to bring a tab to the front of the Widget, 218 * and is used to post to the rest of the UI that a different tab 219 * has been brought to the foreground. 220 * 221 * Note, this is separate from the traditional "focus" that is 222 * employed from the view logic. 223 * 224 * For instance, if we have a list in a tabbed view, a user may be 225 * navigating up and down the list, moving the UI focus (orange 226 * highlighting) through the list items. The cursor movement does 227 * not effect the "selected" tab though, because what is being 228 * scrolled through is all on the same tab. The selected tab only 229 * changes when we navigate between tabs (moving from the list view 230 * to the next tabbed view, in this example). 231 * 232 * To move both the focus AND the selected tab at once, please use 233 * {@link #setCurrentTab}. Normally, the view logic takes care of 234 * adjusting the focus, so unless you're circumventing the UI, 235 * you'll probably just focus your interest here. 236 * 237 * @param index The tab that you want to indicate as the selected 238 * tab (tab brought to the front of the widget) 239 * 240 * @see #focusCurrentTab 241 */ 242 public void setCurrentTab(int index) { 243 if (index < 0 || index >= getTabCount()) { 244 return; 245 } 246 247 getChildTabViewAt(mSelectedTab).setSelected(false); 248 mSelectedTab = index; 249 getChildTabViewAt(mSelectedTab).setSelected(true); 250 mStripMoved = true; 251 } 252 253 /** 254 * Sets the current tab and focuses the UI on it. 255 * This method makes sure that the focused tab matches the selected 256 * tab, normally at {@link #setCurrentTab}. Normally this would not 257 * be an issue if we go through the UI, since the UI is responsible 258 * for calling TabWidget.onFocusChanged(), but in the case where we 259 * are selecting the tab programmatically, we'll need to make sure 260 * focus keeps up. 261 * 262 * @param index The tab that you want focused (highlighted in orange) 263 * and selected (tab brought to the front of the widget) 264 * 265 * @see #setCurrentTab 266 */ 267 public void focusCurrentTab(int index) { 268 final int oldTab = mSelectedTab; 269 270 // set the tab 271 setCurrentTab(index); 272 273 // change the focus if applicable. 274 if (oldTab != index) { 275 getChildTabViewAt(index).requestFocus(); 276 } 277 } 278 279 @Override 280 public void setEnabled(boolean enabled) { 281 super.setEnabled(enabled); 282 int count = getTabCount(); 283 284 for (int i = 0; i < count; i++) { 285 View child = getChildTabViewAt(i); 286 child.setEnabled(enabled); 287 } 288 } 289 290 @Override 291 public void addView(View child) { 292 if (child.getLayoutParams() == null) { 293 final LinearLayout.LayoutParams lp = new LayoutParams( 294 0, 295 ViewGroup.LayoutParams.FILL_PARENT, 1.0f); 296 lp.setMargins(0, 0, 0, 0); 297 child.setLayoutParams(lp); 298 } 299 300 // Ensure you can navigate to the tab with the keyboard, and you can touch it 301 child.setFocusable(true); 302 child.setClickable(true); 303 304 // If we have dividers between the tabs and we already have at least one 305 // tab, then add a divider before adding the next tab. 306 if (mDividerDrawable != null && getTabCount() > 0) { 307 ImageView divider = new ImageView(mContext); 308 final LinearLayout.LayoutParams lp = new LayoutParams( 309 mDividerDrawable.getIntrinsicWidth(), 310 LayoutParams.FILL_PARENT); 311 lp.setMargins(0, 0, 0, 0); 312 divider.setLayoutParams(lp); 313 divider.setBackgroundDrawable(mDividerDrawable); 314 super.addView(divider); 315 } 316 super.addView(child); 317 318 // TODO: detect this via geometry with a tabwidget listener rather 319 // than potentially interfere with the view's listener 320 child.setOnClickListener(new TabClickListener(getTabCount() - 1)); 321 child.setOnFocusChangeListener(this); 322 } 323 324 /** 325 * Provides a way for {@link TabHost} to be notified that the user clicked on a tab indicator. 326 */ 327 void setTabSelectionListener(OnTabSelectionChanged listener) { 328 mSelectionChangedListener = listener; 329 } 330 331 public void onFocusChange(View v, boolean hasFocus) { 332 if (v == this && hasFocus) { 333 getChildTabViewAt(mSelectedTab).requestFocus(); 334 return; 335 } 336 337 if (hasFocus) { 338 int i = 0; 339 int numTabs = getTabCount(); 340 while (i < numTabs) { 341 if (getChildTabViewAt(i) == v) { 342 setCurrentTab(i); 343 mSelectionChangedListener.onTabSelectionChanged(i, false); 344 break; 345 } 346 i++; 347 } 348 } 349 } 350 351 // registered with each tab indicator so we can notify tab host 352 private class TabClickListener implements OnClickListener { 353 354 private final int mTabIndex; 355 356 private TabClickListener(int tabIndex) { 357 mTabIndex = tabIndex; 358 } 359 360 public void onClick(View v) { 361 mSelectionChangedListener.onTabSelectionChanged(mTabIndex, true); 362 } 363 } 364 365 /** 366 * Let {@link TabHost} know that the user clicked on a tab indicator. 367 */ 368 static interface OnTabSelectionChanged { 369 /** 370 * Informs the TabHost which tab was selected. It also indicates 371 * if the tab was clicked/pressed or just focused into. 372 * 373 * @param tabIndex index of the tab that was selected 374 * @param clicked whether the selection changed due to a touch/click 375 * or due to focus entering the tab through navigation. Pass true 376 * if it was due to a press/click and false otherwise. 377 */ 378 void onTabSelectionChanged(int tabIndex, boolean clicked); 379 } 380 381} 382 383