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