TabBar.java revision 43cfe8a4521b5c153055fbfa181940bc56bb3b06
1/* 2 * Copyright (C) 2010 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 17package com.android.browser; 18 19import android.animation.Animator; 20import android.animation.Animator.AnimatorListener; 21import android.animation.AnimatorSet; 22import android.animation.ObjectAnimator; 23import android.app.Activity; 24import android.content.Context; 25import android.content.res.Configuration; 26import android.content.res.Resources; 27import android.graphics.Bitmap; 28import android.graphics.BitmapShader; 29import android.graphics.Canvas; 30import android.graphics.Matrix; 31import android.graphics.Paint; 32import android.graphics.Path; 33import android.graphics.Shader; 34import android.graphics.drawable.BitmapDrawable; 35import android.graphics.drawable.Drawable; 36import android.graphics.drawable.LayerDrawable; 37import android.graphics.drawable.PaintDrawable; 38import android.view.ContextMenu; 39import android.view.Gravity; 40import android.view.LayoutInflater; 41import android.view.MenuInflater; 42import android.view.View; 43import android.view.View.OnClickListener; 44import android.widget.ImageButton; 45import android.widget.ImageView; 46import android.widget.LinearLayout; 47import android.widget.TextView; 48 49import java.util.HashMap; 50import java.util.List; 51import java.util.Map; 52 53/** 54 * tabbed title bar for xlarge screen browser 55 */ 56public class TabBar extends LinearLayout implements OnClickListener { 57 58 private static final int PROGRESS_MAX = 100; 59 60 private Activity mActivity; 61 private UiController mUiController; 62 private TabControl mTabControl; 63 private XLargeUi mUi; 64 65 private int mTabWidthSelected; 66 private int mTabWidthUnselected; 67 68 private TabScrollView mTabs; 69 70 private ImageButton mNewTab; 71 private int mButtonWidth; 72 73 private Map<Tab, TabView> mTabMap; 74 75 private Drawable mGenericFavicon; 76 77 private int mCurrentTextureWidth = 0; 78 private int mCurrentTextureHeight = 0; 79 80 private Drawable mActiveDrawable; 81 private Drawable mInactiveDrawable; 82 83 private final Paint mActiveShaderPaint = new Paint(); 84 private final Paint mInactiveShaderPaint = new Paint(); 85 private final Paint mFocusPaint = new Paint(); 86 private final Matrix mActiveMatrix = new Matrix(); 87 private final Matrix mInactiveMatrix = new Matrix(); 88 89 private BitmapShader mActiveShader; 90 private BitmapShader mInactiveShader; 91 92 private int mTabOverlap; 93 private int mAddTabOverlap; 94 private int mTabSliceWidth; 95 private boolean mUseQuickControls; 96 97 public TabBar(Activity activity, UiController controller, XLargeUi ui) { 98 super(activity); 99 mActivity = activity; 100 mUiController = controller; 101 mTabControl = mUiController.getTabControl(); 102 mUi = ui; 103 Resources res = activity.getResources(); 104 mTabWidthSelected = (int) res.getDimension(R.dimen.tab_width_selected); 105 mTabWidthUnselected = (int) res.getDimension(R.dimen.tab_width_unselected); 106 mActiveDrawable = res.getDrawable(R.drawable.bg_urlbar); 107 mInactiveDrawable = res.getDrawable(R.drawable.browsertab_inactive); 108 109 mTabMap = new HashMap<Tab, TabView>(); 110 LayoutInflater factory = LayoutInflater.from(activity); 111 factory.inflate(R.layout.tab_bar, this); 112 setPadding(0, (int) res.getDimension(R.dimen.tab_padding_top), 0, 0); 113 mTabs = (TabScrollView) findViewById(R.id.tabs); 114 mNewTab = (ImageButton) findViewById(R.id.newtab); 115 mNewTab.setOnClickListener(this); 116 mGenericFavicon = res.getDrawable(R.drawable.app_web_browser_sm); 117 118 updateTabs(mUiController.getTabs()); 119 mButtonWidth = -1; 120 // tab dimensions 121 mTabOverlap = (int) res.getDimension(R.dimen.tab_overlap); 122 mAddTabOverlap = (int) res.getDimension(R.dimen.tab_addoverlap); 123 mTabSliceWidth = (int) res.getDimension(R.dimen.tab_slice); 124 125 mActiveShaderPaint.setStyle(Paint.Style.FILL); 126 mActiveShaderPaint.setAntiAlias(true); 127 128 mInactiveShaderPaint.setStyle(Paint.Style.FILL); 129 mInactiveShaderPaint.setAntiAlias(true); 130 131 mFocusPaint.setStyle(Paint.Style.STROKE); 132 mFocusPaint.setStrokeWidth(res.getDimension(R.dimen.tab_focus_stroke)); 133 mFocusPaint.setAntiAlias(true); 134 mFocusPaint.setColor(res.getColor(R.color.tabFocusHighlight)); 135 } 136 137 @Override 138 public void onConfigurationChanged(Configuration config) { 139 super.onConfigurationChanged(config); 140 Resources res = mActivity.getResources(); 141 mTabWidthSelected = (int) res.getDimension(R.dimen.tab_width_selected); 142 mTabWidthUnselected = (int) res.getDimension(R.dimen.tab_width_unselected); 143 // force update of tab bar 144 mTabs.updateLayout(); 145 } 146 147 void setUseQuickControls(boolean useQuickControls) { 148 mUseQuickControls = useQuickControls; 149 mNewTab.setVisibility(mUseQuickControls ? View.GONE 150 : View.VISIBLE); 151 } 152 153 int getTabCount() { 154 return mTabMap.size(); 155 } 156 157 void updateTabs(List<Tab> tabs) { 158 mTabs.clearTabs(); 159 mTabMap.clear(); 160 for (Tab tab : tabs) { 161 TabView tv = buildTabView(tab); 162 mTabs.addTab(tv); 163 } 164 mTabs.setSelectedTab(mTabControl.getCurrentPosition()); 165 } 166 167 @Override 168 protected void onMeasure(int hspec, int vspec) { 169 super.onMeasure(hspec, vspec); 170 int w = getMeasuredWidth(); 171 // adjust for new tab overlap 172 if (!mUseQuickControls) { 173 w -= mAddTabOverlap; 174 } 175 setMeasuredDimension(w, getMeasuredHeight()); 176 } 177 178 @Override 179 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 180 // use paddingLeft and paddingTop 181 int pl = getPaddingLeft(); 182 int pt = getPaddingTop(); 183 int sw = mTabs.getMeasuredWidth(); 184 int w = right - left - pl; 185 if (mUseQuickControls) { 186 mButtonWidth = 0; 187 } else { 188 mButtonWidth = mNewTab.getMeasuredWidth() - mAddTabOverlap; 189 if (w-sw < mButtonWidth) { 190 sw = w - mButtonWidth; 191 } 192 } 193 mTabs.layout(pl, pt, pl + sw, bottom - top); 194 // adjust for overlap 195 if (!mUseQuickControls) { 196 mNewTab.layout(pl + sw - mAddTabOverlap, pt, 197 pl + sw + mButtonWidth - mAddTabOverlap, bottom - top); 198 } 199 } 200 201 public void onClick(View view) { 202 if (mNewTab == view) { 203 mUiController.openTabToHomePage(); 204 } else if (mTabs.getSelectedTab() == view) { 205 if (mUseQuickControls) { 206 if (mUi.isTitleBarShowing() && !isLoading()) { 207 mUi.stopEditingUrl(); 208 mUi.hideTitleBar(); 209 } else { 210 mUi.stopWebViewScrolling(); 211 mUi.editUrl(false); 212 } 213 } else if (mUi.isTitleBarShowing() && !isLoading()) { 214 mUi.stopEditingUrl(); 215 mUi.hideTitleBar(); 216 } else { 217 showUrlBar(); 218 } 219 } else if (view instanceof TabView) { 220 final Tab tab = ((TabView) view).mTab; 221 int ix = mTabs.getChildIndex(view); 222 if (ix >= 0) { 223 mTabs.setSelectedTab(ix); 224 mUiController.switchToTab(tab); 225 } 226 } 227 } 228 229 private void showUrlBar() { 230 mUi.stopWebViewScrolling(); 231 mUi.showTitleBar(); 232 } 233 234 private TabView buildTabView(Tab tab) { 235 TabView tabview = new TabView(mActivity, tab); 236 mTabMap.put(tab, tabview); 237 tabview.setOnClickListener(this); 238 return tabview; 239 } 240 241 private static Bitmap getDrawableAsBitmap(Drawable drawable, int width, int height) { 242 Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 243 Canvas c = new Canvas(b); 244 drawable.setBounds(0, 0, width, height); 245 drawable.draw(c); 246 c.setBitmap(null); 247 return b; 248 } 249 250 /** 251 * View used in the tab bar 252 */ 253 class TabView extends LinearLayout implements OnClickListener { 254 255 Tab mTab; 256 View mTabContent; 257 TextView mTitle; 258 View mIncognito; 259 View mSnapshot; 260 ImageView mIconView; 261 ImageView mLock; 262 ImageView mClose; 263 boolean mSelected; 264 boolean mInLoad; 265 Path mPath; 266 Path mFocusPath; 267 int[] mWindowPos; 268 269 /** 270 * @param context 271 */ 272 public TabView(Context context, Tab tab) { 273 super(context); 274 setWillNotDraw(false); 275 mPath = new Path(); 276 mFocusPath = new Path(); 277 mWindowPos = new int[2]; 278 mTab = tab; 279 setGravity(Gravity.CENTER_VERTICAL); 280 setOrientation(LinearLayout.HORIZONTAL); 281 setPadding(mTabOverlap, 0, mTabSliceWidth, 0); 282 LayoutInflater inflater = LayoutInflater.from(getContext()); 283 mTabContent = inflater.inflate(R.layout.tab_title, this, true); 284 mTitle = (TextView) mTabContent.findViewById(R.id.title); 285 mIconView = (ImageView) mTabContent.findViewById(R.id.favicon); 286 mLock = (ImageView) mTabContent.findViewById(R.id.lock); 287 mClose = (ImageView) mTabContent.findViewById(R.id.close); 288 mClose.setOnClickListener(this); 289 mIncognito = mTabContent.findViewById(R.id.incognito); 290 mSnapshot = mTabContent.findViewById(R.id.snapshot); 291 mSelected = false; 292 mInLoad = false; 293 // update the status 294 updateFromTab(); 295 } 296 297 @Override 298 public void onClick(View v) { 299 if (v == mClose) { 300 closeTab(); 301 } 302 } 303 304 private void updateFromTab() { 305 String displayTitle = mTab.getTitle(); 306 if (displayTitle == null) { 307 displayTitle = mTab.getUrl(); 308 } 309 setDisplayTitle(displayTitle); 310 setProgress(mTab.getLoadProgress()); 311 if (mTab.getFavicon() != null) { 312 setFavicon(renderFavicon(mTab.getFavicon())); 313 } 314 updateTabIcons(); 315 } 316 317 private void updateTabIcons() { 318 mIncognito.setVisibility( 319 mTab.isPrivateBrowsingEnabled() ? 320 View.VISIBLE : View.GONE); 321 mSnapshot.setVisibility(mTab.isSnapshot() 322 ? View.VISIBLE : View.GONE); 323 } 324 325 @Override 326 public void setActivated(boolean selected) { 327 mSelected = selected; 328 mClose.setVisibility(mSelected ? View.VISIBLE : View.GONE); 329 mTitle.setTextAppearance(mActivity, mSelected ? 330 R.style.TabTitleSelected : R.style.TabTitleUnselected); 331 setHorizontalFadingEdgeEnabled(!mSelected); 332 super.setActivated(selected); 333 updateLayoutParams(); 334 setFocusable(!selected); 335 postInvalidate(); 336 } 337 338 public void updateLayoutParams() { 339 LayoutParams lp = (LinearLayout.LayoutParams) getLayoutParams(); 340 lp.width = mSelected ? mTabWidthSelected : mTabWidthUnselected; 341 lp.height = LayoutParams.MATCH_PARENT; 342 setLayoutParams(lp); 343 } 344 345 void setDisplayTitle(String title) { 346 mTitle.setText(title); 347 } 348 349 void setFavicon(Drawable d) { 350 mIconView.setImageDrawable(d); 351 } 352 353 void setLock(Drawable d) { 354 if (null == d) { 355 mLock.setVisibility(View.GONE); 356 } else { 357 mLock.setImageDrawable(d); 358 mLock.setVisibility(View.VISIBLE); 359 } 360 } 361 362 void setProgress(int newProgress) { 363 if (newProgress >= PROGRESS_MAX) { 364 mInLoad = false; 365 } else { 366 if (!mInLoad && getWindowToken() != null) { 367 mInLoad = true; 368 } 369 } 370 } 371 372 private void closeTab() { 373 if (mTab == mTabControl.getCurrentTab()) { 374 mUiController.closeCurrentTab(); 375 } else { 376 mUiController.closeTab(mTab); 377 } 378 } 379 380 @Override 381 protected void onLayout(boolean changed, int l, int t, int r, int b) { 382 super.onLayout(changed, l, t, r, b); 383 setTabPath(mPath, 0, 0, r - l, b - t); 384 setFocusPath(mFocusPath, 0, 0, r - l, b - t); 385 } 386 387 @Override 388 protected void dispatchDraw(Canvas canvas) { 389 if (mCurrentTextureWidth != mUi.getContentWidth() || 390 mCurrentTextureHeight != getHeight()) { 391 mCurrentTextureWidth = mUi.getContentWidth(); 392 mCurrentTextureHeight = getHeight(); 393 394 if (mCurrentTextureWidth > 0 && mCurrentTextureHeight > 0) { 395 Bitmap activeTexture = getDrawableAsBitmap(mActiveDrawable, 396 mCurrentTextureWidth, mCurrentTextureHeight); 397 Bitmap inactiveTexture = getDrawableAsBitmap(mInactiveDrawable, 398 mCurrentTextureWidth, mCurrentTextureHeight); 399 400 mActiveShader = new BitmapShader(activeTexture, 401 Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 402 mActiveShaderPaint.setShader(mActiveShader); 403 404 mInactiveShader = new BitmapShader(inactiveTexture, 405 Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 406 mInactiveShaderPaint.setShader(mInactiveShader); 407 } 408 } 409 // add some monkey protection 410 if ((mActiveShader != null) && (mInactiveShader != null)) { 411 int state = canvas.save(); 412 getLocationInWindow(mWindowPos); 413 Paint paint = mSelected ? mActiveShaderPaint : mInactiveShaderPaint; 414 drawClipped(canvas, paint, mPath, mWindowPos[0]); 415 canvas.restoreToCount(state); 416 } 417 super.dispatchDraw(canvas); 418 } 419 420 private void drawClipped(Canvas canvas, Paint paint, Path clipPath, int left) { 421 // TODO: We should change the matrix/shader only when needed 422 final Matrix matrix = mSelected ? mActiveMatrix : mInactiveMatrix; 423 matrix.setTranslate(-left, 0.0f); 424 (mSelected ? mActiveShader : mInactiveShader).setLocalMatrix(matrix); 425 canvas.drawPath(clipPath, paint); 426 if (isFocused()) { 427 canvas.drawPath(mFocusPath, mFocusPaint); 428 } 429 } 430 431 private void setTabPath(Path path, int l, int t, int r, int b) { 432 path.reset(); 433 path.moveTo(l, b); 434 path.lineTo(l, t); 435 path.lineTo(r - mTabSliceWidth, t); 436 path.lineTo(r, b); 437 path.close(); 438 } 439 440 private void setFocusPath(Path path, int l, int t, int r, int b) { 441 path.reset(); 442 path.moveTo(l, b); 443 path.lineTo(l, t); 444 path.lineTo(r - mTabSliceWidth, t); 445 path.lineTo(r, b); 446 } 447 448 } 449 450 static Drawable createFaviconBackground(Context context) { 451 PaintDrawable faviconBackground = new PaintDrawable(); 452 Resources res = context.getResources(); 453 faviconBackground.getPaint().setColor(context.getResources() 454 .getColor(R.color.tabFaviconBackground)); 455 faviconBackground.setCornerRadius( 456 res.getDimension(R.dimen.tab_favicon_corner_radius)); 457 return faviconBackground; 458 } 459 460 private Drawable renderFavicon(Bitmap icon) { 461 Drawable[] array = new Drawable[2]; 462 array[0] = createFaviconBackground(getContext()); 463 if (icon == null) { 464 array[1] = mGenericFavicon; 465 } else { 466 array[1] = new BitmapDrawable(icon); 467 } 468 LayerDrawable d = new LayerDrawable(array); 469 d.setLayerInset(1, 2, 2, 2, 2); 470 return d; 471 } 472 473 private void animateTabOut(final Tab tab, final TabView tv) { 474 ObjectAnimator scalex = ObjectAnimator.ofFloat(tv, "scaleX", 1.0f, 0.0f); 475 ObjectAnimator scaley = ObjectAnimator.ofFloat(tv, "scaleY", 1.0f, 0.0f); 476 ObjectAnimator alpha = ObjectAnimator.ofFloat(tv, "alpha", 1.0f, 0.0f); 477 AnimatorSet animator = new AnimatorSet(); 478 animator.playTogether(scalex, scaley, alpha); 479 animator.setDuration(150); 480 animator.addListener(new AnimatorListener() { 481 482 @Override 483 public void onAnimationCancel(Animator animation) { 484 } 485 486 @Override 487 public void onAnimationEnd(Animator animation) { 488 mTabs.removeTab(tv); 489 mTabMap.remove(tab); 490 mUi.onRemoveTabCompleted(tab); 491 } 492 493 @Override 494 public void onAnimationRepeat(Animator animation) { 495 } 496 497 @Override 498 public void onAnimationStart(Animator animation) { 499 } 500 501 }); 502 animator.start(); 503 } 504 505 private void animateTabIn(final Tab tab, final TabView tv) { 506 ObjectAnimator scalex = ObjectAnimator.ofFloat(tv, "scaleX", 0.0f, 1.0f); 507 scalex.setDuration(150); 508 scalex.addListener(new AnimatorListener() { 509 510 @Override 511 public void onAnimationCancel(Animator animation) { 512 } 513 514 @Override 515 public void onAnimationEnd(Animator animation) { 516 mUi.onAddTabCompleted(tab); 517 } 518 519 @Override 520 public void onAnimationRepeat(Animator animation) { 521 } 522 523 @Override 524 public void onAnimationStart(Animator animation) { 525 mTabs.addTab(tv); 526 } 527 528 }); 529 scalex.start(); 530 } 531 532 // TabChangeListener implementation 533 534 public void onSetActiveTab(Tab tab) { 535 mTabs.setSelectedTab(mTabControl.getTabPosition(tab)); 536 TabView tv = mTabMap.get(tab); 537 if (tv != null) { 538 tv.setProgress(tv.mTab.getLoadProgress()); 539 } 540 } 541 542 public void onFavicon(Tab tab, Bitmap favicon) { 543 TabView tv = mTabMap.get(tab); 544 if (tv != null) { 545 tv.setFavicon(renderFavicon(favicon)); 546 } 547 } 548 549 public void onNewTab(Tab tab) { 550 TabView tv = buildTabView(tab); 551 animateTabIn(tab, tv); 552 } 553 554 public void onProgress(Tab tab, int progress) { 555 TabView tv = mTabMap.get(tab); 556 if (tv != null) { 557 tv.setProgress(progress); 558 } 559 } 560 561 public void onRemoveTab(Tab tab) { 562 TabView tv = mTabMap.get(tab); 563 if (tv != null) { 564 animateTabOut(tab, tv); 565 } else { 566 mTabMap.remove(tab); 567 } 568 } 569 570 public void onUrlAndTitle(Tab tab, String url, String title) { 571 TabView tv = mTabMap.get(tab); 572 if (tv != null) { 573 if (title != null) { 574 tv.setDisplayTitle(title); 575 } else if (url != null) { 576 tv.setDisplayTitle(UrlUtils.stripUrl(url)); 577 } 578 tv.updateTabIcons(); 579 } 580 } 581 582 private boolean isLoading() { 583 TabView tv = mTabMap.get(mTabControl.getCurrentTab()); 584 if (tv != null) { 585 return tv.mInLoad; 586 } else { 587 return false; 588 } 589 } 590 591} 592