ActionBarView.java revision 661c908e4e26c99adc2cab7558a02129eaee059d
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.ActionMenuView;
22import com.android.internal.view.menu.MenuBuilder;
23
24import android.app.ActionBar;
25import android.app.Activity;
26import android.app.ActionBar.NavigationCallback;
27import android.content.Context;
28import android.content.pm.ApplicationInfo;
29import android.content.pm.PackageManager;
30import android.content.res.TypedArray;
31import android.graphics.PorterDuff;
32import android.graphics.PorterDuffColorFilter;
33import android.graphics.drawable.Drawable;
34import android.text.TextUtils.TruncateAt;
35import android.util.AttributeSet;
36import android.util.DisplayMetrics;
37import android.view.Gravity;
38import android.view.LayoutInflater;
39import android.view.Menu;
40import android.view.View;
41import android.view.ViewGroup;
42import android.widget.AdapterView;
43import android.widget.ImageView;
44import android.widget.LinearLayout;
45import android.widget.Spinner;
46import android.widget.SpinnerAdapter;
47import android.widget.TextView;
48
49/**
50 * @hide
51 */
52public class ActionBarView extends ViewGroup {
53    private static final String TAG = "ActionBarView";
54
55    // TODO: This must be defined in the default theme
56    private static final int CONTENT_HEIGHT_DIP = 50;
57    private static final int CONTENT_PADDING_DIP = 3;
58    private static final int CONTENT_SPACING_DIP = 6;
59    private static final int CONTENT_ACTION_SPACING_DIP = 12;
60
61    /**
62     * Display options applied by default
63     */
64    public static final int DISPLAY_DEFAULT = 0;
65
66    /**
67     * Display options that require re-layout as opposed to a simple invalidate
68     */
69    private static final int DISPLAY_RELAYOUT_MASK =
70            ActionBar.DISPLAY_HIDE_HOME |
71            ActionBar.DISPLAY_USE_LOGO;
72
73    private final int mContentHeight;
74
75    private int mNavigationMode;
76    private int mDisplayOptions;
77    private int mSpacing;
78    private int mActionSpacing;
79    private CharSequence mTitle;
80    private CharSequence mSubtitle;
81    private Drawable mIcon;
82    private Drawable mLogo;
83
84    private ImageView mIconView;
85    private ImageView mLogoView;
86    private LinearLayout mTitleLayout;
87    private TextView mTitleView;
88    private TextView mSubtitleView;
89    private Spinner mSpinner;
90    private LinearLayout mTabLayout;
91    private View mCustomNavView;
92
93    private boolean mShowMenu;
94
95    private MenuBuilder mOptionsMenu;
96    private ActionMenuView mMenuView;
97
98    private ActionMenuItem mLogoNavItem;
99
100    private NavigationCallback mCallback;
101
102    private final AdapterView.OnItemSelectedListener mNavItemSelectedListener =
103            new AdapterView.OnItemSelectedListener() {
104        public void onItemSelected(AdapterView parent, View view, int position, long id) {
105            if (mCallback != null) {
106                mCallback.onNavigationItemSelected(position, id);
107            }
108        }
109        public void onNothingSelected(AdapterView parent) {
110            // Do nothing
111        }
112    };
113
114    private OnClickListener mHomeClickListener = null;
115
116    public ActionBarView(Context context, AttributeSet attrs) {
117        super(context, attrs);
118
119        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
120        mContentHeight = (int) (CONTENT_HEIGHT_DIP * metrics.density + 0.5f);
121
122        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar);
123
124        final int colorFilter = a.getColor(R.styleable.ActionBar_colorFilter, 0);
125
126        if (colorFilter != 0) {
127            final Drawable d = getBackground();
128            d.setDither(true);
129            d.setColorFilter(new PorterDuffColorFilter(colorFilter, PorterDuff.Mode.OVERLAY));
130        }
131
132        ApplicationInfo info = context.getApplicationInfo();
133        PackageManager pm = context.getPackageManager();
134        mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode,
135                ActionBar.NAVIGATION_MODE_STANDARD);
136        mTitle = a.getText(R.styleable.ActionBar_title);
137        mSubtitle = a.getText(R.styleable.ActionBar_subtitle);
138        mDisplayOptions = a.getInt(R.styleable.ActionBar_displayOptions, DISPLAY_DEFAULT);
139
140        mLogo = a.getDrawable(R.styleable.ActionBar_logo);
141        if (mLogo == null) {
142            mLogo = info.loadLogo(pm);
143        }
144        mIcon = a.getDrawable(R.styleable.ActionBar_icon);
145        if (mIcon == null) {
146            mIcon = info.loadIcon(pm);
147        }
148
149        Drawable background = a.getDrawable(R.styleable.ActionBar_background);
150        if (background != null) {
151            setBackgroundDrawable(background);
152        }
153
154        final int customNavId = a.getResourceId(R.styleable.ActionBar_customNavigationLayout, 0);
155        if (customNavId != 0) {
156            LayoutInflater inflater = LayoutInflater.from(context);
157            mCustomNavView = (View) inflater.inflate(customNavId, null);
158            mNavigationMode = ActionBar.NAVIGATION_MODE_CUSTOM;
159        }
160
161        a.recycle();
162
163        // TODO: Set this in the theme
164        int padding = (int) (CONTENT_PADDING_DIP * metrics.density + 0.5f);
165        setPadding(padding, padding, padding, padding);
166
167        mSpacing = (int) (CONTENT_SPACING_DIP * metrics.density + 0.5f);
168        mActionSpacing = (int) (CONTENT_ACTION_SPACING_DIP * metrics.density + 0.5f);
169
170        if (mLogo != null || mIcon != null || mTitle != null) {
171            mLogoNavItem = new ActionMenuItem(context, 0, android.R.id.home, 0, 0, mTitle);
172            mHomeClickListener = new OnClickListener() {
173                public void onClick(View v) {
174                    Context context = getContext();
175                    if (context instanceof Activity) {
176                        Activity activity = (Activity) context;
177                        activity.onOptionsItemSelected(mLogoNavItem);
178                    }
179                }
180            };
181        }
182    }
183
184    public void setCallback(NavigationCallback callback) {
185        mCallback = callback;
186    }
187
188    public void setMenu(Menu menu) {
189        MenuBuilder builder = (MenuBuilder) menu;
190        mOptionsMenu = builder;
191        if (mMenuView != null) {
192            removeView(mMenuView);
193        }
194        final ActionMenuView menuView = (ActionMenuView) builder.getMenuView(
195                MenuBuilder.TYPE_ACTION_BUTTON, null);
196        mActionSpacing = menuView.getItemMargin();
197        final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
198                LayoutParams.MATCH_PARENT);
199        menuView.setLayoutParams(layoutParams);
200        addView(menuView);
201        mMenuView = menuView;
202    }
203
204    public void setCustomNavigationView(View view) {
205        mCustomNavView = view;
206        if (view != null) {
207            setNavigationMode(ActionBar.NAVIGATION_MODE_CUSTOM);
208        }
209    }
210
211    public CharSequence getTitle() {
212        return mTitle;
213    }
214
215    public void setTitle(CharSequence title) {
216        mTitle = title;
217        if (mTitleView != null) {
218            mTitleView.setText(title);
219        }
220        if (mLogoNavItem != null) {
221            mLogoNavItem.setTitle(title);
222        }
223    }
224
225    public CharSequence getSubtitle() {
226        return mSubtitle;
227    }
228
229    public void setSubtitle(CharSequence subtitle) {
230        mSubtitle = subtitle;
231        if (mSubtitleView != null) {
232            mSubtitleView.setText(subtitle);
233        }
234    }
235
236    public void setDisplayOptions(int options) {
237        final int flagsChanged = options ^ mDisplayOptions;
238        mDisplayOptions = options;
239        if ((flagsChanged & DISPLAY_RELAYOUT_MASK) != 0) {
240            final int vis = (options & ActionBar.DISPLAY_HIDE_HOME) != 0 ? GONE : VISIBLE;
241            if (mLogoView != null) {
242                mLogoView.setVisibility(vis);
243            }
244            if (mIconView != null) {
245                mIconView.setVisibility(vis);
246            }
247
248            requestLayout();
249        } else {
250            invalidate();
251        }
252    }
253
254    public void setNavigationMode(int mode) {
255        final int oldMode = mNavigationMode;
256        if (mode != oldMode) {
257            switch (oldMode) {
258            case ActionBar.NAVIGATION_MODE_STANDARD:
259                if (mTitleLayout != null) {
260                    removeView(mTitleLayout);
261                    mTitleLayout = null;
262                    mTitleView = null;
263                    mSubtitleView = null;
264                }
265                break;
266            case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST:
267                if (mSpinner != null) {
268                    removeView(mSpinner);
269                    mSpinner = null;
270                }
271                break;
272            case ActionBar.NAVIGATION_MODE_CUSTOM:
273                if (mCustomNavView != null) {
274                    removeView(mCustomNavView);
275                    mCustomNavView = null;
276                }
277                break;
278            case ActionBar.NAVIGATION_MODE_TABS:
279                if (mTabLayout != null) {
280                    removeView(mTabLayout);
281                    mTabLayout = null;
282                }
283            }
284
285            switch (mode) {
286            case ActionBar.NAVIGATION_MODE_STANDARD:
287                initTitle();
288                break;
289            case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST:
290                mSpinner = new Spinner(mContext, null,
291                        com.android.internal.R.attr.dropDownSpinnerStyle);
292                mSpinner.setOnItemSelectedListener(mNavItemSelectedListener);
293                addView(mSpinner);
294                break;
295            case ActionBar.NAVIGATION_MODE_CUSTOM:
296                addView(mCustomNavView);
297                break;
298            case ActionBar.NAVIGATION_MODE_TABS:
299                mTabLayout = new LinearLayout(getContext());
300                addView(mTabLayout);
301                break;
302            }
303            mNavigationMode = mode;
304            requestLayout();
305        }
306    }
307
308    public void setDropdownAdapter(SpinnerAdapter adapter) {
309        mSpinner.setAdapter(adapter);
310    }
311
312    public View getCustomNavigationView() {
313        return mCustomNavView;
314    }
315
316    public int getNavigationMode() {
317        return mNavigationMode;
318    }
319
320    public int getDisplayOptions() {
321        return mDisplayOptions;
322    }
323
324    private TabView createTabView(ActionBar.Tab tab) {
325        final TabView tabView = new TabView(getContext(), tab);
326        tabView.setFocusable(true);
327        tabView.setOnClickListener(new TabClickListener());
328        return tabView;
329    }
330
331    public void addTab(ActionBar.Tab tab) {
332        final boolean isFirst = mTabLayout.getChildCount() == 0;
333        final TabView tabView = createTabView(tab);
334        mTabLayout.addView(tabView);
335        if (isFirst) {
336            tabView.setSelected(true);
337        }
338    }
339
340    public void insertTab(ActionBar.Tab tab, int position) {
341        final boolean isFirst = mTabLayout.getChildCount() == 0;
342        final TabView tabView = createTabView(tab);
343        mTabLayout.addView(tabView, position);
344        if (isFirst) {
345            tabView.setSelected(true);
346        }
347    }
348
349    public void removeTabAt(int position) {
350        mTabLayout.removeViewAt(position);
351    }
352
353    @Override
354    protected LayoutParams generateDefaultLayoutParams() {
355        // Used by custom nav views if they don't supply layout params. Everything else
356        // added to an ActionBarView should have them already.
357        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
358    }
359
360    @Override
361    protected void onFinishInflate() {
362        super.onFinishInflate();
363
364        if ((mDisplayOptions & ActionBar.DISPLAY_HIDE_HOME) == 0) {
365            if (mLogo != null && (mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) {
366                mLogoView = new ImageView(getContext());
367                mLogoView.setAdjustViewBounds(true);
368                mLogoView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
369                        LayoutParams.MATCH_PARENT));
370                mLogoView.setImageDrawable(mLogo);
371                mLogoView.setClickable(true);
372                mLogoView.setFocusable(true);
373                mLogoView.setOnClickListener(mHomeClickListener);
374                addView(mLogoView);
375            } else if (mIcon != null) {
376                mIconView = new ImageView(getContext());
377                mIconView.setAdjustViewBounds(true);
378                mIconView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
379                        LayoutParams.MATCH_PARENT));
380                mIconView.setImageDrawable(mIcon);
381                mIconView.setClickable(true);
382                mIconView.setFocusable(true);
383                mIconView.setOnClickListener(mHomeClickListener);
384                addView(mIconView);
385            }
386        }
387
388        switch (mNavigationMode) {
389        case ActionBar.NAVIGATION_MODE_STANDARD:
390            if (mLogoView == null) {
391                initTitle();
392            }
393            break;
394
395        case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST:
396            throw new UnsupportedOperationException(
397                    "Inflating dropdown list navigation isn't supported yet!");
398
399        case ActionBar.NAVIGATION_MODE_TABS:
400            throw new UnsupportedOperationException(
401                    "Inflating tab navigation isn't supported yet!");
402
403        case ActionBar.NAVIGATION_MODE_CUSTOM:
404            if (mCustomNavView != null) {
405                addView(mCustomNavView);
406            }
407            break;
408        }
409    }
410
411    private void initTitle() {
412        LayoutInflater inflater = LayoutInflater.from(getContext());
413        mTitleLayout = (LinearLayout) inflater.inflate(R.layout.action_bar_title_item, null);
414        mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
415        mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
416        if (mTitle != null) {
417            mTitleView.setText(mTitle);
418        }
419        if (mSubtitle != null) {
420            mSubtitleView.setText(mSubtitle);
421            mSubtitleView.setVisibility(VISIBLE);
422        }
423        addView(mTitleLayout);
424    }
425
426    public void setTabSelected(int position) {
427        final int tabCount = mTabLayout.getChildCount();
428        for (int i = 0; i < tabCount; i++) {
429            final View child = mTabLayout.getChildAt(i);
430            child.setSelected(i == position);
431        }
432    }
433
434    @Override
435    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
436        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
437        if (widthMode != MeasureSpec.EXACTLY) {
438            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
439                    "with android:layout_width=\"match_parent\" (or fill_parent)");
440        }
441
442        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
443        if (heightMode != MeasureSpec.AT_MOST) {
444            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
445                    "with android:layout_height=\"wrap_content\"");
446        }
447
448        int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
449
450        int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight();
451        final int height = mContentHeight - getPaddingTop() - getPaddingBottom();
452        final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
453
454        if (mLogoView != null && mLogoView.getVisibility() != GONE) {
455            availableWidth = measureChildView(mLogoView, availableWidth, childSpecHeight, mSpacing);
456        }
457        if (mIconView != null && mIconView.getVisibility() != GONE) {
458            availableWidth = measureChildView(mIconView, availableWidth, childSpecHeight, mSpacing);
459        }
460
461        if (mMenuView != null) {
462            availableWidth = measureChildView(mMenuView, availableWidth,
463                    childSpecHeight, 0);
464        }
465
466        switch (mNavigationMode) {
467        case ActionBar.NAVIGATION_MODE_STANDARD:
468            if (mTitleLayout != null) {
469                measureChildView(mTitleLayout, availableWidth, childSpecHeight, mSpacing);
470            }
471            break;
472        case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST:
473            if (mSpinner != null) {
474                mSpinner.measure(
475                        MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
476                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
477            }
478            break;
479        case ActionBar.NAVIGATION_MODE_CUSTOM:
480            if (mCustomNavView != null) {
481                LayoutParams lp = mCustomNavView.getLayoutParams();
482                final int customNavWidthMode = lp.width != LayoutParams.WRAP_CONTENT ?
483                        MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
484                final int customNavWidth = lp.width >= 0 ?
485                        Math.min(lp.width, availableWidth) : availableWidth;
486                final int customNavHeightMode = lp.height != LayoutParams.WRAP_CONTENT ?
487                        MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
488                final int customNavHeight = lp.height >= 0 ?
489                        Math.min(lp.height, height) : height;
490                mCustomNavView.measure(
491                        MeasureSpec.makeMeasureSpec(customNavWidth, customNavWidthMode),
492                        MeasureSpec.makeMeasureSpec(customNavHeight, customNavHeightMode));
493            }
494            break;
495        case ActionBar.NAVIGATION_MODE_TABS:
496            if (mTabLayout != null) {
497                mTabLayout.measure(
498                        MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
499                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
500            }
501            break;
502        }
503
504        setMeasuredDimension(contentWidth, mContentHeight);
505    }
506
507    private int measureChildView(View child, int availableWidth, int childSpecHeight, int spacing) {
508        child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
509                childSpecHeight);
510
511        availableWidth -= child.getMeasuredWidth();
512        availableWidth -= spacing;
513
514        return availableWidth;
515    }
516
517    @Override
518    protected void onLayout(boolean changed, int l, int t, int r, int b) {
519        int x = getPaddingLeft();
520        final int y = getPaddingTop();
521        final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
522
523        if (mLogoView != null && mLogoView.getVisibility() != GONE) {
524            x += positionChild(mLogoView, x, y, contentHeight) + mSpacing;
525        }
526        if (mIconView != null && mIconView.getVisibility() != GONE) {
527            x += positionChild(mIconView, x, y, contentHeight) + mSpacing;
528        }
529
530        switch (mNavigationMode) {
531        case ActionBar.NAVIGATION_MODE_STANDARD:
532            if (mTitleLayout != null) {
533                x += positionChild(mTitleLayout, x, y, contentHeight) + mSpacing;
534            }
535            break;
536        case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST:
537            if (mSpinner != null) {
538                x += positionChild(mSpinner, x, y, contentHeight) + mSpacing;
539            }
540            break;
541        case ActionBar.NAVIGATION_MODE_CUSTOM:
542            if (mCustomNavView != null) {
543                x += positionChild(mCustomNavView, x, y, contentHeight) + mSpacing;
544            }
545            break;
546        case ActionBar.NAVIGATION_MODE_TABS:
547            if (mTabLayout != null) {
548                x += positionChild(mTabLayout, x, y, contentHeight) + mSpacing;
549            }
550        }
551
552        x = r - l - getPaddingRight();
553
554        if (mMenuView != null) {
555            x -= positionChildInverse(mMenuView, x + mActionSpacing, y, contentHeight)
556                    - mActionSpacing;
557        }
558    }
559
560    private int positionChild(View child, int x, int y, int contentHeight) {
561        int childWidth = child.getMeasuredWidth();
562        int childHeight = child.getMeasuredHeight();
563        int childTop = y + (contentHeight - childHeight) / 2;
564
565        child.layout(x, childTop, x + childWidth, childTop + childHeight);
566
567        return childWidth;
568    }
569
570    private int positionChildInverse(View child, int x, int y, int contentHeight) {
571        int childWidth = child.getMeasuredWidth();
572        int childHeight = child.getMeasuredHeight();
573        int childTop = y + (contentHeight - childHeight) / 2;
574
575        child.layout(x - childWidth, childTop, x, childTop + childHeight);
576
577        return childWidth;
578    }
579
580    private static class TabView extends LinearLayout {
581        private ActionBar.Tab mTab;
582
583        public TabView(Context context, ActionBar.Tab tab) {
584            super(context);
585            mTab = tab;
586
587            // TODO Style tabs based on the theme
588
589            final Drawable icon = tab.getIcon();
590            final CharSequence text = tab.getText();
591
592            if (icon != null) {
593                ImageView iconView = new ImageView(context);
594                iconView.setImageDrawable(icon);
595                LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
596                        LayoutParams.WRAP_CONTENT);
597                lp.gravity = Gravity.CENTER_VERTICAL;
598                iconView.setLayoutParams(lp);
599                addView(iconView);
600            }
601
602            if (text != null) {
603                TextView textView = new TextView(context);
604                textView.setText(text);
605                textView.setSingleLine();
606                textView.setEllipsize(TruncateAt.END);
607                LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
608                        LayoutParams.WRAP_CONTENT);
609                lp.gravity = Gravity.CENTER_VERTICAL;
610                textView.setLayoutParams(lp);
611                addView(textView);
612            }
613
614            setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
615                    LayoutParams.MATCH_PARENT, 1));
616        }
617
618        public ActionBar.Tab getTab() {
619            return mTab;
620        }
621    }
622
623    private class TabClickListener implements OnClickListener {
624        public void onClick(View view) {
625            TabView tabView = (TabView) view;
626            tabView.getTab().select();
627            final int tabCount = mTabLayout.getChildCount();
628            for (int i = 0; i < tabCount; i++) {
629                final View child = mTabLayout.getChildAt(i);
630                child.setSelected(child == view);
631            }
632        }
633    }
634}
635