2 * Copyright (C) 2011 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.actionbarsherlock.internal.widget;
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;
42 * This widget implements the dynamic action bar tab behavior that can change
43 * across different configurations or circumstances.
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;
51 private IcsLinearLayout mTabLayout;
52 private IcsSpinner mTabSpinner;
53 private boolean mAllowCollapse;
55 private LayoutInflater mInflater;
58 private int mContentHeight;
59 private int mSelectedTabIndex;
61 protected Animator mVisibilityAnim;
62 protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
64 private static final /*Time*/Interpolator sAlphaInterpolator = new DecelerateInterpolator();
66 private static final int FADE_DURATION = 200;
68 public ScrollingTabContainerView(Context context) {
70 setHorizontalScrollBarEnabled(false);
72 TypedArray a = getContext().obtainStyledAttributes(null, R.styleable.SherlockActionBar,
73 R.attr.actionBarStyle, 0);
74 setContentHeight(a.getLayoutDimension(R.styleable.SherlockActionBar_height, 0));
77 mInflater = LayoutInflater.from(context);
79 mTabLayout = createTabLayout();
80 addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
81 ViewGroup.LayoutParams.MATCH_PARENT));
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);
90 final int childCount = mTabLayout.getChildCount();
92 (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)) {
94 mMaxTabWidth = (int) (MeasureSpec.getSize(widthMeasureSpec) * 0.4f);
96 mMaxTabWidth = MeasureSpec.getSize(widthMeasureSpec) / 2;
102 heightMeasureSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.EXACTLY);
104 final boolean canCollapse = !lockedExpanded && mAllowCollapse;
107 // See if we should expand
108 mTabLayout.measure(MeasureSpec.UNSPECIFIED, heightMeasureSpec);
109 if (mTabLayout.getMeasuredWidth() > MeasureSpec.getSize(widthMeasureSpec)) {
118 final int oldWidth = getMeasuredWidth();
119 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
120 final int newWidth = getMeasuredWidth();
122 if (lockedExpanded && oldWidth != newWidth) {
123 // Recenter the tab display if we're at a new (scrollable) size.
124 setTabSelected(mSelectedTabIndex);
129 * Indicates whether this view is collapsed into a dropdown menu instead
130 * of traditional tabs.
131 * @return true if showing as a spinner
133 private boolean isCollapsed() {
134 return mTabSpinner != null && mTabSpinner.getParent() == this;
137 public void setAllowCollapse(boolean allowCollapse) {
138 mAllowCollapse = allowCollapse;
141 private void performCollapse() {
142 if (isCollapsed()) return;
144 if (mTabSpinner == null) {
145 mTabSpinner = createSpinner();
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());
153 if (mTabSelector != null) {
154 removeCallbacks(mTabSelector);
157 mTabSpinner.setSelection(mSelectedTabIndex);
160 private boolean performExpand() {
161 if (!isCollapsed()) return false;
163 removeView(mTabSpinner);
164 addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
165 ViewGroup.LayoutParams.MATCH_PARENT));
166 setTabSelected(mTabSpinner.getSelectedItemPosition());
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);
178 animateToTab(position);
183 public void setContentHeight(int contentHeight) {
184 mContentHeight = contentHeight;
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.setLayoutParams(new LinearLayout.LayoutParams(
192 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT));
196 private IcsSpinner createSpinner() {
197 final IcsSpinner spinner = new IcsSpinner(getContext(), null,
198 R.attr.actionDropDownStyle);
199 spinner.setLayoutParams(new LinearLayout.LayoutParams(
200 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT));
201 spinner.setOnItemSelectedListener(this);
206 protected void onConfigurationChanged(Configuration newConfig) {
207 super.onConfigurationChanged(newConfig);
209 // Action bar can change size on configuration changes.
210 // Reread the desired height from the theme-specified style.
211 TypedArray a = getContext().obtainStyledAttributes(null, R.styleable.SherlockActionBar,
212 R.attr.actionBarStyle, 0);
213 setContentHeight(a.getLayoutDimension(R.styleable.SherlockActionBar_height, 0));
217 public void animateToVisibility(int visibility) {
218 if (mVisibilityAnim != null) {
219 mVisibilityAnim.cancel();
221 if (visibility == VISIBLE) {
222 if (getVisibility() != VISIBLE) {
225 ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 1);
226 anim.setDuration(FADE_DURATION);
227 anim.setInterpolator(sAlphaInterpolator);
229 anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
232 ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 0);
233 anim.setDuration(FADE_DURATION);
234 anim.setInterpolator(sAlphaInterpolator);
236 anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
241 public void animateToTab(final int position) {
242 final View tabView = mTabLayout.getChildAt(position);
243 if (mTabSelector != null) {
244 removeCallbacks(mTabSelector);
246 mTabSelector = new Runnable() {
248 final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2;
249 smoothScrollTo(scrollPos, 0);
257 public void onAttachedToWindow() {
258 super.onAttachedToWindow();
259 if (mTabSelector != null) {
260 // Re-post the selector we saved
266 public void onDetachedFromWindow() {
267 super.onDetachedFromWindow();
268 if (mTabSelector != null) {
269 removeCallbacks(mTabSelector);
273 private TabView createTabView(ActionBar.Tab tab, boolean forAdapter) {
274 //Workaround for not being able to pass a defStyle on pre-3.0
275 final TabView tabView = (TabView)mInflater.inflate(R.layout.abs__action_bar_tab, null);
276 tabView.init(this, tab, forAdapter);
279 tabView.setBackgroundDrawable(null);
280 tabView.setLayoutParams(new ListView.LayoutParams(ListView.LayoutParams.MATCH_PARENT,
283 tabView.setFocusable(true);
285 if (mTabClickListener == null) {
286 mTabClickListener = new TabClickListener();
288 tabView.setOnClickListener(mTabClickListener);
293 public void addTab(ActionBar.Tab tab, boolean setSelected) {
294 TabView tabView = createTabView(tab, false);
295 mTabLayout.addView(tabView, new IcsLinearLayout.LayoutParams(0,
296 LayoutParams.MATCH_PARENT, 1));
297 if (mTabSpinner != null) {
298 ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
301 tabView.setSelected(true);
303 if (mAllowCollapse) {
308 public void addTab(ActionBar.Tab tab, int position, boolean setSelected) {
309 final TabView tabView = createTabView(tab, false);
310 mTabLayout.addView(tabView, position, new IcsLinearLayout.LayoutParams(
311 0, LayoutParams.MATCH_PARENT, 1));
312 if (mTabSpinner != null) {
313 ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
316 tabView.setSelected(true);
318 if (mAllowCollapse) {
323 public void updateTab(int position) {
324 ((TabView) mTabLayout.getChildAt(position)).update();
325 if (mTabSpinner != null) {
326 ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
328 if (mAllowCollapse) {
333 public void removeTabAt(int position) {
334 mTabLayout.removeViewAt(position);
335 if (mTabSpinner != null) {
336 ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
338 if (mAllowCollapse) {
343 public void removeAllTabs() {
344 mTabLayout.removeAllViews();
345 if (mTabSpinner != null) {
346 ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
348 if (mAllowCollapse) {
354 public void onItemSelected(IcsAdapterView<?> parent, View view, int position, long id) {
355 TabView tabView = (TabView) view;
356 tabView.getTab().select();
360 public void onNothingSelected(IcsAdapterView<?> parent) {
363 public static class TabView extends LinearLayout {
364 private ScrollingTabContainerView mParent;
365 private ActionBar.Tab mTab;
366 private CapitalizingTextView mTextView;
367 private ImageView mIconView;
368 private View mCustomView;
370 public TabView(Context context, AttributeSet attrs) {
371 //TODO super(context, null, R.attr.actionBarTabStyle);
372 super(context, attrs);
375 public void init(ScrollingTabContainerView parent, ActionBar.Tab tab, boolean forList) {
380 setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
386 public void bindTab(ActionBar.Tab tab) {
392 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
393 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
395 // Re-measure if we went beyond our maximum size.
396 if (mParent.mMaxTabWidth > 0 && getMeasuredWidth() > mParent.mMaxTabWidth) {
397 super.onMeasure(MeasureSpec.makeMeasureSpec(mParent.mMaxTabWidth, MeasureSpec.EXACTLY),
402 public void update() {
403 final ActionBar.Tab tab = mTab;
404 final View custom = tab.getCustomView();
405 if (custom != null) {
406 final ViewParent customParent = custom.getParent();
407 if (customParent != this) {
408 if (customParent != null) ((ViewGroup) customParent).removeView(custom);
411 mCustomView = custom;
412 if (mTextView != null) mTextView.setVisibility(GONE);
413 if (mIconView != null) {
414 mIconView.setVisibility(GONE);
415 mIconView.setImageDrawable(null);
418 if (mCustomView != null) {
419 removeView(mCustomView);
423 final Drawable icon = tab.getIcon();
424 final CharSequence text = tab.getText();
427 if (mIconView == null) {
428 ImageView iconView = new ImageView(getContext());
429 LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
430 LayoutParams.WRAP_CONTENT);
431 lp.gravity = Gravity.CENTER_VERTICAL;
432 iconView.setLayoutParams(lp);
433 addView(iconView, 0);
434 mIconView = iconView;
436 mIconView.setImageDrawable(icon);
437 mIconView.setVisibility(VISIBLE);
438 } else if (mIconView != null) {
439 mIconView.setVisibility(GONE);
440 mIconView.setImageDrawable(null);
444 if (mTextView == null) {
445 CapitalizingTextView textView = new CapitalizingTextView(getContext(), null,
446 R.attr.actionBarTabTextStyle);
447 textView.setEllipsize(TruncateAt.END);
448 LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
449 LayoutParams.WRAP_CONTENT);
450 lp.gravity = Gravity.CENTER_VERTICAL;
451 textView.setLayoutParams(lp);
453 mTextView = textView;
455 mTextView.setTextCompat(text);
456 mTextView.setVisibility(VISIBLE);
457 } else if (mTextView != null) {
458 mTextView.setVisibility(GONE);
459 mTextView.setText(null);
462 if (mIconView != null) {
463 mIconView.setContentDescription(tab.getContentDescription());
468 public ActionBar.Tab getTab() {
473 private class TabAdapter extends BaseAdapter {
475 public int getCount() {
476 return mTabLayout.getChildCount();
480 public Object getItem(int position) {
481 return ((TabView) mTabLayout.getChildAt(position)).getTab();
485 public long getItemId(int position) {
490 public View getView(int position, View convertView, ViewGroup parent) {
491 if (convertView == null) {
492 convertView = createTabView((ActionBar.Tab) getItem(position), true);
494 ((TabView) convertView).bindTab((ActionBar.Tab) getItem(position));
500 private class TabClickListener implements OnClickListener {
501 public void onClick(View view) {
502 TabView tabView = (TabView) view;
503 tabView.getTab().select();
504 final int tabCount = mTabLayout.getChildCount();
505 for (int i = 0; i < tabCount; i++) {
506 final View child = mTabLayout.getChildAt(i);
507 child.setSelected(child == view);
512 protected class VisibilityAnimListener implements Animator.AnimatorListener {
513 private boolean mCanceled = false;
514 private int mFinalVisibility;
516 public VisibilityAnimListener withFinalVisibility(int visibility) {
517 mFinalVisibility = visibility;
522 public void onAnimationStart(Animator animation) {
523 setVisibility(VISIBLE);
524 mVisibilityAnim = animation;
529 public void onAnimationEnd(Animator animation) {
530 if (mCanceled) return;
532 mVisibilityAnim = null;
533 setVisibility(mFinalVisibility);
537 public void onAnimationCancel(Animator animation) {
542 public void onAnimationRepeat(Animator animation) {