ActionBarContextView.java revision 3d0f21dab8d891b9aebdd5277348d549eeb843e6
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 */
16package com.android.internal.widget;
17
18import com.android.internal.R;
19
20import android.util.TypedValue;
21import android.view.ContextThemeWrapper;
22import android.widget.ActionMenuPresenter;
23import android.widget.ActionMenuView;
24import com.android.internal.view.menu.MenuBuilder;
25
26import android.animation.Animator;
27import android.animation.Animator.AnimatorListener;
28import android.animation.AnimatorSet;
29import android.animation.ObjectAnimator;
30import android.content.Context;
31import android.content.res.TypedArray;
32import android.graphics.drawable.Drawable;
33import android.text.TextUtils;
34import android.util.AttributeSet;
35import android.view.ActionMode;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.view.ViewGroup;
39import android.view.accessibility.AccessibilityEvent;
40import android.view.animation.DecelerateInterpolator;
41import android.widget.LinearLayout;
42import android.widget.TextView;
43
44/**
45 * @hide
46 */
47public class ActionBarContextView extends AbsActionBarView implements AnimatorListener {
48    private static final String TAG = "ActionBarContextView";
49
50    private CharSequence mTitle;
51    private CharSequence mSubtitle;
52
53    private View mClose;
54    private View mCustomView;
55    private LinearLayout mTitleLayout;
56    private TextView mTitleView;
57    private TextView mSubtitleView;
58    private int mTitleStyleRes;
59    private int mSubtitleStyleRes;
60    private Drawable mSplitBackground;
61    private boolean mTitleOptional;
62
63    private Animator mCurrentAnimation;
64    private boolean mAnimateInOnLayout;
65    private int mAnimationMode;
66
67    private static final int ANIMATE_IDLE = 0;
68    private static final int ANIMATE_IN = 1;
69    private static final int ANIMATE_OUT = 2;
70
71    public ActionBarContextView(Context context) {
72        this(context, null);
73    }
74
75    public ActionBarContextView(Context context, AttributeSet attrs) {
76        this(context, attrs, com.android.internal.R.attr.actionModeStyle);
77    }
78
79    public ActionBarContextView(Context context, AttributeSet attrs, int defStyleAttr) {
80        this(context, attrs, defStyleAttr, 0);
81    }
82
83    public ActionBarContextView(
84            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
85        super(context, attrs, defStyleAttr, defStyleRes);
86
87        final TypedArray a = context.obtainStyledAttributes(
88                attrs, R.styleable.ActionMode, defStyleAttr, defStyleRes);
89        setBackground(a.getDrawable(
90                com.android.internal.R.styleable.ActionMode_background));
91        mTitleStyleRes = a.getResourceId(
92                com.android.internal.R.styleable.ActionMode_titleTextStyle, 0);
93        mSubtitleStyleRes = a.getResourceId(
94                com.android.internal.R.styleable.ActionMode_subtitleTextStyle, 0);
95
96        mContentHeight = a.getLayoutDimension(
97                com.android.internal.R.styleable.ActionMode_height, 0);
98
99        mSplitBackground = a.getDrawable(
100                com.android.internal.R.styleable.ActionMode_backgroundSplit);
101
102        a.recycle();
103    }
104
105    @Override
106    public void onDetachedFromWindow() {
107        super.onDetachedFromWindow();
108        if (mActionMenuPresenter != null) {
109            mActionMenuPresenter.hideOverflowMenu();
110            mActionMenuPresenter.hideSubMenus();
111        }
112    }
113
114    @Override
115    public void setSplitToolbar(boolean split) {
116        if (mSplitActionBar != split) {
117            if (mActionMenuPresenter != null) {
118                // Mode is already active; move everything over and adjust the menu itself.
119                final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
120                        LayoutParams.MATCH_PARENT);
121                if (!split) {
122                    mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
123                    mMenuView.setBackgroundDrawable(null);
124                    final ViewGroup oldParent = (ViewGroup) mMenuView.getParent();
125                    if (oldParent != null) oldParent.removeView(mMenuView);
126                    addView(mMenuView, layoutParams);
127                } else {
128                    // Allow full screen width in split mode.
129                    mActionMenuPresenter.setWidthLimit(
130                            getContext().getResources().getDisplayMetrics().widthPixels, true);
131                    // No limit to the item count; use whatever will fit.
132                    mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
133                    // Span the whole width
134                    layoutParams.width = LayoutParams.MATCH_PARENT;
135                    layoutParams.height = mContentHeight;
136                    mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
137                    mMenuView.setBackgroundDrawable(mSplitBackground);
138                    final ViewGroup oldParent = (ViewGroup) mMenuView.getParent();
139                    if (oldParent != null) oldParent.removeView(mMenuView);
140                    mSplitView.addView(mMenuView, layoutParams);
141                }
142            }
143            super.setSplitToolbar(split);
144        }
145    }
146
147    public void setContentHeight(int height) {
148        mContentHeight = height;
149    }
150
151    public void setCustomView(View view) {
152        if (mCustomView != null) {
153            removeView(mCustomView);
154        }
155        mCustomView = view;
156        if (mTitleLayout != null) {
157            removeView(mTitleLayout);
158            mTitleLayout = null;
159        }
160        if (view != null) {
161            addView(view);
162        }
163        requestLayout();
164    }
165
166    public void setTitle(CharSequence title) {
167        mTitle = title;
168        initTitle();
169    }
170
171    public void setSubtitle(CharSequence subtitle) {
172        mSubtitle = subtitle;
173        initTitle();
174    }
175
176    public CharSequence getTitle() {
177        return mTitle;
178    }
179
180    public CharSequence getSubtitle() {
181        return mSubtitle;
182    }
183
184    private void initTitle() {
185        if (mTitleLayout == null) {
186            LayoutInflater inflater = LayoutInflater.from(getContext());
187            inflater.inflate(R.layout.action_bar_title_item, this);
188            mTitleLayout = (LinearLayout) getChildAt(getChildCount() - 1);
189            mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
190            mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
191            if (mTitleStyleRes != 0) {
192                mTitleView.setTextAppearance(mContext, mTitleStyleRes);
193            }
194            if (mSubtitleStyleRes != 0) {
195                mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes);
196            }
197        }
198
199        mTitleView.setText(mTitle);
200        mSubtitleView.setText(mSubtitle);
201
202        final boolean hasTitle = !TextUtils.isEmpty(mTitle);
203        final boolean hasSubtitle = !TextUtils.isEmpty(mSubtitle);
204        mSubtitleView.setVisibility(hasSubtitle ? VISIBLE : GONE);
205        mTitleLayout.setVisibility(hasTitle || hasSubtitle ? VISIBLE : GONE);
206        if (mTitleLayout.getParent() == null) {
207            addView(mTitleLayout);
208        }
209    }
210
211    public void initForMode(final ActionMode mode) {
212        if (mClose == null) {
213            LayoutInflater inflater = LayoutInflater.from(mContext);
214            mClose = inflater.inflate(R.layout.action_mode_close_item, this, false);
215            addView(mClose);
216        } else if (mClose.getParent() == null) {
217            addView(mClose);
218        }
219
220        View closeButton = mClose.findViewById(R.id.action_mode_close_button);
221        closeButton.setOnClickListener(new OnClickListener() {
222            public void onClick(View v) {
223                mode.finish();
224            }
225        });
226
227        final MenuBuilder menu = (MenuBuilder) mode.getMenu();
228        if (mActionMenuPresenter != null) {
229            mActionMenuPresenter.dismissPopupMenus();
230        }
231        mActionMenuPresenter = new ActionMenuPresenter(mContext);
232        mActionMenuPresenter.setReserveOverflow(true);
233
234        final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
235                LayoutParams.MATCH_PARENT);
236        if (!mSplitActionBar) {
237            menu.addMenuPresenter(mActionMenuPresenter, mPopupContext);
238            mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
239            mMenuView.setBackgroundDrawable(null);
240            addView(mMenuView, layoutParams);
241        } else {
242            // Allow full screen width in split mode.
243            mActionMenuPresenter.setWidthLimit(
244                    getContext().getResources().getDisplayMetrics().widthPixels, true);
245            // No limit to the item count; use whatever will fit.
246            mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
247            // Span the whole width
248            layoutParams.width = LayoutParams.MATCH_PARENT;
249            layoutParams.height = mContentHeight;
250            menu.addMenuPresenter(mActionMenuPresenter, mPopupContext);
251            mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
252            mMenuView.setBackgroundDrawable(mSplitBackground);
253            mSplitView.addView(mMenuView, layoutParams);
254        }
255
256        mAnimateInOnLayout = true;
257    }
258
259    public void closeMode() {
260        if (mAnimationMode == ANIMATE_OUT) {
261            // Called again during close; just finish what we were doing.
262            return;
263        }
264        if (mClose == null) {
265            killMode();
266            return;
267        }
268
269        finishAnimation();
270        mAnimationMode = ANIMATE_OUT;
271        mCurrentAnimation = makeOutAnimation();
272        mCurrentAnimation.start();
273    }
274
275    private void finishAnimation() {
276        final Animator a = mCurrentAnimation;
277        if (a != null) {
278            mCurrentAnimation = null;
279            a.end();
280        }
281    }
282
283    public void killMode() {
284        finishAnimation();
285        removeAllViews();
286        if (mSplitView != null) {
287            mSplitView.removeView(mMenuView);
288        }
289        mCustomView = null;
290        mMenuView = null;
291        mAnimateInOnLayout = false;
292    }
293
294    @Override
295    public boolean showOverflowMenu() {
296        if (mActionMenuPresenter != null) {
297            return mActionMenuPresenter.showOverflowMenu();
298        }
299        return false;
300    }
301
302    @Override
303    public boolean hideOverflowMenu() {
304        if (mActionMenuPresenter != null) {
305            return mActionMenuPresenter.hideOverflowMenu();
306        }
307        return false;
308    }
309
310    @Override
311    public boolean isOverflowMenuShowing() {
312        if (mActionMenuPresenter != null) {
313            return mActionMenuPresenter.isOverflowMenuShowing();
314        }
315        return false;
316    }
317
318    @Override
319    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
320        // Used by custom views if they don't supply layout params. Everything else
321        // added to an ActionBarContextView should have them already.
322        return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
323    }
324
325    @Override
326    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
327        return new MarginLayoutParams(getContext(), attrs);
328    }
329
330    @Override
331    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
332        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
333        if (widthMode != MeasureSpec.EXACTLY) {
334            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
335                    "with android:layout_width=\"match_parent\" (or fill_parent)");
336        }
337
338        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
339        if (heightMode == MeasureSpec.UNSPECIFIED) {
340            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
341                    "with android:layout_height=\"wrap_content\"");
342        }
343
344        final int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
345
346        int maxHeight = mContentHeight > 0 ?
347                mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
348
349        final int verticalPadding = getPaddingTop() + getPaddingBottom();
350        int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight();
351        final int height = maxHeight - verticalPadding;
352        final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
353
354        if (mClose != null) {
355            availableWidth = measureChildView(mClose, availableWidth, childSpecHeight, 0);
356            MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams();
357            availableWidth -= lp.leftMargin + lp.rightMargin;
358        }
359
360        if (mMenuView != null && mMenuView.getParent() == this) {
361            availableWidth = measureChildView(mMenuView, availableWidth,
362                    childSpecHeight, 0);
363        }
364
365        if (mTitleLayout != null && mCustomView == null) {
366            if (mTitleOptional) {
367                final int titleWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
368                mTitleLayout.measure(titleWidthSpec, childSpecHeight);
369                final int titleWidth = mTitleLayout.getMeasuredWidth();
370                final boolean titleFits = titleWidth <= availableWidth;
371                if (titleFits) {
372                    availableWidth -= titleWidth;
373                }
374                mTitleLayout.setVisibility(titleFits ? VISIBLE : GONE);
375            } else {
376                availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0);
377            }
378        }
379
380        if (mCustomView != null) {
381            ViewGroup.LayoutParams lp = mCustomView.getLayoutParams();
382            final int customWidthMode = lp.width != LayoutParams.WRAP_CONTENT ?
383                    MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
384            final int customWidth = lp.width >= 0 ?
385                    Math.min(lp.width, availableWidth) : availableWidth;
386            final int customHeightMode = lp.height != LayoutParams.WRAP_CONTENT ?
387                    MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
388            final int customHeight = lp.height >= 0 ?
389                    Math.min(lp.height, height) : height;
390            mCustomView.measure(MeasureSpec.makeMeasureSpec(customWidth, customWidthMode),
391                    MeasureSpec.makeMeasureSpec(customHeight, customHeightMode));
392        }
393
394        if (mContentHeight <= 0) {
395            int measuredHeight = 0;
396            final int count = getChildCount();
397            for (int i = 0; i < count; i++) {
398                View v = getChildAt(i);
399                int paddedViewHeight = v.getMeasuredHeight() + verticalPadding;
400                if (paddedViewHeight > measuredHeight) {
401                    measuredHeight = paddedViewHeight;
402                }
403            }
404            setMeasuredDimension(contentWidth, measuredHeight);
405        } else {
406            setMeasuredDimension(contentWidth, maxHeight);
407        }
408    }
409
410    private Animator makeInAnimation() {
411        mClose.setTranslationX(-mClose.getWidth() -
412                ((MarginLayoutParams) mClose.getLayoutParams()).leftMargin);
413        ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX", 0);
414        buttonAnimator.setDuration(200);
415        buttonAnimator.addListener(this);
416        buttonAnimator.setInterpolator(new DecelerateInterpolator());
417
418        AnimatorSet set = new AnimatorSet();
419        AnimatorSet.Builder b = set.play(buttonAnimator);
420
421        if (mMenuView != null) {
422            final int count = mMenuView.getChildCount();
423            if (count > 0) {
424                for (int i = count - 1, j = 0; i >= 0; i--, j++) {
425                    View child = mMenuView.getChildAt(i);
426                    child.setScaleY(0);
427                    ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 0, 1);
428                    a.setDuration(300);
429                    b.with(a);
430                }
431            }
432        }
433
434        return set;
435    }
436
437    private Animator makeOutAnimation() {
438        ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX",
439                -mClose.getWidth() - ((MarginLayoutParams) mClose.getLayoutParams()).leftMargin);
440        buttonAnimator.setDuration(200);
441        buttonAnimator.addListener(this);
442        buttonAnimator.setInterpolator(new DecelerateInterpolator());
443
444        AnimatorSet set = new AnimatorSet();
445        AnimatorSet.Builder b = set.play(buttonAnimator);
446
447        if (mMenuView != null) {
448            final int count = mMenuView.getChildCount();
449            if (count > 0) {
450                for (int i = 0; i < 0; i++) {
451                    View child = mMenuView.getChildAt(i);
452                    child.setScaleY(0);
453                    ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 0);
454                    a.setDuration(300);
455                    b.with(a);
456                }
457            }
458        }
459
460        return set;
461    }
462
463    @Override
464    protected void onLayout(boolean changed, int l, int t, int r, int b) {
465        final boolean isLayoutRtl = isLayoutRtl();
466        int x = isLayoutRtl ? r - l - getPaddingRight() : getPaddingLeft();
467        final int y = getPaddingTop();
468        final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
469
470        if (mClose != null && mClose.getVisibility() != GONE) {
471            MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams();
472            final int startMargin = (isLayoutRtl ? lp.rightMargin : lp.leftMargin);
473            final int endMargin = (isLayoutRtl ? lp.leftMargin : lp.rightMargin);
474            x = next(x, startMargin, isLayoutRtl);
475            x += positionChild(mClose, x, y, contentHeight, isLayoutRtl);
476            x = next(x, endMargin, isLayoutRtl);
477
478            if (mAnimateInOnLayout) {
479                mAnimationMode = ANIMATE_IN;
480                mCurrentAnimation = makeInAnimation();
481                mCurrentAnimation.start();
482                mAnimateInOnLayout = false;
483            }
484        }
485
486        if (mTitleLayout != null && mCustomView == null && mTitleLayout.getVisibility() != GONE) {
487            x += positionChild(mTitleLayout, x, y, contentHeight, isLayoutRtl);
488        }
489
490        if (mCustomView != null) {
491            x += positionChild(mCustomView, x, y, contentHeight, isLayoutRtl);
492        }
493
494        x = isLayoutRtl ? getPaddingLeft() : r - l - getPaddingRight();
495
496        if (mMenuView != null) {
497            x += positionChild(mMenuView, x, y, contentHeight, !isLayoutRtl);
498        }
499    }
500
501    @Override
502    public void onAnimationStart(Animator animation) {
503    }
504
505    @Override
506    public void onAnimationEnd(Animator animation) {
507        if (mAnimationMode == ANIMATE_OUT) {
508            killMode();
509        }
510        mAnimationMode = ANIMATE_IDLE;
511    }
512
513    @Override
514    public void onAnimationCancel(Animator animation) {
515    }
516
517    @Override
518    public void onAnimationRepeat(Animator animation) {
519    }
520
521    @Override
522    public boolean shouldDelayChildPressedState() {
523        return false;
524    }
525
526    @Override
527    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
528        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
529            // Action mode started
530            event.setSource(this);
531            event.setClassName(getClass().getName());
532            event.setPackageName(getContext().getPackageName());
533            event.setContentDescription(mTitle);
534        } else {
535            super.onInitializeAccessibilityEvent(event);
536        }
537    }
538
539    public void setTitleOptional(boolean titleOptional) {
540        if (titleOptional != mTitleOptional) {
541            requestLayout();
542        }
543        mTitleOptional = titleOptional;
544    }
545
546    public boolean isTitleOptional() {
547        return mTitleOptional;
548    }
549}
550