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