Home | History | Annotate | Download | only in browser
      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 
     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
    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 
    136     void setUseQuickControls(boolean useQuickControls) {
    137         mUseQuickControls = useQuickControls;
    138         mNewTab.setVisibility(mUseQuickControls ? View.GONE
    139                 : View.VISIBLE);
    140     }
    141 
    142     int getTabCount() {
    143         return mTabMap.size();
    144     }
    145 
    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
    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
    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 
    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 
    218     private void showUrlBar() {
    219         mUi.stopWebViewScrolling();
    220         mUi.showTitleBar();
    221     }
    222 
    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 
    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          */
    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
    285         public void onClick(View v) {
    286             if (v == mClose) {
    287                 closeTab();
    288             }
    289         }
    290 
    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 
    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
    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 
    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 
    332         void setDisplayTitle(String title) {
    333             mTitle.setText(title);
    334         }
    335 
    336         void setFavicon(Drawable d) {
    337             mIconView.setImageDrawable(d);
    338         }
    339 
    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 
    349         private void closeTab() {
    350             if (mTab == mTabControl.getCurrentTab()) {
    351                 mUiController.closeCurrentTab();
    352             } else {
    353                 mUiController.closeTab(mTab);
    354             }
    355         }
    356 
    357         @Override
    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
    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 
    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             (mSelected ? mActiveShader : mInactiveShader).setLocalMatrix(matrix);
    402             canvas.drawPath(clipPath, paint);
    403             if (isFocused()) {
    404                 canvas.drawPath(mFocusPath, mFocusPaint);
    405             }
    406         }
    407 
    408         private void setTabPath(Path path, int l, int t, int r, int b) {
    409             path.reset();
    410             path.moveTo(l, b);
    411             path.lineTo(l, t);
    412             path.lineTo(r - mTabSliceWidth, t);
    413             path.lineTo(r, b);
    414             path.close();
    415         }
    416 
    417         private void setFocusPath(Path path, int l, int t, int r, int b) {
    418             path.reset();
    419             path.moveTo(l, b);
    420             path.lineTo(l, t);
    421             path.lineTo(r - mTabSliceWidth, t);
    422             path.lineTo(r, b);
    423         }
    424 
    425     }
    426 
    427     private void animateTabOut(final Tab tab, final TabView tv) {
    428         ObjectAnimator scalex = ObjectAnimator.ofFloat(tv, "scaleX", 1.0f, 0.0f);
    429         ObjectAnimator scaley = ObjectAnimator.ofFloat(tv, "scaleY", 1.0f, 0.0f);
    430         ObjectAnimator alpha = ObjectAnimator.ofFloat(tv, "alpha", 1.0f, 0.0f);
    431         AnimatorSet animator = new AnimatorSet();
    432         animator.playTogether(scalex, scaley, alpha);
    433         animator.setDuration(150);
    434         animator.addListener(new AnimatorListener() {
    435 
    436             @Override
    437             public void onAnimationCancel(Animator animation) {
    438             }
    439 
    440             @Override
    441             public void onAnimationEnd(Animator animation) {
    442                 mTabs.removeTab(tv);
    443                 mTabMap.remove(tab);
    444                 mUi.onRemoveTabCompleted(tab);
    445             }
    446 
    447             @Override
    448             public void onAnimationRepeat(Animator animation) {
    449             }
    450 
    451             @Override
    452             public void onAnimationStart(Animator animation) {
    453             }
    454 
    455         });
    456         animator.start();
    457     }
    458 
    459     private void animateTabIn(final Tab tab, final TabView tv) {
    460         ObjectAnimator scalex = ObjectAnimator.ofFloat(tv, "scaleX", 0.0f, 1.0f);
    461         scalex.setDuration(150);
    462         scalex.addListener(new AnimatorListener() {
    463 
    464             @Override
    465             public void onAnimationCancel(Animator animation) {
    466             }
    467 
    468             @Override
    469             public void onAnimationEnd(Animator animation) {
    470                 mUi.onAddTabCompleted(tab);
    471             }
    472 
    473             @Override
    474             public void onAnimationRepeat(Animator animation) {
    475             }
    476 
    477             @Override
    478             public void onAnimationStart(Animator animation) {
    479                 mTabs.addTab(tv);
    480             }
    481 
    482         });
    483         scalex.start();
    484     }
    485 
    486     // TabChangeListener implementation
    487 
    488     public void onSetActiveTab(Tab tab) {
    489         mTabs.setSelectedTab(mTabControl.getTabPosition(tab));
    490     }
    491 
    492     public void onFavicon(Tab tab, Bitmap favicon) {
    493         TabView tv = mTabMap.get(tab);
    494         if (tv != null) {
    495             tv.setFavicon(mUi.getFaviconDrawable(favicon));
    496         }
    497     }
    498 
    499     public void onNewTab(Tab tab) {
    500         TabView tv = buildTabView(tab);
    501         animateTabIn(tab, tv);
    502     }
    503 
    504     public void onRemoveTab(Tab tab) {
    505         TabView tv = mTabMap.get(tab);
    506         if (tv != null) {
    507             animateTabOut(tab, tv);
    508         } else {
    509             mTabMap.remove(tab);
    510         }
    511     }
    512 
    513     public void onUrlAndTitle(Tab tab, String url, String title) {
    514         TabView tv = mTabMap.get(tab);
    515         if (tv != null) {
    516             if (title != null) {
    517                 tv.setDisplayTitle(title);
    518             } else if (url != null) {
    519                 tv.setDisplayTitle(UrlUtils.stripUrl(url));
    520             }
    521             tv.updateTabIcons();
    522         }
    523     }
    524 
    525     private boolean isLoading() {
    526         Tab tab = mTabControl.getCurrentTab();
    527         if (tab != null) {
    528             return tab.inPageLoad();
    529         } else {
    530             return false;
    531         }
    532     }
    533 
    534 }
    535