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
.setMeasureWithLargestChildEnabled(true
);
192 tabLayout
.setLayoutParams(new LinearLayout
.LayoutParams(
193 LinearLayout
.LayoutParams
.WRAP_CONTENT
, LinearLayout
.LayoutParams
.MATCH_PARENT
));
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);
207 protected void onConfigurationChanged(Configuration newConfig
) {
208 super.onConfigurationChanged(newConfig
);
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));
218 public void animateToVisibility(int visibility
) {
219 if (mVisibilityAnim
!= null
) {
220 mVisibilityAnim
.cancel();
222 if (visibility
== VISIBLE
) {
223 if (getVisibility() != VISIBLE
) {
226 ObjectAnimator anim
= ObjectAnimator
.ofFloat(this, "alpha", 1);
227 anim
.setDuration(FADE_DURATION
);
228 anim
.setInterpolator(sAlphaInterpolator
);
230 anim
.addListener(mVisAnimListener
.withFinalVisibility(visibility
));
233 ObjectAnimator anim
= ObjectAnimator
.ofFloat(this, "alpha", 0);
234 anim
.setDuration(FADE_DURATION
);
235 anim
.setInterpolator(sAlphaInterpolator
);
237 anim
.addListener(mVisAnimListener
.withFinalVisibility(visibility
));
242 public void animateToTab(final int position
) {
243 final View tabView
= mTabLayout
.getChildAt(position
);
244 if (mTabSelector
!= null
) {
245 removeCallbacks(mTabSelector
);
247 mTabSelector
= new Runnable() {
249 final int scrollPos
= tabView
.getLeft() - (getWidth() - tabView
.getWidth()) / 2;
250 smoothScrollTo(scrollPos
, 0);
258 public void onAttachedToWindow() {
259 super.onAttachedToWindow();
260 if (mTabSelector
!= null
) {
261 // Re-post the selector we saved
267 public void onDetachedFromWindow() {
268 super.onDetachedFromWindow();
269 if (mTabSelector
!= null
) {
270 removeCallbacks(mTabSelector
);
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
);
280 tabView
.setBackgroundDrawable(null
);
281 tabView
.setLayoutParams(new ListView
.LayoutParams(ListView
.LayoutParams
.MATCH_PARENT
,
284 tabView
.setFocusable(true
);
286 if (mTabClickListener
== null
) {
287 mTabClickListener
= new TabClickListener();
289 tabView
.setOnClickListener(mTabClickListener
);
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();
302 tabView
.setSelected(true
);
304 if (mAllowCollapse
) {
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();
317 tabView
.setSelected(true
);
319 if (mAllowCollapse
) {
324 public void updateTab(int position
) {
325 ((TabView
) mTabLayout
.getChildAt(position
)).update();
326 if (mTabSpinner
!= null
) {
327 ((TabAdapter
) mTabSpinner
.getAdapter()).notifyDataSetChanged();
329 if (mAllowCollapse
) {
334 public void removeTabAt(int position
) {
335 mTabLayout
.removeViewAt(position
);
336 if (mTabSpinner
!= null
) {
337 ((TabAdapter
) mTabSpinner
.getAdapter()).notifyDataSetChanged();
339 if (mAllowCollapse
) {
344 public void removeAllTabs() {
345 mTabLayout
.removeAllViews();
346 if (mTabSpinner
!= null
) {
347 ((TabAdapter
) mTabSpinner
.getAdapter()).notifyDataSetChanged();
349 if (mAllowCollapse
) {
355 public void onItemSelected(IcsAdapterView
<?
> parent
, View view
, int position
, long id
) {
356 TabView tabView
= (TabView
) view
;
357 tabView
.getTab().select();
361 public void onNothingSelected(IcsAdapterView
<?
> parent
) {
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
;
371 public TabView(Context context
, AttributeSet attrs
) {
372 //TODO super(context, null, R.attr.actionBarTabStyle);
373 super(context
, attrs
);
376 public void init(ScrollingTabContainerView parent
, ActionBar
.Tab tab
, boolean forList
) {
381 setGravity(Gravity
.LEFT
| Gravity
.CENTER_VERTICAL
);
387 public void bindTab(ActionBar
.Tab tab
) {
393 public void onMeasure(int widthMeasureSpec
, int heightMeasureSpec
) {
394 super.onMeasure(widthMeasureSpec
, heightMeasureSpec
);
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
),
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
);
412 mCustomView
= custom
;
413 if (mTextView
!= null
) mTextView
.setVisibility(GONE
);
414 if (mIconView
!= null
) {
415 mIconView
.setVisibility(GONE
);
416 mIconView
.setImageDrawable(null
);
419 if (mCustomView
!= null
) {
420 removeView(mCustomView
);
424 final Drawable icon
= tab
.getIcon();
425 final CharSequence text
= tab
.getText();
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
;
437 mIconView
.setImageDrawable(icon
);
438 mIconView
.setVisibility(VISIBLE
);
439 } else if (mIconView
!= null
) {
440 mIconView
.setVisibility(GONE
);
441 mIconView
.setImageDrawable(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
);
454 mTextView
= textView
;
456 mTextView
.setTextCompat(text
);
457 mTextView
.setVisibility(VISIBLE
);
458 } else if (mTextView
!= null
) {
459 mTextView
.setVisibility(GONE
);
460 mTextView
.setText(null
);
463 if (mIconView
!= null
) {
464 mIconView
.setContentDescription(tab
.getContentDescription());
469 public ActionBar
.Tab
getTab() {
474 private class TabAdapter
extends BaseAdapter
{
476 public int getCount() {
477 return mTabLayout
.getChildCount();
481 public Object
getItem(int position
) {
482 return ((TabView
) mTabLayout
.getChildAt(position
)).getTab();
486 public long getItemId(int position
) {
491 public View
getView(int position
, View convertView
, ViewGroup parent
) {
492 if (convertView
== null
) {
493 convertView
= createTabView((ActionBar
.Tab
) getItem(position
), true
);
495 ((TabView
) convertView
).bindTab((ActionBar
.Tab
) getItem(position
));
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
);
513 protected class VisibilityAnimListener
implements Animator
.AnimatorListener
{
514 private boolean mCanceled
= false
;
515 private int mFinalVisibility
;
517 public VisibilityAnimListener
withFinalVisibility(int visibility
) {
518 mFinalVisibility
= visibility
;
523 public void onAnimationStart(Animator animation
) {
524 setVisibility(VISIBLE
);
525 mVisibilityAnim
= animation
;
530 public void onAnimationEnd(Animator animation
) {
531 if (mCanceled
) return;
533 mVisibilityAnim
= null
;
534 setVisibility(mFinalVisibility
);
538 public void onAnimationCancel(Animator animation
) {
543 public void onAnimationRepeat(Animator animation
) {