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