blob: f6f1ab1ae68f00705573f7a049376e97dca64149 [file] [log] [blame]
Evan Millar7911ff52009-07-21 15:55:18 -07001/*
2 * Copyright (C) 2009 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 com.android.contacts;
18
19import android.content.Context;
20import android.graphics.Canvas;
21import android.graphics.drawable.Drawable;
22import android.util.AttributeSet;
23import android.util.Log;
24import android.view.InflateException;
25import android.view.KeyEvent;
26import android.view.LayoutInflater;
27import android.view.MotionEvent;
28import android.view.View;
29import android.view.ViewGroup;
30import android.view.View.OnClickListener;
31import android.widget.HorizontalScrollView;
32import android.widget.LinearLayout;
33import android.widget.RelativeLayout;
34import android.widget.TabHost;
35
36/*
37 * Tab widget that can contain more tabs than can fit on screen at once and scroll over them.
38 */
39public class ScrollingTabWidget extends RelativeLayout
40 implements OnClickListener {
41
42 private static final String TAG = "ScrollingTabWidget";
43
44 private OnTabSelectionChangedListener mSelectionChangedListener;
45 private int mSelectedTab = 0;
46 private LinearLayout mLeftArrowView;
47 private LinearLayout mRightArrowView;
48 private NoDragHorizontalScrollView mTabsScrollWrapper;
49 private LinearLayout mTabsView;
50 private LayoutInflater mInflater;
51 private Drawable mDividerDrawable;
52
53 // Keeps track of the left most visible tab.
54 private int mLeftMostVisibleTabIndex = 0;
55
56 public ScrollingTabWidget(Context context) {
57 this(context, null);
58 }
59
60 public ScrollingTabWidget(Context context, AttributeSet attrs) {
61 this(context, attrs, 0);
62 }
63
64 public ScrollingTabWidget(Context context, AttributeSet attrs, int defStyle) {
65 super(context, attrs);
66
67 mInflater = (LayoutInflater) mContext.getSystemService(
68 Context.LAYOUT_INFLATER_SERVICE);
69
70 mLeftArrowView = (LinearLayout) mInflater.inflate(R.layout.tab_left_arrow, this, false);
71 mLeftArrowView.setOnClickListener(this);
72 mRightArrowView = (LinearLayout) mInflater.inflate(R.layout.tab_right_arrow, this, false);
73 mRightArrowView.setOnClickListener(this);
74 mTabsScrollWrapper = (NoDragHorizontalScrollView) mInflater.inflate(
75 R.layout.tab_layout, this, false);
76 mTabsView = (LinearLayout) mTabsScrollWrapper.findViewById(android.R.id.tabs);
77 mDividerDrawable = mContext.getResources().getDrawable(
78 R.drawable.tab_divider);
79
80 mLeftArrowView.setVisibility(View.INVISIBLE);
81 mRightArrowView.setVisibility(View.INVISIBLE);
82
83 addView(mTabsScrollWrapper);
84 addView(mLeftArrowView);
85 addView(mRightArrowView);
86 }
87
88 protected void updateArrowVisibility() {
89 int scrollViewLeftEdge = mTabsScrollWrapper.getScrollX();
90 int tabsViewLeftEdge = mTabsView.getLeft();
91 int scrollViewRightEdge = scrollViewLeftEdge + mTabsScrollWrapper.getWidth();
92 int tabsViewRightEdge = mTabsView.getRight();
93
94 int rightArrowCurrentVisibility = mRightArrowView.getVisibility();
95 if (scrollViewRightEdge == tabsViewRightEdge
96 && rightArrowCurrentVisibility == View.VISIBLE) {
97 mRightArrowView.setVisibility(View.INVISIBLE);
98 } else if (scrollViewRightEdge < tabsViewRightEdge
99 && rightArrowCurrentVisibility != View.VISIBLE) {
100 mRightArrowView.setVisibility(View.VISIBLE);
101 }
102
103 int leftArrowCurrentVisibility = mLeftArrowView.getVisibility();
104 if (scrollViewLeftEdge == tabsViewLeftEdge
105 && leftArrowCurrentVisibility == View.VISIBLE) {
106 mLeftArrowView.setVisibility(View.INVISIBLE);
107 } else if (scrollViewLeftEdge > tabsViewLeftEdge
108 && leftArrowCurrentVisibility != View.VISIBLE) {
109 mLeftArrowView.setVisibility(View.VISIBLE);
110 }
111 }
112
113 /**
114 * Returns the tab indicator view at the given index.
115 *
116 * @param index the zero-based index of the tab indicator view to return
117 * @return the tab indicator view at the given index
118 */
119 public View getChildTabViewAt(int index) {
120 return mTabsView.getChildAt(index*2);
121 }
122
123 /**
124 * Returns the number of tab indicator views.
125 *
126 * @return the number of tab indicator views.
127 */
128 public int getTabCount() {
129 int children = mTabsView.getChildCount();
130 return (children + 1) / 2;
131 }
132
133 public void removeAllTabs() {
134 mTabsView.removeAllViews();
135 }
136
137 @Override
138 public void dispatchDraw(Canvas canvas) {
139 updateArrowVisibility();
140 super.dispatchDraw(canvas);
141 }
142
143 /**
144 * Sets the current tab.
145 * This method is used to bring a tab to the front of the Widget,
146 * and is used to post to the rest of the UI that a different tab
147 * has been brought to the foreground.
148 *
149 * Note, this is separate from the traditional "focus" that is
150 * employed from the view logic.
151 *
152 * For instance, if we have a list in a tabbed view, a user may be
153 * navigating up and down the list, moving the UI focus (orange
154 * highlighting) through the list items. The cursor movement does
155 * not effect the "selected" tab though, because what is being
156 * scrolled through is all on the same tab. The selected tab only
157 * changes when we navigate between tabs (moving from the list view
158 * to the next tabbed view, in this example).
159 *
160 * To move both the focus AND the selected tab at once, please use
161 * {@link #focusCurrentTab}. Normally, the view logic takes care of
162 * adjusting the focus, so unless you're circumventing the UI,
163 * you'll probably just focus your interest here.
164 *
165 * @param index The tab that you want to indicate as the selected
166 * tab (tab brought to the front of the widget)
167 *
168 * @see #focusCurrentTab
169 */
170 public void setCurrentTab(int index) {
171 if (index < 0 || index >= getTabCount()) {
172 return;
173 }
174
175 getChildTabViewAt(mSelectedTab).setSelected(false);
176 mSelectedTab = index;
177 getChildTabViewAt(mSelectedTab).setSelected(true);
178 }
179
180 /**
181 * Sets the current tab and focuses the UI on it.
182 * This method makes sure that the focused tab matches the selected
183 * tab, normally at {@link #setCurrentTab}. Normally this would not
184 * be an issue if we go through the UI, since the UI is responsible
185 * for calling TabWidget.onFocusChanged(), but in the case where we
186 * are selecting the tab programmatically, we'll need to make sure
187 * focus keeps up.
188 *
189 * @param index The tab that you want focused (highlighted in orange)
190 * and selected (tab brought to the front of the widget)
191 *
192 * @see #setCurrentTab
193 */
194 public void focusCurrentTab(int index) {
195 final int oldTab = mSelectedTab;
196
197 // set the tab
198 setCurrentTab(index);
199
200 // change the focus if applicable.
201 if (oldTab != index) {
202 getChildTabViewAt(index).requestFocus();
203 }
204 }
205
206 /**
207 * Adds a tab to the list of tabs. The tab's indicator view is specified
208 * by a layout id. InflateException will be thrown if there is a problem
209 * inflating.
210 *
211 * @param layoutResId The layout id to be inflated to make the tab indicator.
212 */
213 public void addTab(int layoutResId) {
214 addTab(mInflater.inflate(layoutResId, mTabsView, false));
215 }
216
217 /**
218 * Adds a tab to the list of tabs. The tab's indicator view must be provided.
219 *
220 * @param child
221 */
222 public void addTab(View child) {
223 if (child == null) {
224 return;
225 }
226
227 if (child.getLayoutParams() == null) {
228 final LayoutParams lp = new LayoutParams(
229 ViewGroup.LayoutParams.WRAP_CONTENT,
230 ViewGroup.LayoutParams.WRAP_CONTENT);
231 lp.setMargins(0, 0, 0, 0);
232 child.setLayoutParams(lp);
233 }
234
235 // Ensure you can navigate to the tab with the keyboard, and you can touch it
236 child.setFocusable(true);
237 child.setClickable(true);
238 child.setOnClickListener(new TabClickListener());
239 child.setOnFocusChangeListener(new TabFocusListener());
240
241 // If we already have at least one tab, then add a divider before adding the next tab.
242 if (getTabCount() > 0) {
243 View divider = new View(mContext);
244 final LayoutParams lp = new LayoutParams(
245 mDividerDrawable.getIntrinsicWidth(),
246 ViewGroup.LayoutParams.FILL_PARENT);
247 lp.setMargins(0, 0, 0, 0);
248 divider.setLayoutParams(lp);
249 divider.setBackgroundDrawable(mDividerDrawable);
250 mTabsView.addView(divider);
251 }
252 mTabsView.addView(child);
253 }
254
255 /**
256 * Provides a way for ViewContactActivity and EditContactActivity to be notified that the
257 * user clicked on a tab indicator.
258 */
259 void setTabSelectionListener(OnTabSelectionChangedListener listener) {
260 mSelectionChangedListener = listener;
261 }
262
263 private class TabFocusListener implements OnFocusChangeListener {
264 public void onFocusChange(View v, boolean hasFocus) {
265 if (hasFocus) {
266 for (int i = 0; i < getTabCount(); i++) {
267 if (getChildTabViewAt(i) == v) {
268 setCurrentTab(i);
269 mSelectionChangedListener.onTabSelectionChanged(i, false);
270 break;
271 }
272 }
273 }
274 }
275 }
276
277 private class TabClickListener implements OnClickListener {
278 public void onClick(View v) {
279 for (int i = 0; i < getTabCount(); i++) {
280 if (getChildTabViewAt(i) == v) {
281 setCurrentTab(i);
282 mSelectionChangedListener.onTabSelectionChanged(i, true);
283 break;
284 }
285 }
286 }
287 }
288
289 static interface OnTabSelectionChangedListener {
290 /**
291 * Informs the tab widget host which tab was selected. It also indicates
292 * if the tab was clicked/pressed or just focused into.
293 *
294 * @param tabIndex index of the tab that was selected
295 * @param clicked whether the selection changed due to a touch/click
296 * or due to focus entering the tab through navigation. Pass true
297 * if it was due to a press/click and false otherwise.
298 */
299 void onTabSelectionChanged(int tabIndex, boolean clicked);
300 }
301
302 @Override
303 public boolean dispatchKeyEvent(KeyEvent event) {
304 boolean handled = super.dispatchKeyEvent(event);
305 if (event.getAction() == KeyEvent.ACTION_DOWN) {
306 switch (event.getKeyCode()) {
307 case KeyEvent.KEYCODE_DPAD_LEFT:
308 case KeyEvent.KEYCODE_DPAD_RIGHT:
309 // If tabs move from left/right events we must update mLeftMostVisibleTabIndex.
310 updateLeftMostVisible();
311 break;
312 }
313 }
314
315 return handled;
316 }
317
318 public void onClick(View v) {
319 if (v == mRightArrowView && (mLeftMostVisibleTabIndex + 1 < getTabCount())) {
320 tabScroll(true /* right */);
321 } else if (v == mLeftArrowView && mLeftMostVisibleTabIndex > 0) {
322 tabScroll(false /* left */);
323 }
324 }
325
326 /*
327 * Updates our record of the left most visible tab. We keep track of this explicitly
328 * on arrow clicks, but need to re-calibrate after focus navigation.
329 */
330 protected void updateLeftMostVisible() {
331 int viewableLeftEdge = mTabsScrollWrapper.getScrollX();
332
333 if (mLeftArrowView.getVisibility() == View.VISIBLE) {
334 viewableLeftEdge += mLeftArrowView.getWidth();
335 }
336
337 for (int i = 0; i < getTabCount(); i++) {
338 View tab = getChildTabViewAt(i);
339 int tabLeftEdge = tab.getLeft();
340 if (tabLeftEdge >= viewableLeftEdge) {
341 mLeftMostVisibleTabIndex = i;
342 break;
343 }
344 }
345 }
346
347 /**
348 * Scrolls the tabs by exactly one tab width.
349 *
350 * @param directionRight if true, scroll to the right, if false, scroll to the left.
351 */
352 protected void tabScroll(boolean directionRight) {
353 int scrollWidth = 0;
354 View newLeftMostVisibleTab = null;
355 if (directionRight) {
356 newLeftMostVisibleTab = getChildTabViewAt(++mLeftMostVisibleTabIndex);
357 } else {
358 newLeftMostVisibleTab = getChildTabViewAt(--mLeftMostVisibleTabIndex);
359 }
360
361 scrollWidth = newLeftMostVisibleTab.getLeft() - mTabsScrollWrapper.getScrollX();
362 if (mLeftMostVisibleTabIndex > 0) {
363 scrollWidth -= mLeftArrowView.getWidth();
364 }
365 mTabsScrollWrapper.smoothScrollBy(scrollWidth, 0);
366 }
367
368}