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