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