ActionBarView.java revision e0a799a2ac1ca78e30fbac9e4e12a063425c08d3
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.internal.widget;
18
19import com.android.internal.R;
20import com.android.internal.view.menu.ActionMenuItem;
21import com.android.internal.view.menu.ActionMenuPresenter;
22import com.android.internal.view.menu.ActionMenuView;
23import com.android.internal.view.menu.MenuBuilder;
24import com.android.internal.view.menu.MenuPresenter;
25
26import android.app.ActionBar;
27import android.app.ActionBar.OnNavigationListener;
28import android.app.Activity;
29import android.content.Context;
30import android.content.pm.ApplicationInfo;
31import android.content.pm.PackageManager;
32import android.content.pm.PackageManager.NameNotFoundException;
33import android.content.res.Resources;
34import android.content.res.TypedArray;
35import android.graphics.drawable.Drawable;
36import android.text.TextUtils;
37import android.text.TextUtils.TruncateAt;
38import android.util.AttributeSet;
39import android.util.DisplayMetrics;
40import android.util.Log;
41import android.view.ActionMode;
42import android.view.Gravity;
43import android.view.LayoutInflater;
44import android.view.Menu;
45import android.view.View;
46import android.view.ViewGroup;
47import android.view.ViewParent;
48import android.view.Window;
49import android.widget.AdapterView;
50import android.widget.FrameLayout;
51import android.widget.HorizontalScrollView;
52import android.widget.ImageView;
53import android.widget.LinearLayout;
54import android.widget.ProgressBar;
55import android.widget.Spinner;
56import android.widget.SpinnerAdapter;
57import android.widget.TextView;
58
59/**
60 * @hide
61 */
62public class ActionBarView extends ViewGroup {
63    private static final String TAG = "ActionBarView";
64
65    /**
66     * Display options applied by default
67     */
68    public static final int DISPLAY_DEFAULT = 0;
69
70    /**
71     * Display options that require re-layout as opposed to a simple invalidate
72     */
73    private static final int DISPLAY_RELAYOUT_MASK =
74            ActionBar.DISPLAY_SHOW_HOME |
75            ActionBar.DISPLAY_USE_LOGO |
76            ActionBar.DISPLAY_HOME_AS_UP |
77            ActionBar.DISPLAY_SHOW_CUSTOM |
78            ActionBar.DISPLAY_SHOW_TITLE;
79
80    private static final int DEFAULT_CUSTOM_GRAVITY = Gravity.LEFT | Gravity.CENTER_VERTICAL;
81
82    private final int mContentHeight;
83
84    private int mNavigationMode;
85    private int mDisplayOptions = ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_HOME_AS_UP;
86    private CharSequence mTitle;
87    private CharSequence mSubtitle;
88    private Drawable mIcon;
89    private Drawable mLogo;
90
91    private View mHomeLayout;
92    private View mHomeAsUpView;
93    private ImageView mIconView;
94    private LinearLayout mTitleLayout;
95    private TextView mTitleView;
96    private TextView mSubtitleView;
97    private Spinner mSpinner;
98    private LinearLayout mListNavLayout;
99    private HorizontalScrollView mTabScrollView;
100    private ViewGroup mTabLayout;
101    private View mCustomNavView;
102    private ProgressBar mProgressView;
103    private ProgressBar mIndeterminateProgressView;
104
105    private int mProgressBarPadding;
106    private int mItemPadding;
107
108    private int mTitleStyleRes;
109    private int mSubtitleStyleRes;
110    private int mProgressStyle;
111    private int mIndeterminateProgressStyle;
112
113    private boolean mSplitActionBar;
114    private boolean mUserTitle;
115    private boolean mIncludeTabs;
116
117    private MenuBuilder mOptionsMenu;
118    private ActionMenuView mMenuView;
119    private ActionMenuPresenter mActionMenuPresenter;
120
121    private ActionBarContextView mContextView;
122    private ViewGroup mSplitView;
123
124    private ActionMenuItem mLogoNavItem;
125
126    private SpinnerAdapter mSpinnerAdapter;
127    private OnNavigationListener mCallback;
128
129    private final AdapterView.OnItemSelectedListener mNavItemSelectedListener =
130            new AdapterView.OnItemSelectedListener() {
131        public void onItemSelected(AdapterView parent, View view, int position, long id) {
132            if (mCallback != null) {
133                mCallback.onNavigationItemSelected(position, id);
134            }
135        }
136        public void onNothingSelected(AdapterView parent) {
137            // Do nothing
138        }
139    };
140
141    private OnClickListener mTabClickListener = null;
142
143    public ActionBarView(Context context, AttributeSet attrs) {
144        super(context, attrs);
145
146        // Background is always provided by the container.
147        setBackgroundResource(0);
148
149        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar);
150
151        ApplicationInfo appInfo = context.getApplicationInfo();
152        PackageManager pm = context.getPackageManager();
153        mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode,
154                ActionBar.NAVIGATION_MODE_STANDARD);
155        mTitle = a.getText(R.styleable.ActionBar_title);
156        mSubtitle = a.getText(R.styleable.ActionBar_subtitle);
157
158        mLogo = a.getDrawable(R.styleable.ActionBar_logo);
159        if (mLogo == null) {
160            if (context instanceof Activity) {
161                try {
162                    mLogo = pm.getActivityLogo(((Activity) context).getComponentName());
163                } catch (NameNotFoundException e) {
164                    Log.e(TAG, "Activity component name not found!", e);
165                }
166            }
167            if (mLogo == null) {
168                mLogo = appInfo.loadLogo(pm);
169            }
170        }
171
172        mIcon = a.getDrawable(R.styleable.ActionBar_icon);
173        if (mIcon == null) {
174            if (context instanceof Activity) {
175                try {
176                    mIcon = pm.getActivityIcon(((Activity) context).getComponentName());
177                } catch (NameNotFoundException e) {
178                    Log.e(TAG, "Activity component name not found!", e);
179                }
180            }
181            if (mIcon == null) {
182                mIcon = appInfo.loadIcon(pm);
183            }
184        }
185
186        final LayoutInflater inflater = LayoutInflater.from(context);
187
188        final int homeResId = a.getResourceId(
189                com.android.internal.R.styleable.ActionBar_homeLayout,
190                com.android.internal.R.layout.action_bar_home);
191
192        mHomeLayout = inflater.inflate(homeResId, this, false);
193
194        mHomeAsUpView = mHomeLayout.findViewById(com.android.internal.R.id.up);
195        mIconView = (ImageView) mHomeLayout.findViewById(com.android.internal.R.id.home);
196
197        mTitleStyleRes = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
198        mSubtitleStyleRes = a.getResourceId(R.styleable.ActionBar_subtitleTextStyle, 0);
199        mProgressStyle = a.getResourceId(R.styleable.ActionBar_progressBarStyle, 0);
200        mIndeterminateProgressStyle = a.getResourceId(
201                R.styleable.ActionBar_indeterminateProgressStyle, 0);
202
203        mProgressBarPadding = a.getDimensionPixelOffset(R.styleable.ActionBar_progressBarPadding, 0);
204        mItemPadding = a.getDimensionPixelOffset(R.styleable.ActionBar_itemPadding, 0);
205
206        mIncludeTabs = a.getBoolean(R.styleable.ActionBar_embeddedTabs, true);
207
208        setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, DISPLAY_DEFAULT));
209
210        final int customNavId = a.getResourceId(R.styleable.ActionBar_customNavigationLayout, 0);
211        if (customNavId != 0) {
212            mCustomNavView = (View) inflater.inflate(customNavId, this, false);
213            mNavigationMode = ActionBar.NAVIGATION_MODE_STANDARD;
214            setDisplayOptions(mDisplayOptions | ActionBar.DISPLAY_SHOW_CUSTOM);
215        }
216
217        mContentHeight = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
218
219        a.recycle();
220
221        mLogoNavItem = new ActionMenuItem(context, 0, android.R.id.home, 0, 0, mTitle);
222        mHomeLayout.setOnClickListener(new OnClickListener() {
223            public void onClick(View v) {
224                Context context = getContext();
225                if (context instanceof Activity) {
226                    Activity activity = (Activity) context;
227                    activity.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mLogoNavItem);
228                }
229            }
230        });
231        mHomeLayout.setClickable(true);
232        mHomeLayout.setFocusable(true);
233    }
234
235    @Override
236    public boolean shouldDelayChildPressedState() {
237        return false;
238    }
239
240    public void initProgress() {
241        mProgressView = new ProgressBar(mContext, null, 0, mProgressStyle);
242        mProgressView.setId(R.id.progress_horizontal);
243        mProgressView.setMax(10000);
244        addView(mProgressView);
245    }
246
247    public void initIndeterminateProgress() {
248        mIndeterminateProgressView = new ProgressBar(mContext, null, 0,
249                mIndeterminateProgressStyle);
250        mIndeterminateProgressView.setId(R.id.progress_circular);
251        addView(mIndeterminateProgressView);
252    }
253
254    public void setSplitActionBar(boolean splitActionBar) {
255        if (mSplitActionBar != splitActionBar) {
256            if (mMenuView != null) {
257                if (splitActionBar) {
258                    removeView(mMenuView);
259                    if (mSplitView != null) {
260                        mSplitView.addView(mMenuView);
261                    }
262                } else {
263                    addView(mMenuView);
264                }
265            }
266            mSplitActionBar = splitActionBar;
267        }
268    }
269
270    public boolean isSplitActionBar() {
271        return mSplitActionBar;
272    }
273
274    public boolean hasEmbeddedTabs() {
275        return mIncludeTabs;
276    }
277
278    public void setExternalTabLayout(ViewGroup tabLayout) {
279        mTabLayout = tabLayout;
280    }
281
282    @Override
283    public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
284        // No starting an action mode for an action bar child! (Where would it go?)
285        return null;
286    }
287
288    public void setCallback(OnNavigationListener callback) {
289        mCallback = callback;
290    }
291
292    public void setMenu(Menu menu, MenuPresenter.Callback cb) {
293        if (menu == mOptionsMenu) return;
294
295        if (mOptionsMenu != null) {
296            mOptionsMenu.removeMenuPresenter(mActionMenuPresenter);
297        }
298
299        MenuBuilder builder = (MenuBuilder) menu;
300        mOptionsMenu = builder;
301        if (mMenuView != null) {
302            removeView(mMenuView);
303        }
304        if (mActionMenuPresenter == null) {
305            mActionMenuPresenter = new ActionMenuPresenter();
306            mActionMenuPresenter.setCallback(cb);
307            builder.addMenuPresenter(mActionMenuPresenter);
308        }
309        final ActionMenuView menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
310        final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
311                LayoutParams.MATCH_PARENT);
312        menuView.setLayoutParams(layoutParams);
313        if (!mSplitActionBar) {
314            addView(menuView);
315        } else {
316            // Allow full screen width in split mode.
317            mActionMenuPresenter.setWidthLimit(
318                    getContext().getResources().getDisplayMetrics().widthPixels);
319            // No limit to the item count; use whatever will fit.
320            mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
321            if (mSplitView != null) {
322                mSplitView.addView(menuView);
323            } // We'll add this later if we missed it this time.
324        }
325        mMenuView = menuView;
326    }
327
328    public void setSplitView(ViewGroup splitView) {
329        mSplitView = splitView;
330        splitView.setVisibility(VISIBLE);
331        if (mMenuView != null) {
332            splitView.addView(mMenuView);
333        }
334    }
335
336    public boolean showOverflowMenu() {
337        if (mActionMenuPresenter != null) {
338            return mActionMenuPresenter.showOverflowMenu();
339        }
340        return false;
341    }
342
343    public void openOverflowMenu() {
344        if (mActionMenuPresenter != null) {
345            showOverflowMenu();
346        }
347    }
348
349    public void postShowOverflowMenu() {
350        post(new Runnable() {
351            public void run() {
352                showOverflowMenu();
353            }
354        });
355    }
356
357    public boolean hideOverflowMenu() {
358        if (mActionMenuPresenter != null) {
359            return mActionMenuPresenter.hideOverflowMenu();
360        }
361        return false;
362    }
363
364    public boolean isOverflowMenuShowing() {
365        if (mActionMenuPresenter != null) {
366            return mActionMenuPresenter.isOverflowMenuShowing();
367        }
368        return false;
369    }
370
371    public boolean isOverflowReserved() {
372        return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved();
373    }
374
375    public void dismissPopupMenus() {
376        if (mActionMenuPresenter != null) {
377            mActionMenuPresenter.dismissPopupMenus();
378        }
379    }
380
381    public void setCustomNavigationView(View view) {
382        final boolean showCustom = (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0;
383        if (mCustomNavView != null && showCustom) {
384            removeView(mCustomNavView);
385        }
386        mCustomNavView = view;
387        if (mCustomNavView != null && showCustom) {
388            addView(mCustomNavView);
389        }
390    }
391
392    public CharSequence getTitle() {
393        return mTitle;
394    }
395
396    /**
397     * Set the action bar title. This will always replace or override window titles.
398     * @param title Title to set
399     *
400     * @see #setWindowTitle(CharSequence)
401     */
402    public void setTitle(CharSequence title) {
403        mUserTitle = true;
404        setTitleImpl(title);
405    }
406
407    /**
408     * Set the window title. A window title will always be replaced or overridden by a user title.
409     * @param title Title to set
410     *
411     * @see #setTitle(CharSequence)
412     */
413    public void setWindowTitle(CharSequence title) {
414        if (!mUserTitle) {
415            setTitleImpl(title);
416        }
417    }
418
419    private void setTitleImpl(CharSequence title) {
420        mTitle = title;
421        if (mTitleView != null) {
422            mTitleView.setText(title);
423            mTitleLayout.setVisibility(TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mSubtitle) ?
424                    GONE : VISIBLE);
425        }
426        if (mLogoNavItem != null) {
427            mLogoNavItem.setTitle(title);
428        }
429    }
430
431    public CharSequence getSubtitle() {
432        return mSubtitle;
433    }
434
435    public void setSubtitle(CharSequence subtitle) {
436        mSubtitle = subtitle;
437        if (mSubtitleView != null) {
438            mSubtitleView.setText(subtitle);
439            mSubtitleView.setVisibility(subtitle != null ? VISIBLE : GONE);
440            mTitleLayout.setVisibility(TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mSubtitle) ?
441                    GONE : VISIBLE);
442        }
443    }
444
445    public void setDisplayOptions(int options) {
446        final int flagsChanged = options ^ mDisplayOptions;
447        mDisplayOptions = options;
448
449        if ((flagsChanged & ActionBar.DISPLAY_DISABLE_HOME) != 0) {
450            final boolean disableHome = (options & ActionBar.DISPLAY_DISABLE_HOME) != 0;
451            mHomeLayout.setEnabled(!disableHome);
452        }
453
454        if ((flagsChanged & DISPLAY_RELAYOUT_MASK) != 0) {
455            final int vis = (options & ActionBar.DISPLAY_SHOW_HOME) != 0 ? VISIBLE : GONE;
456            mHomeLayout.setVisibility(vis);
457
458            if ((flagsChanged & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
459                mHomeAsUpView.setVisibility((options & ActionBar.DISPLAY_HOME_AS_UP) != 0
460                        ? VISIBLE : GONE);
461            }
462
463            if ((flagsChanged & ActionBar.DISPLAY_USE_LOGO) != 0) {
464                final boolean logoVis = mLogo != null && (options & ActionBar.DISPLAY_USE_LOGO) != 0;
465                mIconView.setImageDrawable(logoVis ? mLogo : mIcon);
466            }
467
468            if ((flagsChanged & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
469                if ((options & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
470                    initTitle();
471                } else {
472                    removeView(mTitleLayout);
473                }
474            }
475
476            if ((flagsChanged & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomNavView != null) {
477                if ((options & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
478                    addView(mCustomNavView);
479                } else {
480                    removeView(mCustomNavView);
481                }
482            }
483
484            requestLayout();
485        } else {
486            invalidate();
487        }
488    }
489
490    public void setIcon(Drawable icon) {
491        mIcon = icon;
492        if (icon != null &&
493                ((mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) == 0 || mLogo == null)) {
494            mIconView.setImageDrawable(icon);
495        }
496    }
497
498    public void setIcon(int resId) {
499        setIcon(mContext.getResources().getDrawableForDensity(resId, getPreferredIconDensity()));
500    }
501
502    public void setLogo(Drawable logo) {
503        mLogo = logo;
504        if (logo != null && (mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) {
505            mIconView.setImageDrawable(logo);
506        }
507    }
508
509    public void setLogo(int resId) {
510        mContext.getResources().getDrawable(resId);
511    }
512
513    /**
514     * @return Drawable density to load that will best fit the available height.
515     */
516    private int getPreferredIconDensity() {
517        final Resources res = mContext.getResources();
518        final int availableHeight = getLayoutParams().height -
519                mIconView.getPaddingTop() - mIconView.getPaddingBottom();
520        int iconSize = res.getDimensionPixelSize(android.R.dimen.app_icon_size);
521
522        if (iconSize * DisplayMetrics.DENSITY_LOW >= availableHeight) {
523            return DisplayMetrics.DENSITY_LOW;
524        } else if (iconSize * DisplayMetrics.DENSITY_MEDIUM >= availableHeight) {
525            return DisplayMetrics.DENSITY_MEDIUM;
526        } else if (iconSize * DisplayMetrics.DENSITY_HIGH >= availableHeight) {
527            return DisplayMetrics.DENSITY_HIGH;
528        }
529        return DisplayMetrics.DENSITY_XHIGH;
530    }
531
532    public void setNavigationMode(int mode) {
533        final int oldMode = mNavigationMode;
534        if (mode != oldMode) {
535            switch (oldMode) {
536            case ActionBar.NAVIGATION_MODE_LIST:
537                if (mSpinner != null) {
538                    removeView(mListNavLayout);
539                }
540                break;
541            case ActionBar.NAVIGATION_MODE_TABS:
542                if (mTabScrollView != null) {
543                    removeView(mTabScrollView);
544                }
545            }
546
547            switch (mode) {
548            case ActionBar.NAVIGATION_MODE_LIST:
549                if (mSpinner == null) {
550                    mSpinner = new Spinner(mContext, null,
551                            com.android.internal.R.attr.actionDropDownStyle);
552                    mListNavLayout = new LinearLayout(mContext, null,
553                            com.android.internal.R.attr.actionBarTabBarStyle);
554                    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
555                            LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
556                    params.gravity = Gravity.CENTER;
557                    mListNavLayout.addView(mSpinner, params);
558                }
559                if (mSpinner.getAdapter() != mSpinnerAdapter) {
560                    mSpinner.setAdapter(mSpinnerAdapter);
561                }
562                mSpinner.setOnItemSelectedListener(mNavItemSelectedListener);
563                addView(mListNavLayout);
564                break;
565            case ActionBar.NAVIGATION_MODE_TABS:
566                ensureTabsExist();
567                if (mTabScrollView != null) {
568                    addView(mTabScrollView);
569                }
570                break;
571            }
572            mNavigationMode = mode;
573            requestLayout();
574        }
575    }
576
577    private void ensureTabsExist() {
578        if (!mIncludeTabs) return;
579
580        if (mTabScrollView == null) {
581            mTabScrollView = new HorizontalScrollView(getContext());
582            mTabScrollView.setHorizontalFadingEdgeEnabled(true);
583            mTabLayout = createTabContainer();
584            mTabScrollView.addView(mTabLayout);
585        }
586    }
587
588    public ViewGroup createTabContainer() {
589        ViewGroup result = new LinearLayout(getContext(), null,
590                com.android.internal.R.attr.actionBarTabBarStyle);
591        result.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
592                mContentHeight));
593        return result;
594    }
595
596    public void setDropdownAdapter(SpinnerAdapter adapter) {
597        mSpinnerAdapter = adapter;
598        if (mSpinner != null) {
599            mSpinner.setAdapter(adapter);
600        }
601    }
602
603    public SpinnerAdapter getDropdownAdapter() {
604        return mSpinnerAdapter;
605    }
606
607    public void setDropdownSelectedPosition(int position) {
608        mSpinner.setSelection(position);
609    }
610
611    public int getDropdownSelectedPosition() {
612        return mSpinner.getSelectedItemPosition();
613    }
614
615    public View getCustomNavigationView() {
616        return mCustomNavView;
617    }
618
619    public int getNavigationMode() {
620        return mNavigationMode;
621    }
622
623    public int getDisplayOptions() {
624        return mDisplayOptions;
625    }
626
627    private TabView createTabView(ActionBar.Tab tab) {
628        final TabView tabView = new TabView(getContext(), tab);
629        tabView.setFocusable(true);
630
631        if (mTabClickListener == null) {
632            mTabClickListener = new TabClickListener();
633        }
634        tabView.setOnClickListener(mTabClickListener);
635        return tabView;
636    }
637
638    public void addTab(ActionBar.Tab tab, boolean setSelected) {
639        ensureTabsExist();
640        View tabView = createTabView(tab);
641        mTabLayout.addView(tabView);
642        if (setSelected) {
643            tabView.setSelected(true);
644        }
645    }
646
647    public void addTab(ActionBar.Tab tab, int position, boolean setSelected) {
648        ensureTabsExist();
649        final TabView tabView = createTabView(tab);
650        mTabLayout.addView(tabView, position);
651        if (setSelected) {
652            tabView.setSelected(true);
653        }
654    }
655
656    public void removeTabAt(int position) {
657        if (mTabLayout != null) {
658            mTabLayout.removeViewAt(position);
659        }
660    }
661
662    public void removeAllTabs() {
663        if (mTabLayout != null) {
664            mTabLayout.removeAllViews();
665        }
666    }
667
668    @Override
669    protected LayoutParams generateDefaultLayoutParams() {
670        // Used by custom nav views if they don't supply layout params. Everything else
671        // added to an ActionBarView should have them already.
672        return new ActionBar.LayoutParams(DEFAULT_CUSTOM_GRAVITY);
673    }
674
675    @Override
676    protected void onFinishInflate() {
677        super.onFinishInflate();
678
679        addView(mHomeLayout);
680
681        if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
682            final ViewParent parent = mCustomNavView.getParent();
683            if (parent != this) {
684                if (parent instanceof ViewGroup) {
685                    ((ViewGroup) parent).removeView(mCustomNavView);
686                }
687                addView(mCustomNavView);
688            }
689        }
690    }
691
692    private void initTitle() {
693        LayoutInflater inflater = LayoutInflater.from(getContext());
694        mTitleLayout = (LinearLayout) inflater.inflate(R.layout.action_bar_title_item, null);
695        mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
696        mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
697
698        if (mTitleStyleRes != 0) {
699            mTitleView.setTextAppearance(mContext, mTitleStyleRes);
700        }
701        if (mTitle != null) {
702            mTitleView.setText(mTitle);
703        }
704
705        if (mSubtitleStyleRes != 0) {
706            mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes);
707        }
708        if (mSubtitle != null) {
709            mSubtitleView.setText(mSubtitle);
710            mSubtitleView.setVisibility(VISIBLE);
711        }
712
713        addView(mTitleLayout);
714    }
715
716    public void setTabSelected(int position) {
717        ensureTabsExist();
718        final int tabCount = mTabLayout.getChildCount();
719        for (int i = 0; i < tabCount; i++) {
720            final View child = mTabLayout.getChildAt(i);
721            child.setSelected(i == position);
722        }
723    }
724
725    public void setContextView(ActionBarContextView view) {
726        mContextView = view;
727    }
728
729    @Override
730    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
731        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
732        if (widthMode != MeasureSpec.EXACTLY) {
733            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
734                    "with android:layout_width=\"match_parent\" (or fill_parent)");
735        }
736
737        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
738        if (heightMode != MeasureSpec.AT_MOST) {
739            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
740                    "with android:layout_height=\"wrap_content\"");
741        }
742
743        int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
744
745        int maxHeight = mContentHeight > 0 ?
746                mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
747
748        final int verticalPadding = getPaddingTop() + getPaddingBottom();
749        final int paddingLeft = getPaddingLeft();
750        final int paddingRight = getPaddingRight();
751        final int height = maxHeight - verticalPadding;
752        final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
753
754        int availableWidth = contentWidth - paddingLeft - paddingRight;
755        int leftOfCenter = availableWidth / 2;
756        int rightOfCenter = leftOfCenter;
757
758        if (mHomeLayout.getVisibility() != GONE) {
759            mHomeLayout.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
760                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
761            final int homeWidth = mHomeLayout.getMeasuredWidth();
762            availableWidth -= homeWidth;
763            leftOfCenter -= homeWidth;
764        }
765
766        if (mMenuView != null && mMenuView.getParent() == this) {
767            availableWidth = measureChildView(mMenuView, availableWidth,
768                    childSpecHeight, 0);
769            rightOfCenter -= mMenuView.getMeasuredWidth();
770        }
771
772        boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
773                (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
774        if (showTitle) {
775            availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0);
776            leftOfCenter -= mTitleLayout.getMeasuredWidth();
777        }
778
779        switch (mNavigationMode) {
780        case ActionBar.NAVIGATION_MODE_LIST:
781            if (mListNavLayout != null) {
782                final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
783                availableWidth -= itemPaddingSize;
784                leftOfCenter -= itemPaddingSize;
785                mListNavLayout.measure(
786                        MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
787                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
788                final int listNavWidth = mListNavLayout.getMeasuredWidth();
789                availableWidth -= listNavWidth;
790                leftOfCenter -= listNavWidth;
791            }
792            break;
793        case ActionBar.NAVIGATION_MODE_TABS:
794            if (mTabScrollView != null) {
795                final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
796                availableWidth -= itemPaddingSize;
797                leftOfCenter -= itemPaddingSize;
798                mTabScrollView.measure(
799                        MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
800                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
801                final int tabWidth = mTabScrollView.getMeasuredWidth();
802                availableWidth -= tabWidth;
803                leftOfCenter -= tabWidth;
804            }
805            break;
806        }
807
808        if (mIndeterminateProgressView != null &&
809                mIndeterminateProgressView.getVisibility() != GONE) {
810            availableWidth = measureChildView(mIndeterminateProgressView, availableWidth,
811                    childSpecHeight, 0);
812            rightOfCenter -= mIndeterminateProgressView.getMeasuredWidth();
813        }
814
815        if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomNavView != null) {
816            final LayoutParams lp = generateLayoutParams(mCustomNavView.getLayoutParams());
817            final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ?
818                    (ActionBar.LayoutParams) lp : null;
819
820            int horizontalMargin = 0;
821            int verticalMargin = 0;
822            if (ablp != null) {
823                horizontalMargin = ablp.leftMargin + ablp.rightMargin;
824                verticalMargin = ablp.topMargin + ablp.bottomMargin;
825            }
826
827            // If the action bar is wrapping to its content height, don't allow a custom
828            // view to MATCH_PARENT.
829            int customNavHeightMode;
830            if (mContentHeight <= 0) {
831                customNavHeightMode = MeasureSpec.AT_MOST;
832            } else {
833                customNavHeightMode = lp.height != LayoutParams.WRAP_CONTENT ?
834                        MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
835            }
836            final int customNavHeight = Math.max(0,
837                    (lp.height >= 0 ? Math.min(lp.height, height) : height) - verticalMargin);
838
839            final int customNavWidthMode = lp.width != LayoutParams.WRAP_CONTENT ?
840                    MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
841            int customNavWidth = Math.max(0,
842                    (lp.width >= 0 ? Math.min(lp.width, availableWidth) : availableWidth)
843                    - horizontalMargin);
844            final int hgrav = (ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY) &
845                    Gravity.HORIZONTAL_GRAVITY_MASK;
846
847            // Centering a custom view is treated specially; we try to center within the whole
848            // action bar rather than in the available space.
849            if (hgrav == Gravity.CENTER_HORIZONTAL && lp.width == LayoutParams.MATCH_PARENT) {
850                customNavWidth = Math.min(leftOfCenter, rightOfCenter) * 2;
851            }
852
853            mCustomNavView.measure(
854                    MeasureSpec.makeMeasureSpec(customNavWidth, customNavWidthMode),
855                    MeasureSpec.makeMeasureSpec(customNavHeight, customNavHeightMode));
856        }
857
858        if (mContentHeight <= 0) {
859            int measuredHeight = 0;
860            final int count = getChildCount();
861            for (int i = 0; i < count; i++) {
862                View v = getChildAt(i);
863                int paddedViewHeight = v.getMeasuredHeight() + verticalPadding;
864                if (paddedViewHeight > measuredHeight) {
865                    measuredHeight = paddedViewHeight;
866                }
867            }
868            setMeasuredDimension(contentWidth, measuredHeight);
869        } else {
870            setMeasuredDimension(contentWidth, maxHeight);
871        }
872
873        if (mContextView != null) {
874            mContextView.setHeight(getMeasuredHeight());
875        }
876
877        if (mProgressView != null && mProgressView.getVisibility() != GONE) {
878            mProgressView.measure(MeasureSpec.makeMeasureSpec(
879                    contentWidth - mProgressBarPadding * 2, MeasureSpec.EXACTLY),
880                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST));
881        }
882    }
883
884    private int measureChildView(View child, int availableWidth, int childSpecHeight, int spacing) {
885        child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
886                childSpecHeight);
887
888        availableWidth -= child.getMeasuredWidth();
889        availableWidth -= spacing;
890
891        return availableWidth;
892    }
893
894    @Override
895    protected void onLayout(boolean changed, int l, int t, int r, int b) {
896        int x = getPaddingLeft();
897        final int y = getPaddingTop();
898        final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
899
900        if (mHomeLayout.getVisibility() != GONE) {
901            x += positionChild(mHomeLayout, x, y, contentHeight);
902        }
903
904        final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
905                (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
906        if (showTitle) {
907            x += positionChild(mTitleLayout, x, y, contentHeight);
908        }
909
910        switch (mNavigationMode) {
911        case ActionBar.NAVIGATION_MODE_STANDARD:
912            break;
913        case ActionBar.NAVIGATION_MODE_LIST:
914            if (mListNavLayout != null) {
915                if (showTitle) x += mItemPadding;
916                x += positionChild(mListNavLayout, x, y, contentHeight) + mItemPadding;
917            }
918            break;
919        case ActionBar.NAVIGATION_MODE_TABS:
920            if (mTabScrollView != null) {
921                if (showTitle) x += mItemPadding;
922                x += positionChild(mTabScrollView, x, y, contentHeight) + mItemPadding;
923            }
924            break;
925        }
926
927        int menuLeft = r - l - getPaddingRight();
928        if (mMenuView != null && mMenuView.getParent() == this) {
929            positionChildInverse(mMenuView, menuLeft, y, contentHeight);
930            menuLeft -= mMenuView.getMeasuredWidth();
931        }
932
933        if (mIndeterminateProgressView != null &&
934                mIndeterminateProgressView.getVisibility() != GONE) {
935            positionChildInverse(mIndeterminateProgressView, menuLeft, y, contentHeight);
936            menuLeft -= mIndeterminateProgressView.getMeasuredWidth();
937        }
938
939        if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
940            LayoutParams lp = mCustomNavView.getLayoutParams();
941            final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ?
942                    (ActionBar.LayoutParams) lp : null;
943
944            final int gravity = ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY;
945            final int navWidth = mCustomNavView.getMeasuredWidth();
946
947            int topMargin = 0;
948            int bottomMargin = 0;
949            if (ablp != null) {
950                x += ablp.leftMargin;
951                menuLeft -= ablp.rightMargin;
952                topMargin = ablp.topMargin;
953                bottomMargin = ablp.bottomMargin;
954            }
955
956            int hgravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
957            // See if we actually have room to truly center; if not push against left or right.
958            if (hgravity == Gravity.CENTER_HORIZONTAL) {
959                final int centeredLeft = ((mRight - mLeft) - navWidth) / 2;
960                if (centeredLeft < x) {
961                    hgravity = Gravity.LEFT;
962                } else if (centeredLeft + navWidth > menuLeft) {
963                    hgravity = Gravity.RIGHT;
964                }
965            }
966
967            int xpos = 0;
968            switch (hgravity) {
969                case Gravity.CENTER_HORIZONTAL:
970                    xpos = ((mRight - mLeft) - navWidth) / 2;
971                    break;
972                case Gravity.LEFT:
973                    xpos = x;
974                    break;
975                case Gravity.RIGHT:
976                    xpos = menuLeft - navWidth;
977                    break;
978            }
979
980            int ypos = 0;
981            switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
982                case Gravity.CENTER_VERTICAL:
983                    final int paddedTop = mTop + getPaddingTop();
984                    final int paddedBottom = mBottom - getPaddingBottom();
985                    ypos = ((paddedBottom - paddedTop) - mCustomNavView.getMeasuredHeight()) / 2;
986                    break;
987                case Gravity.TOP:
988                    ypos = getPaddingTop() + topMargin;
989                    break;
990                case Gravity.BOTTOM:
991                    ypos = getHeight() - getPaddingBottom() - mCustomNavView.getMeasuredHeight()
992                            - bottomMargin;
993                    break;
994            }
995            x += positionChild(mCustomNavView, xpos, ypos, contentHeight);
996        }
997
998        if (mProgressView != null) {
999            mProgressView.bringToFront();
1000            final int halfProgressHeight = mProgressView.getMeasuredHeight() / 2;
1001            mProgressView.layout(mProgressBarPadding, -halfProgressHeight,
1002                    mProgressBarPadding + mProgressView.getMeasuredWidth(), halfProgressHeight);
1003        }
1004    }
1005
1006    private int positionChild(View child, int x, int y, int contentHeight) {
1007        int childWidth = child.getMeasuredWidth();
1008        int childHeight = child.getMeasuredHeight();
1009        int childTop = y + (contentHeight - childHeight) / 2;
1010
1011        child.layout(x, childTop, x + childWidth, childTop + childHeight);
1012
1013        return childWidth;
1014    }
1015
1016    private int positionChildInverse(View child, int x, int y, int contentHeight) {
1017        int childWidth = child.getMeasuredWidth();
1018        int childHeight = child.getMeasuredHeight();
1019        int childTop = y + (contentHeight - childHeight) / 2;
1020
1021        child.layout(x - childWidth, childTop, x, childTop + childHeight);
1022
1023        return childWidth;
1024    }
1025
1026    private static class TabView extends LinearLayout {
1027        private ActionBar.Tab mTab;
1028
1029        public TabView(Context context, ActionBar.Tab tab) {
1030            super(context, null, com.android.internal.R.attr.actionBarTabStyle);
1031            mTab = tab;
1032
1033            final View custom = tab.getCustomView();
1034            if (custom != null) {
1035                addView(custom);
1036            } else {
1037                // TODO Style tabs based on the theme
1038
1039                final Drawable icon = tab.getIcon();
1040                final CharSequence text = tab.getText();
1041
1042                if (icon != null) {
1043                    ImageView iconView = new ImageView(context);
1044                    iconView.setImageDrawable(icon);
1045                    LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
1046                            LayoutParams.WRAP_CONTENT);
1047                    lp.gravity = Gravity.CENTER_VERTICAL;
1048                    iconView.setLayoutParams(lp);
1049                    addView(iconView);
1050                }
1051
1052                if (text != null) {
1053                    TextView textView = new TextView(context, null,
1054                            com.android.internal.R.attr.actionBarTabTextStyle);
1055                    textView.setText(text);
1056                    textView.setSingleLine();
1057                    textView.setEllipsize(TruncateAt.END);
1058                    LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
1059                            LayoutParams.WRAP_CONTENT);
1060                    lp.gravity = Gravity.CENTER_VERTICAL;
1061                    textView.setLayoutParams(lp);
1062                    addView(textView);
1063                }
1064            }
1065
1066            setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
1067                    LayoutParams.MATCH_PARENT, 1));
1068        }
1069
1070        public ActionBar.Tab getTab() {
1071            return mTab;
1072        }
1073    }
1074
1075    private class TabClickListener implements OnClickListener {
1076        public void onClick(View view) {
1077            TabView tabView = (TabView) view;
1078            tabView.getTab().select();
1079            final int tabCount = mTabLayout.getChildCount();
1080            for (int i = 0; i < tabCount; i++) {
1081                final View child = mTabLayout.getChildAt(i);
1082                child.setSelected(child == view);
1083            }
1084        }
1085    }
1086
1087    private static class HomeView extends FrameLayout {
1088        private View mUpView;
1089        private View mIconView;
1090
1091        public HomeView(Context context) {
1092            this(context, null);
1093        }
1094
1095        public HomeView(Context context, AttributeSet attrs) {
1096            super(context, attrs);
1097        }
1098
1099        @Override
1100        protected void onFinishInflate() {
1101            mUpView = findViewById(com.android.internal.R.id.up);
1102            mIconView = (ImageView) findViewById(com.android.internal.R.id.home);
1103        }
1104
1105        @Override
1106        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1107            measureChildWithMargins(mUpView, widthMeasureSpec, 0, heightMeasureSpec, 0);
1108            final LayoutParams upLp = (LayoutParams) mUpView.getLayoutParams();
1109            int width = upLp.leftMargin + mUpView.getMeasuredWidth() + upLp.rightMargin;
1110            int height = upLp.topMargin + mUpView.getMeasuredHeight() + upLp.bottomMargin;
1111            measureChildWithMargins(mIconView, widthMeasureSpec, width, heightMeasureSpec, 0);
1112            final LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams();
1113            width += iconLp.leftMargin + mIconView.getMeasuredWidth() + iconLp.rightMargin;
1114            height = Math.max(height,
1115                    iconLp.topMargin + mIconView.getMeasuredHeight() + iconLp.bottomMargin);
1116            setMeasuredDimension(width, height);
1117        }
1118
1119        @Override
1120        protected void onLayout(boolean changed, int l, int t, int r, int b) {
1121            final int vCenter = (b - t) / 2;
1122            int width = r - l;
1123            if (mUpView.getVisibility() != GONE) {
1124                final LayoutParams upLp = (LayoutParams) mUpView.getLayoutParams();
1125                final int upHeight = mUpView.getMeasuredHeight();
1126                final int upWidth = mUpView.getMeasuredWidth();
1127                final int upTop = t + vCenter - upHeight / 2;
1128                mUpView.layout(l, upTop, l + upWidth, upTop + upHeight);
1129                final int upOffset = upLp.leftMargin + upWidth + upLp.rightMargin;
1130                width -= upOffset;
1131                l += upOffset;
1132            }
1133            final LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams();
1134            final int iconHeight = mIconView.getMeasuredHeight();
1135            final int iconWidth = mIconView.getMeasuredWidth();
1136            final int hCenter = (r - l) / 2;
1137            final int iconLeft = l + iconLp.leftMargin + hCenter - iconWidth / 2;
1138            final int iconTop = t + iconLp.topMargin + vCenter - iconHeight / 2;
1139            mIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight);
1140        }
1141    }
1142}
1143