48fb5d8b4f99527c2836ab161ee977851a9cc8b0
[pub/Android/ownCloud.git] / actionbarsherlock / src / com / actionbarsherlock / internal / widget / ScrollingTabContainerView.java
1 /*
2 * Copyright (C) 2011 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 package com.actionbarsherlock.internal.widget;
17
18 import android.content.Context;
19 import android.content.res.Configuration;
20 import android.content.res.TypedArray;
21 import android.graphics.drawable.Drawable;
22 import android.text.TextUtils.TruncateAt;
23 import android.util.AttributeSet;
24 import android.view.Gravity;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.view.ViewParent;
29 import android.view.animation.DecelerateInterpolator;
30 import android.view.animation.Interpolator;
31 import android.widget.BaseAdapter;
32 import android.widget.ImageView;
33 import android.widget.LinearLayout;
34 import android.widget.ListView;
35 import com.actionbarsherlock.R;
36 import com.actionbarsherlock.app.ActionBar;
37 import com.actionbarsherlock.internal.nineoldandroids.animation.Animator;
38 import com.actionbarsherlock.internal.nineoldandroids.animation.ObjectAnimator;
39 import com.actionbarsherlock.internal.nineoldandroids.widget.NineHorizontalScrollView;
40
41 /**
42 * This widget implements the dynamic action bar tab behavior that can change
43 * across different configurations or circumstances.
44 */
45 public class ScrollingTabContainerView extends NineHorizontalScrollView
46 implements IcsAdapterView.OnItemSelectedListener {
47 //UNUSED private static final String TAG = "ScrollingTabContainerView";
48 Runnable mTabSelector;
49 private TabClickListener mTabClickListener;
50
51 private IcsLinearLayout mTabLayout;
52 private IcsSpinner mTabSpinner;
53 private boolean mAllowCollapse;
54
55 private LayoutInflater mInflater;
56
57 int mMaxTabWidth;
58 private int mContentHeight;
59 private int mSelectedTabIndex;
60
61 protected Animator mVisibilityAnim;
62 protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
63
64 private static final /*Time*/Interpolator sAlphaInterpolator = new DecelerateInterpolator();
65
66 private static final int FADE_DURATION = 200;
67
68 public ScrollingTabContainerView(Context context) {
69 super(context);
70 setHorizontalScrollBarEnabled(false);
71
72 TypedArray a = getContext().obtainStyledAttributes(null, R.styleable.SherlockActionBar,
73 R.attr.actionBarStyle, 0);
74 setContentHeight(a.getLayoutDimension(R.styleable.SherlockActionBar_height, 0));
75 a.recycle();
76
77 mInflater = LayoutInflater.from(context);
78
79 mTabLayout = createTabLayout();
80 addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
81 ViewGroup.LayoutParams.MATCH_PARENT));
82 }
83
84 @Override
85 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
86 final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
87 final boolean lockedExpanded = widthMode == MeasureSpec.EXACTLY;
88 setFillViewport(lockedExpanded);
89
90 final int childCount = mTabLayout.getChildCount();
91 if (childCount > 1 &&
92 (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)) {
93 if (childCount > 2) {
94 mMaxTabWidth = (int) (MeasureSpec.getSize(widthMeasureSpec) * 0.4f);
95 } else {
96 mMaxTabWidth = MeasureSpec.getSize(widthMeasureSpec) / 2;
97 }
98 } else {
99 mMaxTabWidth = -1;
100 }
101
102 heightMeasureSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.EXACTLY);
103
104 final boolean canCollapse = !lockedExpanded && mAllowCollapse;
105
106 if (canCollapse) {
107 // See if we should expand
108 mTabLayout.measure(MeasureSpec.UNSPECIFIED, heightMeasureSpec);
109 if (mTabLayout.getMeasuredWidth() > MeasureSpec.getSize(widthMeasureSpec)) {
110 performCollapse();
111 } else {
112 performExpand();
113 }
114 } else {
115 performExpand();
116 }
117
118 final int oldWidth = getMeasuredWidth();
119 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
120 final int newWidth = getMeasuredWidth();
121
122 if (lockedExpanded && oldWidth != newWidth) {
123 // Recenter the tab display if we're at a new (scrollable) size.
124 setTabSelected(mSelectedTabIndex);
125 }
126 }
127
128 /**
129 * Indicates whether this view is collapsed into a dropdown menu instead
130 * of traditional tabs.
131 * @return true if showing as a spinner
132 */
133 private boolean isCollapsed() {
134 return mTabSpinner != null && mTabSpinner.getParent() == this;
135 }
136
137 public void setAllowCollapse(boolean allowCollapse) {
138 mAllowCollapse = allowCollapse;
139 }
140
141 private void performCollapse() {
142 if (isCollapsed()) return;
143
144 if (mTabSpinner == null) {
145 mTabSpinner = createSpinner();
146 }
147 removeView(mTabLayout);
148 addView(mTabSpinner, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
149 ViewGroup.LayoutParams.MATCH_PARENT));
150 if (mTabSpinner.getAdapter() == null) {
151 mTabSpinner.setAdapter(new TabAdapter());
152 }
153 if (mTabSelector != null) {
154 removeCallbacks(mTabSelector);
155 mTabSelector = null;
156 }
157 mTabSpinner.setSelection(mSelectedTabIndex);
158 }
159
160 private boolean performExpand() {
161 if (!isCollapsed()) return false;
162
163 removeView(mTabSpinner);
164 addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
165 ViewGroup.LayoutParams.MATCH_PARENT));
166 setTabSelected(mTabSpinner.getSelectedItemPosition());
167 return false;
168 }
169
170 public void setTabSelected(int position) {
171 mSelectedTabIndex = position;
172 final int tabCount = mTabLayout.getChildCount();
173 for (int i = 0; i < tabCount; i++) {
174 final View child = mTabLayout.getChildAt(i);
175 final boolean isSelected = i == position;
176 child.setSelected(isSelected);
177 if (isSelected) {
178 animateToTab(position);
179 }
180 }
181 }
182
183 public void setContentHeight(int contentHeight) {
184 mContentHeight = contentHeight;
185 requestLayout();
186 }
187
188 private IcsLinearLayout createTabLayout() {
189 final IcsLinearLayout tabLayout = (IcsLinearLayout) LayoutInflater.from(getContext())
190 .inflate(R.layout.abs__action_bar_tab_bar_view, null);
191 tabLayout.setMeasureWithLargestChildEnabled(true);
192 tabLayout.setLayoutParams(new LinearLayout.LayoutParams(
193 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT));
194 return tabLayout;
195 }
196
197 private IcsSpinner createSpinner() {
198 final IcsSpinner spinner = new IcsSpinner(getContext(), null,
199 R.attr.actionDropDownStyle);
200 spinner.setLayoutParams(new LinearLayout.LayoutParams(
201 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT));
202 spinner.setOnItemSelectedListener(this);
203 return spinner;
204 }
205
206 @Override
207 protected void onConfigurationChanged(Configuration newConfig) {
208 super.onConfigurationChanged(newConfig);
209
210 // Action bar can change size on configuration changes.
211 // Reread the desired height from the theme-specified style.
212 TypedArray a = getContext().obtainStyledAttributes(null, R.styleable.SherlockActionBar,
213 R.attr.actionBarStyle, 0);
214 setContentHeight(a.getLayoutDimension(R.styleable.SherlockActionBar_height, 0));
215 a.recycle();
216 }
217
218 public void animateToVisibility(int visibility) {
219 if (mVisibilityAnim != null) {
220 mVisibilityAnim.cancel();
221 }
222 if (visibility == VISIBLE) {
223 if (getVisibility() != VISIBLE) {
224 setAlpha(0);
225 }
226 ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 1);
227 anim.setDuration(FADE_DURATION);
228 anim.setInterpolator(sAlphaInterpolator);
229
230 anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
231 anim.start();
232 } else {
233 ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 0);
234 anim.setDuration(FADE_DURATION);
235 anim.setInterpolator(sAlphaInterpolator);
236
237 anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
238 anim.start();
239 }
240 }
241
242 public void animateToTab(final int position) {
243 final View tabView = mTabLayout.getChildAt(position);
244 if (mTabSelector != null) {
245 removeCallbacks(mTabSelector);
246 }
247 mTabSelector = new Runnable() {
248 public void run() {
249 final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2;
250 smoothScrollTo(scrollPos, 0);
251 mTabSelector = null;
252 }
253 };
254 post(mTabSelector);
255 }
256
257 @Override
258 public void onAttachedToWindow() {
259 super.onAttachedToWindow();
260 if (mTabSelector != null) {
261 // Re-post the selector we saved
262 post(mTabSelector);
263 }
264 }
265
266 @Override
267 public void onDetachedFromWindow() {
268 super.onDetachedFromWindow();
269 if (mTabSelector != null) {
270 removeCallbacks(mTabSelector);
271 }
272 }
273
274 private TabView createTabView(ActionBar.Tab tab, boolean forAdapter) {
275 //Workaround for not being able to pass a defStyle on pre-3.0
276 final TabView tabView = (TabView)mInflater.inflate(R.layout.abs__action_bar_tab, null);
277 tabView.init(this, tab, forAdapter);
278
279 if (forAdapter) {
280 tabView.setBackgroundDrawable(null);
281 tabView.setLayoutParams(new ListView.LayoutParams(ListView.LayoutParams.MATCH_PARENT,
282 mContentHeight));
283 } else {
284 tabView.setFocusable(true);
285
286 if (mTabClickListener == null) {
287 mTabClickListener = new TabClickListener();
288 }
289 tabView.setOnClickListener(mTabClickListener);
290 }
291 return tabView;
292 }
293
294 public void addTab(ActionBar.Tab tab, boolean setSelected) {
295 TabView tabView = createTabView(tab, false);
296 mTabLayout.addView(tabView, new IcsLinearLayout.LayoutParams(0,
297 LayoutParams.MATCH_PARENT, 1));
298 if (mTabSpinner != null) {
299 ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
300 }
301 if (setSelected) {
302 tabView.setSelected(true);
303 }
304 if (mAllowCollapse) {
305 requestLayout();
306 }
307 }
308
309 public void addTab(ActionBar.Tab tab, int position, boolean setSelected) {
310 final TabView tabView = createTabView(tab, false);
311 mTabLayout.addView(tabView, position, new IcsLinearLayout.LayoutParams(
312 0, LayoutParams.MATCH_PARENT, 1));
313 if (mTabSpinner != null) {
314 ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
315 }
316 if (setSelected) {
317 tabView.setSelected(true);
318 }
319 if (mAllowCollapse) {
320 requestLayout();
321 }
322 }
323
324 public void updateTab(int position) {
325 ((TabView) mTabLayout.getChildAt(position)).update();
326 if (mTabSpinner != null) {
327 ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
328 }
329 if (mAllowCollapse) {
330 requestLayout();
331 }
332 }
333
334 public void removeTabAt(int position) {
335 mTabLayout.removeViewAt(position);
336 if (mTabSpinner != null) {
337 ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
338 }
339 if (mAllowCollapse) {
340 requestLayout();
341 }
342 }
343
344 public void removeAllTabs() {
345 mTabLayout.removeAllViews();
346 if (mTabSpinner != null) {
347 ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
348 }
349 if (mAllowCollapse) {
350 requestLayout();
351 }
352 }
353
354 @Override
355 public void onItemSelected(IcsAdapterView<?> parent, View view, int position, long id) {
356 TabView tabView = (TabView) view;
357 tabView.getTab().select();
358 }
359
360 @Override
361 public void onNothingSelected(IcsAdapterView<?> parent) {
362 }
363
364 public static class TabView extends LinearLayout {
365 private ScrollingTabContainerView mParent;
366 private ActionBar.Tab mTab;
367 private CapitalizingTextView mTextView;
368 private ImageView mIconView;
369 private View mCustomView;
370
371 public TabView(Context context, AttributeSet attrs) {
372 //TODO super(context, null, R.attr.actionBarTabStyle);
373 super(context, attrs);
374 }
375
376 public void init(ScrollingTabContainerView parent, ActionBar.Tab tab, boolean forList) {
377 mParent = parent;
378 mTab = tab;
379
380 if (forList) {
381 setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
382 }
383
384 update();
385 }
386
387 public void bindTab(ActionBar.Tab tab) {
388 mTab = tab;
389 update();
390 }
391
392 @Override
393 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
394 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
395
396 // Re-measure if we went beyond our maximum size.
397 if (mParent.mMaxTabWidth > 0 && getMeasuredWidth() > mParent.mMaxTabWidth) {
398 super.onMeasure(MeasureSpec.makeMeasureSpec(mParent.mMaxTabWidth, MeasureSpec.EXACTLY),
399 heightMeasureSpec);
400 }
401 }
402
403 public void update() {
404 final ActionBar.Tab tab = mTab;
405 final View custom = tab.getCustomView();
406 if (custom != null) {
407 final ViewParent customParent = custom.getParent();
408 if (customParent != this) {
409 if (customParent != null) ((ViewGroup) customParent).removeView(custom);
410 addView(custom);
411 }
412 mCustomView = custom;
413 if (mTextView != null) mTextView.setVisibility(GONE);
414 if (mIconView != null) {
415 mIconView.setVisibility(GONE);
416 mIconView.setImageDrawable(null);
417 }
418 } else {
419 if (mCustomView != null) {
420 removeView(mCustomView);
421 mCustomView = null;
422 }
423
424 final Drawable icon = tab.getIcon();
425 final CharSequence text = tab.getText();
426
427 if (icon != null) {
428 if (mIconView == null) {
429 ImageView iconView = new ImageView(getContext());
430 LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
431 LayoutParams.WRAP_CONTENT);
432 lp.gravity = Gravity.CENTER_VERTICAL;
433 iconView.setLayoutParams(lp);
434 addView(iconView, 0);
435 mIconView = iconView;
436 }
437 mIconView.setImageDrawable(icon);
438 mIconView.setVisibility(VISIBLE);
439 } else if (mIconView != null) {
440 mIconView.setVisibility(GONE);
441 mIconView.setImageDrawable(null);
442 }
443
444 if (text != null) {
445 if (mTextView == null) {
446 CapitalizingTextView textView = new CapitalizingTextView(getContext(), null,
447 R.attr.actionBarTabTextStyle);
448 textView.setEllipsize(TruncateAt.END);
449 LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
450 LayoutParams.WRAP_CONTENT);
451 lp.gravity = Gravity.CENTER_VERTICAL;
452 textView.setLayoutParams(lp);
453 addView(textView);
454 mTextView = textView;
455 }
456 mTextView.setTextCompat(text);
457 mTextView.setVisibility(VISIBLE);
458 } else if (mTextView != null) {
459 mTextView.setVisibility(GONE);
460 mTextView.setText(null);
461 }
462
463 if (mIconView != null) {
464 mIconView.setContentDescription(tab.getContentDescription());
465 }
466 }
467 }
468
469 public ActionBar.Tab getTab() {
470 return mTab;
471 }
472 }
473
474 private class TabAdapter extends BaseAdapter {
475 @Override
476 public int getCount() {
477 return mTabLayout.getChildCount();
478 }
479
480 @Override
481 public Object getItem(int position) {
482 return ((TabView) mTabLayout.getChildAt(position)).getTab();
483 }
484
485 @Override
486 public long getItemId(int position) {
487 return position;
488 }
489
490 @Override
491 public View getView(int position, View convertView, ViewGroup parent) {
492 if (convertView == null) {
493 convertView = createTabView((ActionBar.Tab) getItem(position), true);
494 } else {
495 ((TabView) convertView).bindTab((ActionBar.Tab) getItem(position));
496 }
497 return convertView;
498 }
499 }
500
501 private class TabClickListener implements OnClickListener {
502 public void onClick(View view) {
503 TabView tabView = (TabView) view;
504 tabView.getTab().select();
505 final int tabCount = mTabLayout.getChildCount();
506 for (int i = 0; i < tabCount; i++) {
507 final View child = mTabLayout.getChildAt(i);
508 child.setSelected(child == view);
509 }
510 }
511 }
512
513 protected class VisibilityAnimListener implements Animator.AnimatorListener {
514 private boolean mCanceled = false;
515 private int mFinalVisibility;
516
517 public VisibilityAnimListener withFinalVisibility(int visibility) {
518 mFinalVisibility = visibility;
519 return this;
520 }
521
522 @Override
523 public void onAnimationStart(Animator animation) {
524 setVisibility(VISIBLE);
525 mVisibilityAnim = animation;
526 mCanceled = false;
527 }
528
529 @Override
530 public void onAnimationEnd(Animator animation) {
531 if (mCanceled) return;
532
533 mVisibilityAnim = null;
534 setVisibility(mFinalVisibility);
535 }
536
537 @Override
538 public void onAnimationCancel(Animator animation) {
539 mCanceled = true;
540 }
541
542 @Override
543 public void onAnimationRepeat(Animator animation) {
544 }
545 }
546 }