• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.widget;
18 
19 import android.R;
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.content.res.TypedArray;
23 import android.graphics.Canvas;
24 import android.graphics.Rect;
25 import android.graphics.drawable.Drawable;
26 import android.os.Build;
27 import android.util.AttributeSet;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.View.OnFocusChangeListener;
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  *
44  * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-tabwidget.html">Tab Layout
45  * tutorial</a>.</p>
46  *
47  * @attr ref android.R.styleable#TabWidget_divider
48  * @attr ref android.R.styleable#TabWidget_tabStripEnabled
49  * @attr ref android.R.styleable#TabWidget_tabStripLeft
50  * @attr ref android.R.styleable#TabWidget_tabStripRight
51  */
52 public class TabWidget extends LinearLayout implements OnFocusChangeListener {
53     private OnTabSelectionChanged mSelectionChangedListener;
54 
55     private int mSelectedTab = 0;
56 
57     private Drawable mLeftStrip;
58     private Drawable mRightStrip;
59 
60     private boolean mDrawBottomStrips = true;
61     private boolean mStripMoved;
62 
63     private Drawable mDividerDrawable;
64 
65     private final Rect mBounds = new Rect();
66 
TabWidget(Context context)67     public TabWidget(Context context) {
68         this(context, null);
69     }
70 
TabWidget(Context context, AttributeSet attrs)71     public TabWidget(Context context, AttributeSet attrs) {
72         this(context, attrs, com.android.internal.R.attr.tabWidgetStyle);
73     }
74 
TabWidget(Context context, AttributeSet attrs, int defStyle)75     public TabWidget(Context context, AttributeSet attrs, int defStyle) {
76         super(context, attrs);
77 
78         TypedArray a =
79             context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TabWidget,
80                     defStyle, 0);
81 
82         mDrawBottomStrips = a.getBoolean(R.styleable.TabWidget_tabStripEnabled, true);
83         mDividerDrawable = a.getDrawable(R.styleable.TabWidget_divider);
84         mLeftStrip = a.getDrawable(R.styleable.TabWidget_tabStripLeft);
85         mRightStrip = a.getDrawable(R.styleable.TabWidget_tabStripRight);
86 
87         a.recycle();
88 
89         initTabWidget();
90     }
91 
92     @Override
onSizeChanged(int w, int h, int oldw, int oldh)93     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
94         mStripMoved = true;
95         super.onSizeChanged(w, h, oldw, oldh);
96     }
97 
98     @Override
getChildDrawingOrder(int childCount, int i)99     protected int getChildDrawingOrder(int childCount, int i) {
100         // Always draw the selected tab last, so that drop shadows are drawn
101         // in the correct z-order.
102         if (i == childCount - 1) {
103             return mSelectedTab;
104         } else if (i >= mSelectedTab) {
105             return i + 1;
106         } else {
107             return i;
108         }
109     }
110 
initTabWidget()111     private void initTabWidget() {
112         setOrientation(LinearLayout.HORIZONTAL);
113         mGroupFlags |= FLAG_USE_CHILD_DRAWING_ORDER;
114 
115         final Context context = mContext;
116         final Resources resources = context.getResources();
117 
118         if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
119             // Donut apps get old color scheme
120             if (mLeftStrip == null) {
121                 mLeftStrip = resources.getDrawable(
122                         com.android.internal.R.drawable.tab_bottom_left_v4);
123             }
124             if (mRightStrip == null) {
125                 mRightStrip = resources.getDrawable(
126                         com.android.internal.R.drawable.tab_bottom_right_v4);
127             }
128         } else {
129             // Use modern color scheme for Eclair and beyond
130             if (mLeftStrip == null) {
131                 mLeftStrip = resources.getDrawable(
132                         com.android.internal.R.drawable.tab_bottom_left);
133             }
134             if (mRightStrip == null) {
135                 mRightStrip = resources.getDrawable(
136                         com.android.internal.R.drawable.tab_bottom_right);
137             }
138         }
139 
140         // Deal with focus, as we don't want the focus to go by default
141         // to a tab other than the current tab
142         setFocusable(true);
143         setOnFocusChangeListener(this);
144     }
145 
146     /**
147      * Returns the tab indicator view at the given index.
148      *
149      * @param index the zero-based index of the tab indicator view to return
150      * @return the tab indicator view at the given index
151      */
getChildTabViewAt(int index)152     public View getChildTabViewAt(int index) {
153         // If we are using dividers, then instead of tab views at 0, 1, 2, ...
154         // we have tab views at 0, 2, 4, ...
155         if (mDividerDrawable != null) {
156             index *= 2;
157         }
158         return getChildAt(index);
159     }
160 
161     /**
162      * Returns the number of tab indicator views.
163      * @return the number of tab indicator views.
164      */
getTabCount()165     public int getTabCount() {
166         int children = getChildCount();
167 
168         // If we have dividers, then we will always have an odd number of
169         // children: 1, 3, 5, ... and we want to convert that sequence to
170         // this: 1, 2, 3, ...
171         if (mDividerDrawable != null) {
172             children = (children + 1) / 2;
173         }
174         return children;
175     }
176 
177     /**
178      * Sets the drawable to use as a divider between the tab indicators.
179      * @param drawable the divider drawable
180      */
setDividerDrawable(Drawable drawable)181     public void setDividerDrawable(Drawable drawable) {
182         mDividerDrawable = drawable;
183         requestLayout();
184         invalidate();
185     }
186 
187     /**
188      * Sets the drawable to use as a divider between the tab indicators.
189      * @param resId the resource identifier of the drawable to use as a
190      * divider.
191      */
setDividerDrawable(int resId)192     public void setDividerDrawable(int resId) {
193         mDividerDrawable = mContext.getResources().getDrawable(resId);
194         requestLayout();
195         invalidate();
196     }
197 
198     /**
199      * Sets the drawable to use as the left part of the strip below the
200      * tab indicators.
201      * @param drawable the left strip drawable
202      */
setLeftStripDrawable(Drawable drawable)203     public void setLeftStripDrawable(Drawable drawable) {
204         mLeftStrip = drawable;
205         requestLayout();
206         invalidate();
207     }
208 
209     /**
210      * Sets the drawable to use as the left part of the strip below the
211      * tab indicators.
212      * @param resId the resource identifier of the drawable to use as the
213      * left strip drawable
214      */
setLeftStripDrawable(int resId)215     public void setLeftStripDrawable(int resId) {
216         mLeftStrip = mContext.getResources().getDrawable(resId);
217         requestLayout();
218         invalidate();
219     }
220 
221     /**
222      * Sets the drawable to use as the right part of the strip below the
223      * tab indicators.
224      * @param drawable the right strip drawable
225      */
setRightStripDrawable(Drawable drawable)226     public void setRightStripDrawable(Drawable drawable) {
227         mRightStrip = drawable;
228         requestLayout();
229         invalidate();    }
230 
231     /**
232      * Sets the drawable to use as the right part of the strip below the
233      * tab indicators.
234      * @param resId the resource identifier of the drawable to use as the
235      * right strip drawable
236      */
setRightStripDrawable(int resId)237     public void setRightStripDrawable(int resId) {
238         mRightStrip = mContext.getResources().getDrawable(resId);
239         requestLayout();
240         invalidate();
241     }
242 
243     /**
244      * Controls whether the bottom strips on the tab indicators are drawn or
245      * not.  The default is to draw them.  If the user specifies a custom
246      * view for the tab indicators, then the TabHost class calls this method
247      * to disable drawing of the bottom strips.
248      * @param stripEnabled true if the bottom strips should be drawn.
249      */
setStripEnabled(boolean stripEnabled)250     public void setStripEnabled(boolean stripEnabled) {
251         mDrawBottomStrips = stripEnabled;
252         invalidate();
253     }
254 
255     /**
256      * Indicates whether the bottom strips on the tab indicators are drawn
257      * or not.
258      */
isStripEnabled()259     public boolean isStripEnabled() {
260         return mDrawBottomStrips;
261     }
262 
263     @Override
childDrawableStateChanged(View child)264     public void childDrawableStateChanged(View child) {
265         if (getTabCount() > 0 && child == getChildTabViewAt(mSelectedTab)) {
266             // To make sure that the bottom strip is redrawn
267             invalidate();
268         }
269         super.childDrawableStateChanged(child);
270     }
271 
272     @Override
dispatchDraw(Canvas canvas)273     public void dispatchDraw(Canvas canvas) {
274         super.dispatchDraw(canvas);
275 
276         // Do nothing if there are no tabs.
277         if (getTabCount() == 0) return;
278 
279         // If the user specified a custom view for the tab indicators, then
280         // do not draw the bottom strips.
281         if (!mDrawBottomStrips) {
282             // Skip drawing the bottom strips.
283             return;
284         }
285 
286         final View selectedChild = getChildTabViewAt(mSelectedTab);
287 
288         final Drawable leftStrip = mLeftStrip;
289         final Drawable rightStrip = mRightStrip;
290 
291         leftStrip.setState(selectedChild.getDrawableState());
292         rightStrip.setState(selectedChild.getDrawableState());
293 
294         if (mStripMoved) {
295             final Rect bounds = mBounds;
296             bounds.left = selectedChild.getLeft();
297             bounds.right = selectedChild.getRight();
298             final int myHeight = getHeight();
299             leftStrip.setBounds(Math.min(0, bounds.left - leftStrip.getIntrinsicWidth()),
300                     myHeight - leftStrip.getIntrinsicHeight(), bounds.left, myHeight);
301             rightStrip.setBounds(bounds.right, myHeight - rightStrip.getIntrinsicHeight(),
302                     Math.max(getWidth(), bounds.right + rightStrip.getIntrinsicWidth()), myHeight);
303             mStripMoved = false;
304         }
305 
306         leftStrip.draw(canvas);
307         rightStrip.draw(canvas);
308     }
309 
310     /**
311      * Sets the current tab.
312      * This method is used to bring a tab to the front of the Widget,
313      * and is used to post to the rest of the UI that a different tab
314      * has been brought to the foreground.
315      *
316      * Note, this is separate from the traditional "focus" that is
317      * employed from the view logic.
318      *
319      * For instance, if we have a list in a tabbed view, a user may be
320      * navigating up and down the list, moving the UI focus (orange
321      * highlighting) through the list items.  The cursor movement does
322      * not effect the "selected" tab though, because what is being
323      * scrolled through is all on the same tab.  The selected tab only
324      * changes when we navigate between tabs (moving from the list view
325      * to the next tabbed view, in this example).
326      *
327      * To move both the focus AND the selected tab at once, please use
328      * {@link #setCurrentTab}. Normally, the view logic takes care of
329      * adjusting the focus, so unless you're circumventing the UI,
330      * you'll probably just focus your interest here.
331      *
332      *  @param index The tab that you want to indicate as the selected
333      *  tab (tab brought to the front of the widget)
334      *
335      *  @see #focusCurrentTab
336      */
setCurrentTab(int index)337     public void setCurrentTab(int index) {
338         if (index < 0 || index >= getTabCount()) {
339             return;
340         }
341 
342         getChildTabViewAt(mSelectedTab).setSelected(false);
343         mSelectedTab = index;
344         getChildTabViewAt(mSelectedTab).setSelected(true);
345         mStripMoved = true;
346     }
347 
348     /**
349      * Sets the current tab and focuses the UI on it.
350      * This method makes sure that the focused tab matches the selected
351      * tab, normally at {@link #setCurrentTab}.  Normally this would not
352      * be an issue if we go through the UI, since the UI is responsible
353      * for calling TabWidget.onFocusChanged(), but in the case where we
354      * are selecting the tab programmatically, we'll need to make sure
355      * focus keeps up.
356      *
357      *  @param index The tab that you want focused (highlighted in orange)
358      *  and selected (tab brought to the front of the widget)
359      *
360      *  @see #setCurrentTab
361      */
focusCurrentTab(int index)362     public void focusCurrentTab(int index) {
363         final int oldTab = mSelectedTab;
364 
365         // set the tab
366         setCurrentTab(index);
367 
368         // change the focus if applicable.
369         if (oldTab != index) {
370             getChildTabViewAt(index).requestFocus();
371         }
372     }
373 
374     @Override
setEnabled(boolean enabled)375     public void setEnabled(boolean enabled) {
376         super.setEnabled(enabled);
377         int count = getTabCount();
378 
379         for (int i = 0; i < count; i++) {
380             View child = getChildTabViewAt(i);
381             child.setEnabled(enabled);
382         }
383     }
384 
385     @Override
addView(View child)386     public void addView(View child) {
387         if (child.getLayoutParams() == null) {
388             final LinearLayout.LayoutParams lp = new LayoutParams(
389                     0,
390                     ViewGroup.LayoutParams.MATCH_PARENT, 1.0f);
391             lp.setMargins(0, 0, 0, 0);
392             child.setLayoutParams(lp);
393         }
394 
395         // Ensure you can navigate to the tab with the keyboard, and you can touch it
396         child.setFocusable(true);
397         child.setClickable(true);
398 
399         // If we have dividers between the tabs and we already have at least one
400         // tab, then add a divider before adding the next tab.
401         if (mDividerDrawable != null && getTabCount() > 0) {
402             ImageView divider = new ImageView(mContext);
403             final LinearLayout.LayoutParams lp = new LayoutParams(
404                     mDividerDrawable.getIntrinsicWidth(),
405                     LayoutParams.MATCH_PARENT);
406             lp.setMargins(0, 0, 0, 0);
407             divider.setLayoutParams(lp);
408             divider.setBackgroundDrawable(mDividerDrawable);
409             super.addView(divider);
410         }
411         super.addView(child);
412 
413         // TODO: detect this via geometry with a tabwidget listener rather
414         // than potentially interfere with the view's listener
415         child.setOnClickListener(new TabClickListener(getTabCount() - 1));
416         child.setOnFocusChangeListener(this);
417     }
418 
419     /**
420      * Provides a way for {@link TabHost} to be notified that the user clicked on a tab indicator.
421      */
setTabSelectionListener(OnTabSelectionChanged listener)422     void setTabSelectionListener(OnTabSelectionChanged listener) {
423         mSelectionChangedListener = listener;
424     }
425 
onFocusChange(View v, boolean hasFocus)426     public void onFocusChange(View v, boolean hasFocus) {
427         if (v == this && hasFocus && getTabCount() > 0) {
428             getChildTabViewAt(mSelectedTab).requestFocus();
429             return;
430         }
431 
432         if (hasFocus) {
433             int i = 0;
434             int numTabs = getTabCount();
435             while (i < numTabs) {
436                 if (getChildTabViewAt(i) == v) {
437                     setCurrentTab(i);
438                     mSelectionChangedListener.onTabSelectionChanged(i, false);
439                     break;
440                 }
441                 i++;
442             }
443         }
444     }
445 
446     // registered with each tab indicator so we can notify tab host
447     private class TabClickListener implements OnClickListener {
448 
449         private final int mTabIndex;
450 
TabClickListener(int tabIndex)451         private TabClickListener(int tabIndex) {
452             mTabIndex = tabIndex;
453         }
454 
onClick(View v)455         public void onClick(View v) {
456             mSelectionChangedListener.onTabSelectionChanged(mTabIndex, true);
457         }
458     }
459 
460     /**
461      * Let {@link TabHost} know that the user clicked on a tab indicator.
462      */
463     static interface OnTabSelectionChanged {
464         /**
465          * Informs the TabHost which tab was selected. It also indicates
466          * if the tab was clicked/pressed or just focused into.
467          *
468          * @param tabIndex index of the tab that was selected
469          * @param clicked whether the selection changed due to a touch/click
470          * or due to focus entering the tab through navigation. Pass true
471          * if it was due to a press/click and false otherwise.
472          */
onTabSelectionChanged(int tabIndex, boolean clicked)473         void onTabSelectionChanged(int tabIndex, boolean clicked);
474     }
475 
476 }
477 
478