create changelog entry
[debian/openrocket] / android-libraries / 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.setLayoutParams(new LinearLayout.LayoutParams(
192                 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT));
193         return tabLayout;
194     }
195
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);
202         return spinner;
203     }
204
205     @Override
206     protected void onConfigurationChanged(Configuration newConfig) {
207         super.onConfigurationChanged(newConfig);
208
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));
214         a.recycle();
215     }
216
217     public void animateToVisibility(int visibility) {
218         if (mVisibilityAnim != null) {
219             mVisibilityAnim.cancel();
220         }
221         if (visibility == VISIBLE) {
222             if (getVisibility() != VISIBLE) {
223                 setAlpha(0);
224             }
225             ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 1);
226             anim.setDuration(FADE_DURATION);
227             anim.setInterpolator(sAlphaInterpolator);
228
229             anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
230             anim.start();
231         } else {
232             ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 0);
233             anim.setDuration(FADE_DURATION);
234             anim.setInterpolator(sAlphaInterpolator);
235
236             anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
237             anim.start();
238         }
239     }
240
241     public void animateToTab(final int position) {
242         final View tabView = mTabLayout.getChildAt(position);
243         if (mTabSelector != null) {
244             removeCallbacks(mTabSelector);
245         }
246         mTabSelector = new Runnable() {
247             public void run() {
248                 final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2;
249                 smoothScrollTo(scrollPos, 0);
250                 mTabSelector = null;
251             }
252         };
253         post(mTabSelector);
254     }
255
256     @Override
257     public void onAttachedToWindow() {
258         super.onAttachedToWindow();
259         if (mTabSelector != null) {
260             // Re-post the selector we saved
261             post(mTabSelector);
262         }
263     }
264
265     @Override
266     public void onDetachedFromWindow() {
267         super.onDetachedFromWindow();
268         if (mTabSelector != null) {
269             removeCallbacks(mTabSelector);
270         }
271     }
272
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);
277
278         if (forAdapter) {
279             tabView.setBackgroundDrawable(null);
280             tabView.setLayoutParams(new ListView.LayoutParams(ListView.LayoutParams.MATCH_PARENT,
281                     mContentHeight));
282         } else {
283             tabView.setFocusable(true);
284
285             if (mTabClickListener == null) {
286                 mTabClickListener = new TabClickListener();
287             }
288             tabView.setOnClickListener(mTabClickListener);
289         }
290         return tabView;
291     }
292
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();
299         }
300         if (setSelected) {
301             tabView.setSelected(true);
302         }
303         if (mAllowCollapse) {
304             requestLayout();
305         }
306     }
307
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();
314         }
315         if (setSelected) {
316             tabView.setSelected(true);
317         }
318         if (mAllowCollapse) {
319             requestLayout();
320         }
321     }
322
323     public void updateTab(int position) {
324         ((TabView) mTabLayout.getChildAt(position)).update();
325         if (mTabSpinner != null) {
326             ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
327         }
328         if (mAllowCollapse) {
329             requestLayout();
330         }
331     }
332
333     public void removeTabAt(int position) {
334         mTabLayout.removeViewAt(position);
335         if (mTabSpinner != null) {
336             ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
337         }
338         if (mAllowCollapse) {
339             requestLayout();
340         }
341     }
342
343     public void removeAllTabs() {
344         mTabLayout.removeAllViews();
345         if (mTabSpinner != null) {
346             ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
347         }
348         if (mAllowCollapse) {
349             requestLayout();
350         }
351     }
352
353     @Override
354     public void onItemSelected(IcsAdapterView<?> parent, View view, int position, long id) {
355         TabView tabView = (TabView) view;
356         tabView.getTab().select();
357     }
358
359     @Override
360     public void onNothingSelected(IcsAdapterView<?> parent) {
361     }
362
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;
369
370         public TabView(Context context, AttributeSet attrs) {
371             //TODO super(context, null, R.attr.actionBarTabStyle);
372             super(context, attrs);
373         }
374
375         public void init(ScrollingTabContainerView parent, ActionBar.Tab tab, boolean forList) {
376             mParent = parent;
377             mTab = tab;
378
379             if (forList) {
380                 setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
381             }
382
383             update();
384         }
385
386         public void bindTab(ActionBar.Tab tab) {
387             mTab = tab;
388             update();
389         }
390
391         @Override
392         public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
393             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
394
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),
398                         heightMeasureSpec);
399             }
400         }
401
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);
409                     addView(custom);
410                 }
411                 mCustomView = custom;
412                 if (mTextView != null) mTextView.setVisibility(GONE);
413                 if (mIconView != null) {
414                     mIconView.setVisibility(GONE);
415                     mIconView.setImageDrawable(null);
416                 }
417             } else {
418                 if (mCustomView != null) {
419                     removeView(mCustomView);
420                     mCustomView = null;
421                 }
422
423                 final Drawable icon = tab.getIcon();
424                 final CharSequence text = tab.getText();
425
426                 if (icon != null) {
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;
435                     }
436                     mIconView.setImageDrawable(icon);
437                     mIconView.setVisibility(VISIBLE);
438                 } else if (mIconView != null) {
439                     mIconView.setVisibility(GONE);
440                     mIconView.setImageDrawable(null);
441                 }
442
443                 if (text != 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);
452                         addView(textView);
453                         mTextView = textView;
454                     }
455                     mTextView.setTextCompat(text);
456                     mTextView.setVisibility(VISIBLE);
457                 } else if (mTextView != null) {
458                     mTextView.setVisibility(GONE);
459                     mTextView.setText(null);
460                 }
461
462                 if (mIconView != null) {
463                     mIconView.setContentDescription(tab.getContentDescription());
464                 }
465             }
466         }
467
468         public ActionBar.Tab getTab() {
469             return mTab;
470         }
471     }
472
473     private class TabAdapter extends BaseAdapter {
474         @Override
475         public int getCount() {
476             return mTabLayout.getChildCount();
477         }
478
479         @Override
480         public Object getItem(int position) {
481             return ((TabView) mTabLayout.getChildAt(position)).getTab();
482         }
483
484         @Override
485         public long getItemId(int position) {
486             return position;
487         }
488
489         @Override
490         public View getView(int position, View convertView, ViewGroup parent) {
491             if (convertView == null) {
492                 convertView = createTabView((ActionBar.Tab) getItem(position), true);
493             } else {
494                 ((TabView) convertView).bindTab((ActionBar.Tab) getItem(position));
495             }
496             return convertView;
497         }
498     }
499
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);
508             }
509         }
510     }
511
512     protected class VisibilityAnimListener implements Animator.AnimatorListener {
513         private boolean mCanceled = false;
514         private int mFinalVisibility;
515
516         public VisibilityAnimListener withFinalVisibility(int visibility) {
517             mFinalVisibility = visibility;
518             return this;
519         }
520
521         @Override
522         public void onAnimationStart(Animator animation) {
523             setVisibility(VISIBLE);
524             mVisibilityAnim = animation;
525             mCanceled = false;
526         }
527
528         @Override
529         public void onAnimationEnd(Animator animation) {
530             if (mCanceled) return;
531
532             mVisibilityAnim = null;
533             setVisibility(mFinalVisibility);
534         }
535
536         @Override
537         public void onAnimationCancel(Animator animation) {
538             mCanceled = true;
539         }
540
541         @Override
542         public void onAnimationRepeat(Animator animation) {
543         }
544     }
545 }