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