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