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