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.BitmapDrawable;
     35 import android.graphics.drawable.Drawable;
     36 import android.graphics.drawable.LayerDrawable;
     37 import android.graphics.drawable.PaintDrawable;
     38 import android.view.Gravity;
     39 import android.view.LayoutInflater;
     40 import android.view.View;
     41 import android.view.View.OnClickListener;
     42 import android.widget.ImageButton;
     43 import android.widget.ImageView;
     44 import android.widget.LinearLayout;
     45 import android.widget.TextView;
     46 
     47 import java.util.HashMap;
     48 import java.util.List;
     49 import java.util.Map;
     50 
     51 /**
     52  * tabbed title bar for xlarge screen browser
     53  */
     54 public 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