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