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