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