TabWidget.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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
53    public TabWidget(Context context) {
54        this(context, null);
55    }
56
57    public TabWidget(Context context, AttributeSet attrs) {
58        this(context, attrs, com.android.internal.R.attr.tabWidgetStyle);
59    }
60
61    public TabWidget(Context context, AttributeSet attrs, int defStyle) {
62        super(context, attrs);
63        initTabWidget();
64
65        TypedArray a =
66            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TabWidget,
67                    defStyle, 0);
68
69        a.recycle();
70    }
71
72    @Override
73    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
74        mStripMoved = true;
75        super.onSizeChanged(w, h, oldw, oldh);
76    }
77
78    private void initTabWidget() {
79        setOrientation(LinearLayout.HORIZONTAL);
80        mBottomLeftStrip = mContext.getResources().getDrawable(
81                com.android.internal.R.drawable.tab_bottom_left);
82        mBottomRightStrip = mContext.getResources().getDrawable(
83                com.android.internal.R.drawable.tab_bottom_right);
84        // Deal with focus, as we don't want the focus to go by default
85        // to a tab other than the current tab
86        setFocusable(true);
87        setOnFocusChangeListener(this);
88    }
89
90    @Override
91    public void childDrawableStateChanged(View child) {
92        if (child == getChildAt(mSelectedTab)) {
93            // To make sure that the bottom strip is redrawn
94            invalidate();
95        }
96        super.childDrawableStateChanged(child);
97    }
98
99    @Override
100    public void dispatchDraw(Canvas canvas) {
101        super.dispatchDraw(canvas);
102
103        View selectedChild = getChildAt(mSelectedTab);
104
105        mBottomLeftStrip.setState(selectedChild.getDrawableState());
106        mBottomRightStrip.setState(selectedChild.getDrawableState());
107
108        if (mStripMoved) {
109            Rect selBounds = new Rect(); // Bounds of the selected tab indicator
110            selBounds.left = selectedChild.getLeft();
111            selBounds.right = selectedChild.getRight();
112            final int myHeight = getHeight();
113            mBottomLeftStrip.setBounds(
114                    Math.min(0, selBounds.left
115                                 - mBottomLeftStrip.getIntrinsicWidth()),
116                    myHeight - mBottomLeftStrip.getIntrinsicHeight(),
117                    selBounds.left,
118                    getHeight());
119            mBottomRightStrip.setBounds(
120                    selBounds.right,
121                    myHeight - mBottomRightStrip.getIntrinsicHeight(),
122                    Math.max(getWidth(),
123                            selBounds.right + mBottomRightStrip.getIntrinsicWidth()),
124                    myHeight);
125            mStripMoved = false;
126        }
127
128        mBottomLeftStrip.draw(canvas);
129        mBottomRightStrip.draw(canvas);
130    }
131
132    /**
133     * Sets the current tab.
134     * This method is used to bring a tab to the front of the Widget,
135     * and is used to post to the rest of the UI that a different tab
136     * has been brought to the foreground.
137     *
138     * Note, this is separate from the traditional "focus" that is
139     * employed from the view logic.
140     *
141     * For instance, if we have a list in a tabbed view, a user may be
142     * navigating up and down the list, moving the UI focus (orange
143     * highlighting) through the list items.  The cursor movement does
144     * not effect the "selected" tab though, because what is being
145     * scrolled through is all on the same tab.  The selected tab only
146     * changes when we navigate between tabs (moving from the list view
147     * to the next tabbed view, in this example).
148     *
149     * To move both the focus AND the selected tab at once, please use
150     * {@link #setCurrentTab}. Normally, the view logic takes care of
151     * adjusting the focus, so unless you're circumventing the UI,
152     * you'll probably just focus your interest here.
153     *
154     *  @param index The tab that you want to indicate as the selected
155     *  tab (tab brought to the front of the widget)
156     *
157     *  @see #focusCurrentTab
158     */
159    public void setCurrentTab(int index) {
160        if (index < 0 || index >= getChildCount()) {
161            return;
162        }
163
164        getChildAt(mSelectedTab).setSelected(false);
165        mSelectedTab = index;
166        getChildAt(mSelectedTab).setSelected(true);
167        mStripMoved = true;
168    }
169
170    /**
171     * Sets the current tab and focuses the UI on it.
172     * This method makes sure that the focused tab matches the selected
173     * tab, normally at {@link #setCurrentTab}.  Normally this would not
174     * be an issue if we go through the UI, since the UI is responsible
175     * for calling TabWidget.onFocusChanged(), but in the case where we
176     * are selecting the tab programmatically, we'll need to make sure
177     * focus keeps up.
178     *
179     *  @param index The tab that you want focused (highlighted in orange)
180     *  and selected (tab brought to the front of the widget)
181     *
182     *  @see #setCurrentTab
183     */
184    public void focusCurrentTab(int index) {
185        final int oldTab = mSelectedTab;
186
187        // set the tab
188        setCurrentTab(index);
189
190        // change the focus if applicable.
191        if (oldTab != index) {
192            getChildAt(index).requestFocus();
193        }
194    }
195
196    @Override
197    public void setEnabled(boolean enabled) {
198        super.setEnabled(enabled);
199        int count = getChildCount();
200
201        for (int i=0; i<count; i++) {
202            View child = getChildAt(i);
203            child.setEnabled(enabled);
204        }
205    }
206
207    @Override
208    public void addView(View child) {
209        if (child.getLayoutParams() == null) {
210            final LinearLayout.LayoutParams lp = new LayoutParams(
211                    0,
212                    ViewGroup.LayoutParams.WRAP_CONTENT, 1);
213            lp.setMargins(0, 0, 0, 0);
214            child.setLayoutParams(lp);
215        }
216
217        // Ensure you can navigate to the tab with the keyboard, and you can touch it
218        child.setFocusable(true);
219        child.setClickable(true);
220
221        super.addView(child);
222
223        // TODO: detect this via geometry with a tabwidget listener rather
224        // than potentially interfere with the view's listener
225        child.setOnClickListener(new TabClickListener(getChildCount() - 1));
226        child.setOnFocusChangeListener(this);
227    }
228
229
230
231
232    /**
233     * Provides a way for {@link TabHost} to be notified that the user clicked on a tab indicator.
234     */
235    void setTabSelectionListener(OnTabSelectionChanged listener) {
236        mSelectionChangedListener = listener;
237    }
238
239    public void onFocusChange(View v, boolean hasFocus) {
240        if (v == this && hasFocus) {
241            getChildAt(mSelectedTab).requestFocus();
242            return;
243        }
244
245        if (hasFocus) {
246            int i = 0;
247            while (i < getChildCount()) {
248                if (getChildAt(i) == v) {
249                    setCurrentTab(i);
250                    mSelectionChangedListener.onTabSelectionChanged(i, false);
251                    break;
252                }
253                i++;
254            }
255        }
256    }
257
258    // registered with each tab indicator so we can notify tab host
259    private class TabClickListener implements OnClickListener {
260
261        private final int mTabIndex;
262
263        private TabClickListener(int tabIndex) {
264            mTabIndex = tabIndex;
265        }
266
267        public void onClick(View v) {
268            mSelectionChangedListener.onTabSelectionChanged(mTabIndex, true);
269        }
270    }
271
272    /**
273     * Let {@link TabHost} know that the user clicked on a tab indicator.
274     */
275    static interface OnTabSelectionChanged {
276        /**
277         * Informs the TabHost which tab was selected. It also indicates
278         * if the tab was clicked/pressed or just focused into.
279         *
280         * @param tabIndex index of the tab that was selected
281         * @param clicked whether the selection changed due to a touch/click
282         * or due to focus entering the tab through navigation. Pass true
283         * if it was due to a press/click and false otherwise.
284         */
285        void onTabSelectionChanged(int tabIndex, boolean clicked);
286    }
287
288}
289
290