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