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