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