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