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