Home | History | Annotate | Download | only in widget
      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.R;
     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.content.res.TypedArray;
     27 import android.graphics.drawable.Drawable;
     28 import android.text.TextUtils.TruncateAt;
     29 import android.view.Gravity;
     30 import android.view.View;
     31 import android.view.ViewGroup;
     32 import android.view.ViewParent;
     33 import android.view.animation.DecelerateInterpolator;
     34 import android.widget.AdapterView;
     35 import android.widget.BaseAdapter;
     36 import android.widget.HorizontalScrollView;
     37 import android.widget.ImageView;
     38 import android.widget.LinearLayout;
     39 import android.widget.ListView;
     40 import android.widget.Spinner;
     41 import android.widget.TextView;
     42 
     43 /**
     44  * This widget implements the dynamic action bar tab behavior that can change
     45  * across different configurations or circumstances.
     46  */
     47 public class ScrollingTabContainerView extends HorizontalScrollView
     48         implements AdapterView.OnItemSelectedListener {
     49     private static final String TAG = "ScrollingTabContainerView";
     50     Runnable mTabSelector;
     51     private TabClickListener mTabClickListener;
     52 
     53     private LinearLayout mTabLayout;
     54     private Spinner mTabSpinner;
     55     private boolean mAllowCollapse;
     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 TimeInterpolator 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.ActionBar,
     73                 com.android.internal.R.attr.actionBarStyle, 0);
     74         setContentHeight(a.getLayoutDimension(R.styleable.ActionBar_height, 0));
     75         a.recycle();
     76 
     77         mTabLayout = createTabLayout();
     78         addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
     79                 ViewGroup.LayoutParams.MATCH_PARENT));
     80     }
     81 
     82     @Override
     83     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     84         final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
     85         final boolean lockedExpanded = widthMode == MeasureSpec.EXACTLY;
     86         setFillViewport(lockedExpanded);
     87 
     88         final int childCount = mTabLayout.getChildCount();
     89         if (childCount > 1 &&
     90                 (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)) {
     91             if (childCount > 2) {
     92                 mMaxTabWidth = (int) (MeasureSpec.getSize(widthMeasureSpec) * 0.4f);
     93             } else {
     94                 mMaxTabWidth = MeasureSpec.getSize(widthMeasureSpec) / 2;
     95             }
     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      */
    131     private boolean isCollapsed() {
    132         return mTabSpinner != null && mTabSpinner.getParent() == this;
    133     }
    134 
    135     public void setAllowCollapse(boolean allowCollapse) {
    136         mAllowCollapse = allowCollapse;
    137     }
    138 
    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 
    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 
    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 
    181     public void setContentHeight(int contentHeight) {
    182         mContentHeight = contentHeight;
    183         requestLayout();
    184     }
    185 
    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.setLayoutParams(new LinearLayout.LayoutParams(
    191                 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT));
    192         return tabLayout;
    193     }
    194 
    195     private Spinner createSpinner() {
    196         final Spinner spinner = new Spinner(getContext(), null,
    197                 com.android.internal.R.attr.actionDropDownStyle);
    198         spinner.setLayoutParams(new LinearLayout.LayoutParams(
    199                 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT));
    200         spinner.setOnItemSelectedListener(this);
    201         return spinner;
    202     }
    203 
    204     @Override
    205     protected void onConfigurationChanged(Configuration newConfig) {
    206         super.onConfigurationChanged(newConfig);
    207 
    208         // Action bar can change size on configuration changes.
    209         // Reread the desired height from the theme-specified style.
    210         TypedArray a = getContext().obtainStyledAttributes(null, R.styleable.ActionBar,
    211                 com.android.internal.R.attr.actionBarStyle, 0);
    212         setContentHeight(a.getLayoutDimension(R.styleable.ActionBar_height, 0));
    213         a.recycle();
    214     }
    215 
    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 
    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
    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
    265     public void onDetachedFromWindow() {
    266         super.onDetachedFromWindow();
    267         if (mTabSelector != null) {
    268             removeCallbacks(mTabSelector);
    269         }
    270     }
    271 
    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 
    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 
    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 
    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 
    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 
    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
    350     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
    351         TabView tabView = (TabView) view;
    352         tabView.getTab().select();
    353     }
    354 
    355     @Override
    356     public void onNothingSelected(AdapterView<?> parent) {
    357     }
    358 
    359     private class TabView extends LinearLayout {
    360         private ActionBar.Tab mTab;
    361         private TextView mTextView;
    362         private ImageView mIconView;
    363         private View mCustomView;
    364 
    365         public TabView(Context context, ActionBar.Tab tab, boolean forList) {
    366             super(context, null, com.android.internal.R.attr.actionBarTabStyle);
    367             mTab = tab;
    368 
    369             if (forList) {
    370                 setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
    371             }
    372 
    373             update();
    374         }
    375 
    376         public void bindTab(ActionBar.Tab tab) {
    377             mTab = tab;
    378             update();
    379         }
    380 
    381         @Override
    382         public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    383             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    384 
    385             // Re-measure if we went beyond our maximum size.
    386             if (mMaxTabWidth > 0 && getMeasuredWidth() > mMaxTabWidth) {
    387                 super.onMeasure(MeasureSpec.makeMeasureSpec(mMaxTabWidth, MeasureSpec.EXACTLY),
    388                         heightMeasureSpec);
    389             }
    390         }
    391 
    392         public void update() {
    393             final ActionBar.Tab tab = mTab;
    394             final View custom = tab.getCustomView();
    395             if (custom != null) {
    396                 final ViewParent customParent = custom.getParent();
    397                 if (customParent != this) {
    398                     if (customParent != null) ((ViewGroup) customParent).removeView(custom);
    399                     addView(custom);
    400                 }
    401                 mCustomView = custom;
    402                 if (mTextView != null) mTextView.setVisibility(GONE);
    403                 if (mIconView != null) {
    404                     mIconView.setVisibility(GONE);
    405                     mIconView.setImageDrawable(null);
    406                 }
    407             } else {
    408                 if (mCustomView != null) {
    409                     removeView(mCustomView);
    410                     mCustomView = null;
    411                 }
    412 
    413                 final Drawable icon = tab.getIcon();
    414                 final CharSequence text = tab.getText();
    415 
    416                 if (icon != null) {
    417                     if (mIconView == null) {
    418                         ImageView iconView = new ImageView(getContext());
    419                         LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
    420                                 LayoutParams.WRAP_CONTENT);
    421                         lp.gravity = Gravity.CENTER_VERTICAL;
    422                         iconView.setLayoutParams(lp);
    423                         addView(iconView, 0);
    424                         mIconView = iconView;
    425                     }
    426                     mIconView.setImageDrawable(icon);
    427                     mIconView.setVisibility(VISIBLE);
    428                 } else if (mIconView != null) {
    429                     mIconView.setVisibility(GONE);
    430                     mIconView.setImageDrawable(null);
    431                 }
    432 
    433                 if (text != null) {
    434                     if (mTextView == null) {
    435                         TextView textView = new TextView(getContext(), null,
    436                                 com.android.internal.R.attr.actionBarTabTextStyle);
    437                         textView.setEllipsize(TruncateAt.END);
    438                         LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
    439                                 LayoutParams.WRAP_CONTENT);
    440                         lp.gravity = Gravity.CENTER_VERTICAL;
    441                         textView.setLayoutParams(lp);
    442                         addView(textView);
    443                         mTextView = textView;
    444                     }
    445                     mTextView.setText(text);
    446                     mTextView.setVisibility(VISIBLE);
    447                 } else if (mTextView != null) {
    448                     mTextView.setVisibility(GONE);
    449                     mTextView.setText(null);
    450                 }
    451 
    452                 if (mIconView != null) {
    453                     mIconView.setContentDescription(tab.getContentDescription());
    454                 }
    455             }
    456         }
    457 
    458         public ActionBar.Tab getTab() {
    459             return mTab;
    460         }
    461     }
    462 
    463     private class TabAdapter extends BaseAdapter {
    464         @Override
    465         public int getCount() {
    466             return mTabLayout.getChildCount();
    467         }
    468 
    469         @Override
    470         public Object getItem(int position) {
    471             return ((TabView) mTabLayout.getChildAt(position)).getTab();
    472         }
    473 
    474         @Override
    475         public long getItemId(int position) {
    476             return position;
    477         }
    478 
    479         @Override
    480         public View getView(int position, View convertView, ViewGroup parent) {
    481             if (convertView == null) {
    482                 convertView = createTabView((ActionBar.Tab) getItem(position), true);
    483             } else {
    484                 ((TabView) convertView).bindTab((ActionBar.Tab) getItem(position));
    485             }
    486             return convertView;
    487         }
    488     }
    489 
    490     private class TabClickListener implements OnClickListener {
    491         public void onClick(View view) {
    492             TabView tabView = (TabView) view;
    493             tabView.getTab().select();
    494             final int tabCount = mTabLayout.getChildCount();
    495             for (int i = 0; i < tabCount; i++) {
    496                 final View child = mTabLayout.getChildAt(i);
    497                 child.setSelected(child == view);
    498             }
    499         }
    500     }
    501 
    502     protected class VisibilityAnimListener implements Animator.AnimatorListener {
    503         private boolean mCanceled = false;
    504         private int mFinalVisibility;
    505 
    506         public VisibilityAnimListener withFinalVisibility(int visibility) {
    507             mFinalVisibility = visibility;
    508             return this;
    509         }
    510 
    511         @Override
    512         public void onAnimationStart(Animator animation) {
    513             setVisibility(VISIBLE);
    514             mVisibilityAnim = animation;
    515             mCanceled = false;
    516         }
    517 
    518         @Override
    519         public void onAnimationEnd(Animator animation) {
    520             if (mCanceled) return;
    521 
    522             mVisibilityAnim = null;
    523             setVisibility(mFinalVisibility);
    524         }
    525 
    526         @Override
    527         public void onAnimationCancel(Animator animation) {
    528             mCanceled = true;
    529         }
    530 
    531         @Override
    532         public void onAnimationRepeat(Animator animation) {
    533         }
    534     }
    535 }
    536