ScrollingTabContainerView.java revision faa6ffa484f6f2a334c8bf2c7a9dc693a61608f3
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 */
16package com.android.internal.widget;
17
18import com.android.internal.R;
19
20import android.animation.Animator;
21import android.animation.ObjectAnimator;
22import android.animation.TimeInterpolator;
23import android.app.ActionBar;
24import android.content.Context;
25import android.content.res.Configuration;
26import android.content.res.TypedArray;
27import android.graphics.drawable.Drawable;
28import android.text.TextUtils.TruncateAt;
29import android.view.Gravity;
30import android.view.View;
31import android.view.ViewGroup;
32import android.view.animation.DecelerateInterpolator;
33import android.widget.HorizontalScrollView;
34import android.widget.ImageView;
35import android.widget.LinearLayout;
36import android.widget.TextView;
37
38public class ScrollingTabContainerView extends HorizontalScrollView {
39    Runnable mTabSelector;
40    private TabClickListener mTabClickListener;
41
42    private LinearLayout mTabLayout;
43
44    int mMaxTabWidth;
45
46    protected Animator mVisibilityAnim;
47    protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
48
49    private static final TimeInterpolator sAlphaInterpolator = new DecelerateInterpolator();
50
51    private static final int FADE_DURATION = 200;
52
53    public ScrollingTabContainerView(Context context) {
54        super(context);
55        setHorizontalScrollBarEnabled(false);
56    }
57
58    @Override
59    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
60        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
61        setFillViewport(widthMode == MeasureSpec.EXACTLY);
62
63        final int childCount = getChildCount();
64        if (childCount > 1 &&
65                (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)) {
66            if (childCount > 2) {
67                mMaxTabWidth = (int) (MeasureSpec.getSize(widthMeasureSpec) * 0.4f);
68            } else {
69                mMaxTabWidth = MeasureSpec.getSize(widthMeasureSpec) / 2;
70            }
71        } else {
72            mMaxTabWidth = -1;
73        }
74
75        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
76    }
77
78    public void setTabSelected(int position) {
79        if (mTabLayout == null) {
80            return;
81        }
82
83        final int tabCount = mTabLayout.getChildCount();
84        for (int i = 0; i < tabCount; i++) {
85            final View child = mTabLayout.getChildAt(i);
86            final boolean isSelected = i == position;
87            child.setSelected(isSelected);
88            if (isSelected) {
89                animateToTab(position);
90            }
91        }
92    }
93
94    public void setContentHeight(int contentHeight) {
95        mTabLayout.getLayoutParams().height = contentHeight;
96        requestLayout();
97    }
98
99    @Override
100    protected void onConfigurationChanged(Configuration newConfig) {
101        super.onConfigurationChanged(newConfig);
102
103        // Action bar can change size on configuration changes.
104        // Reread the desired height from the theme-specified style.
105        TypedArray a = getContext().obtainStyledAttributes(null, R.styleable.ActionBar,
106                com.android.internal.R.attr.actionBarStyle, 0);
107        setContentHeight(a.getLayoutDimension(R.styleable.ActionBar_height, 0));
108        a.recycle();
109    }
110
111    public void animateToVisibility(int visibility) {
112        if (mVisibilityAnim != null) {
113            mVisibilityAnim.cancel();
114        }
115        if (visibility == VISIBLE) {
116            if (getVisibility() != VISIBLE) {
117                setAlpha(0);
118            }
119            ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 1);
120            anim.setDuration(FADE_DURATION);
121            anim.setInterpolator(sAlphaInterpolator);
122
123            anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
124            anim.start();
125        } else {
126            ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 0);
127            anim.setDuration(FADE_DURATION);
128            anim.setInterpolator(sAlphaInterpolator);
129
130            anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
131            anim.start();
132        }
133    }
134
135    public void animateToTab(int position) {
136        final View tabView = mTabLayout.getChildAt(position);
137        if (mTabSelector != null) {
138            removeCallbacks(mTabSelector);
139        }
140        mTabSelector = new Runnable() {
141            public void run() {
142                final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2;
143                smoothScrollTo(scrollPos, 0);
144                mTabSelector = null;
145            }
146        };
147        post(mTabSelector);
148    }
149
150    public void setTabLayout(LinearLayout tabLayout) {
151        if (mTabLayout != tabLayout) {
152            if (mTabLayout != null) {
153                ((ViewGroup) mTabLayout.getParent()).removeView(mTabLayout);
154            }
155            if (tabLayout != null) {
156                addView(tabLayout);
157            }
158            mTabLayout = tabLayout;
159        }
160    }
161
162    public LinearLayout getTabLayout() {
163        return mTabLayout;
164    }
165
166    @Override
167    public void onDetachedFromWindow() {
168        super.onDetachedFromWindow();
169        if (mTabSelector != null) {
170            removeCallbacks(mTabSelector);
171        }
172    }
173
174    private TabView createTabView(ActionBar.Tab tab) {
175        final TabView tabView = new TabView(getContext(), tab);
176        tabView.setFocusable(true);
177
178        if (mTabClickListener == null) {
179            mTabClickListener = new TabClickListener();
180        }
181        tabView.setOnClickListener(mTabClickListener);
182        return tabView;
183    }
184
185    public void addTab(ActionBar.Tab tab, boolean setSelected) {
186        View tabView = createTabView(tab);
187        mTabLayout.addView(tabView, new LinearLayout.LayoutParams(0,
188                LayoutParams.MATCH_PARENT, 1));
189        if (setSelected) {
190            tabView.setSelected(true);
191        }
192    }
193
194    public void addTab(ActionBar.Tab tab, int position, boolean setSelected) {
195        final TabView tabView = createTabView(tab);
196        mTabLayout.addView(tabView, position, new LinearLayout.LayoutParams(
197                0, LayoutParams.MATCH_PARENT, 1));
198        if (setSelected) {
199            tabView.setSelected(true);
200        }
201    }
202
203    public void updateTab(int position) {
204        ((TabView) mTabLayout.getChildAt(position)).update();
205    }
206
207    public void removeTabAt(int position) {
208        if (mTabLayout != null) {
209            mTabLayout.removeViewAt(position);
210        }
211    }
212
213    public void removeAllTabs() {
214        if (mTabLayout != null) {
215            mTabLayout.removeAllViews();
216        }
217    }
218
219    private class TabView extends LinearLayout {
220        private ActionBar.Tab mTab;
221        private TextView mTextView;
222        private ImageView mIconView;
223        private View mCustomView;
224
225        public TabView(Context context, ActionBar.Tab tab) {
226            super(context, null, com.android.internal.R.attr.actionBarTabStyle);
227            mTab = tab;
228
229            update();
230        }
231
232        @Override
233        public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
234            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
235
236            // Re-measure if we went beyond our maximum size.
237            if (mMaxTabWidth > 0 && getMeasuredWidth() > mMaxTabWidth) {
238                super.onMeasure(MeasureSpec.makeMeasureSpec(mMaxTabWidth, MeasureSpec.EXACTLY),
239                        heightMeasureSpec);
240            }
241        }
242
243        public void update() {
244            final ActionBar.Tab tab = mTab;
245            final View custom = tab.getCustomView();
246            if (custom != null) {
247                addView(custom);
248                mCustomView = custom;
249                if (mTextView != null) mTextView.setVisibility(GONE);
250                if (mIconView != null) {
251                    mIconView.setVisibility(GONE);
252                    mIconView.setImageDrawable(null);
253                }
254            } else {
255                if (mCustomView != null) {
256                    removeView(mCustomView);
257                    mCustomView = null;
258                }
259
260                final Drawable icon = tab.getIcon();
261                final CharSequence text = tab.getText();
262
263                if (icon != null) {
264                    if (mIconView == null) {
265                        ImageView iconView = new ImageView(getContext());
266                        LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
267                                LayoutParams.WRAP_CONTENT);
268                        lp.gravity = Gravity.CENTER_VERTICAL;
269                        iconView.setLayoutParams(lp);
270                        addView(iconView, 0);
271                        mIconView = iconView;
272                    }
273                    mIconView.setImageDrawable(icon);
274                    mIconView.setVisibility(VISIBLE);
275                } else if (mIconView != null) {
276                    mIconView.setVisibility(GONE);
277                    mIconView.setImageDrawable(null);
278                }
279
280                if (text != null) {
281                    if (mTextView == null) {
282                        TextView textView = new TextView(getContext(), null,
283                                com.android.internal.R.attr.actionBarTabTextStyle);
284                        textView.setEllipsize(TruncateAt.END);
285                        LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
286                                LayoutParams.WRAP_CONTENT);
287                        lp.gravity = Gravity.CENTER_VERTICAL;
288                        textView.setLayoutParams(lp);
289                        addView(textView);
290                        mTextView = textView;
291                    }
292                    mTextView.setText(text);
293                    mTextView.setVisibility(VISIBLE);
294                } else if (mTextView != null) {
295                    mTextView.setVisibility(GONE);
296                    mTextView.setText(null);
297                }
298            }
299        }
300
301        public ActionBar.Tab getTab() {
302            return mTab;
303        }
304    }
305
306    private class TabClickListener implements OnClickListener {
307        public void onClick(View view) {
308            TabView tabView = (TabView) view;
309            tabView.getTab().select();
310            final int tabCount = mTabLayout.getChildCount();
311            for (int i = 0; i < tabCount; i++) {
312                final View child = mTabLayout.getChildAt(i);
313                child.setSelected(child == view);
314            }
315        }
316    }
317
318    protected class VisibilityAnimListener implements Animator.AnimatorListener {
319        private boolean mCanceled = false;
320        private int mFinalVisibility;
321
322        public VisibilityAnimListener withFinalVisibility(int visibility) {
323            mFinalVisibility = visibility;
324            return this;
325        }
326
327        @Override
328        public void onAnimationStart(Animator animation) {
329            setVisibility(VISIBLE);
330            mVisibilityAnim = animation;
331            mCanceled = false;
332        }
333
334        @Override
335        public void onAnimationEnd(Animator animation) {
336            if (mCanceled) return;
337
338            mVisibilityAnim = null;
339            setVisibility(mFinalVisibility);
340        }
341
342        @Override
343        public void onAnimationCancel(Animator animation) {
344            mCanceled = true;
345        }
346
347        @Override
348        public void onAnimationRepeat(Animator animation) {
349        }
350    }
351}
352