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