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