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 
     17 package androidx.appcompat.widget;
     18 
     19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     20 
     21 import android.content.Context;
     22 import android.text.TextUtils;
     23 import android.util.AttributeSet;
     24 import android.view.LayoutInflater;
     25 import android.view.View;
     26 import android.view.ViewGroup;
     27 import android.view.accessibility.AccessibilityEvent;
     28 import android.widget.LinearLayout;
     29 import android.widget.TextView;
     30 
     31 import androidx.annotation.RestrictTo;
     32 import androidx.appcompat.R;
     33 import androidx.appcompat.view.ActionMode;
     34 import androidx.appcompat.view.menu.MenuBuilder;
     35 import androidx.core.view.ViewCompat;
     36 
     37 /**
     38  * @hide
     39  */
     40 @RestrictTo(LIBRARY_GROUP)
     41 public class ActionBarContextView extends AbsActionBarView {
     42     private static final String TAG = "ActionBarContextView";
     43 
     44     private CharSequence mTitle;
     45     private CharSequence mSubtitle;
     46 
     47     private View mClose;
     48     private View mCustomView;
     49     private LinearLayout mTitleLayout;
     50     private TextView mTitleView;
     51     private TextView mSubtitleView;
     52     private int mTitleStyleRes;
     53     private int mSubtitleStyleRes;
     54     private boolean mTitleOptional;
     55     private int mCloseItemLayout;
     56 
     57     public ActionBarContextView(Context context) {
     58         this(context, null);
     59     }
     60 
     61     public ActionBarContextView(Context context, AttributeSet attrs) {
     62         this(context, attrs, R.attr.actionModeStyle);
     63     }
     64 
     65     public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) {
     66         super(context, attrs, defStyle);
     67 
     68         final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
     69                 R.styleable.ActionMode, defStyle, 0);
     70         ViewCompat.setBackground(this, a.getDrawable(R.styleable.ActionMode_background));
     71         mTitleStyleRes = a.getResourceId(
     72                 R.styleable.ActionMode_titleTextStyle, 0);
     73         mSubtitleStyleRes = a.getResourceId(
     74                 R.styleable.ActionMode_subtitleTextStyle, 0);
     75 
     76         mContentHeight = a.getLayoutDimension(
     77                 R.styleable.ActionMode_height, 0);
     78 
     79         mCloseItemLayout = a.getResourceId(
     80                 R.styleable.ActionMode_closeItemLayout,
     81                 R.layout.abc_action_mode_close_item_material);
     82 
     83         a.recycle();
     84     }
     85 
     86     @Override
     87     public void onDetachedFromWindow() {
     88         super.onDetachedFromWindow();
     89         if (mActionMenuPresenter != null) {
     90             mActionMenuPresenter.hideOverflowMenu();
     91             mActionMenuPresenter.hideSubMenus();
     92         }
     93     }
     94 
     95     @Override
     96     public void setContentHeight(int height) {
     97         mContentHeight = height;
     98     }
     99 
    100     public void setCustomView(View view) {
    101         if (mCustomView != null) {
    102             removeView(mCustomView);
    103         }
    104         mCustomView = view;
    105         if (view != null && mTitleLayout != null) {
    106             removeView(mTitleLayout);
    107             mTitleLayout = null;
    108         }
    109         if (view != null) {
    110             addView(view);
    111         }
    112         requestLayout();
    113     }
    114 
    115     public void setTitle(CharSequence title) {
    116         mTitle = title;
    117         initTitle();
    118     }
    119 
    120     public void setSubtitle(CharSequence subtitle) {
    121         mSubtitle = subtitle;
    122         initTitle();
    123     }
    124 
    125     public CharSequence getTitle() {
    126         return mTitle;
    127     }
    128 
    129     public CharSequence getSubtitle() {
    130         return mSubtitle;
    131     }
    132 
    133     private void initTitle() {
    134         if (mTitleLayout == null) {
    135             LayoutInflater inflater = LayoutInflater.from(getContext());
    136             inflater.inflate(R.layout.abc_action_bar_title_item, this);
    137             mTitleLayout = (LinearLayout) getChildAt(getChildCount() - 1);
    138             mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
    139             mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
    140             if (mTitleStyleRes != 0) {
    141                 mTitleView.setTextAppearance(getContext(), mTitleStyleRes);
    142             }
    143             if (mSubtitleStyleRes != 0) {
    144                 mSubtitleView.setTextAppearance(getContext(), mSubtitleStyleRes);
    145             }
    146         }
    147 
    148         mTitleView.setText(mTitle);
    149         mSubtitleView.setText(mSubtitle);
    150 
    151         final boolean hasTitle = !TextUtils.isEmpty(mTitle);
    152         final boolean hasSubtitle = !TextUtils.isEmpty(mSubtitle);
    153         mSubtitleView.setVisibility(hasSubtitle ? VISIBLE : GONE);
    154         mTitleLayout.setVisibility(hasTitle || hasSubtitle ? VISIBLE : GONE);
    155         if (mTitleLayout.getParent() == null) {
    156             addView(mTitleLayout);
    157         }
    158     }
    159 
    160     public void initForMode(final ActionMode mode) {
    161         if (mClose == null) {
    162             LayoutInflater inflater = LayoutInflater.from(getContext());
    163             mClose = inflater.inflate(mCloseItemLayout, this, false);
    164             addView(mClose);
    165         } else if (mClose.getParent() == null) {
    166             addView(mClose);
    167         }
    168 
    169         View closeButton = mClose.findViewById(R.id.action_mode_close_button);
    170         closeButton.setOnClickListener(new OnClickListener() {
    171             @Override
    172             public void onClick(View v) {
    173                 mode.finish();
    174             }
    175         });
    176 
    177         final MenuBuilder menu = (MenuBuilder) mode.getMenu();
    178         if (mActionMenuPresenter != null) {
    179             mActionMenuPresenter.dismissPopupMenus();
    180         }
    181         mActionMenuPresenter = new ActionMenuPresenter(getContext());
    182         mActionMenuPresenter.setReserveOverflow(true);
    183 
    184         final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
    185                 LayoutParams.MATCH_PARENT);
    186         menu.addMenuPresenter(mActionMenuPresenter, mPopupContext);
    187         mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
    188         ViewCompat.setBackground(mMenuView, null);
    189         addView(mMenuView, layoutParams);
    190     }
    191 
    192     public void closeMode() {
    193         if (mClose == null) {
    194             killMode();
    195             return;
    196         }
    197     }
    198 
    199     public void killMode() {
    200         removeAllViews();
    201         mCustomView = null;
    202         mMenuView = null;
    203     }
    204 
    205     @Override
    206     public boolean showOverflowMenu() {
    207         if (mActionMenuPresenter != null) {
    208             return mActionMenuPresenter.showOverflowMenu();
    209         }
    210         return false;
    211     }
    212 
    213     @Override
    214     public boolean hideOverflowMenu() {
    215         if (mActionMenuPresenter != null) {
    216             return mActionMenuPresenter.hideOverflowMenu();
    217         }
    218         return false;
    219     }
    220 
    221     @Override
    222     public boolean isOverflowMenuShowing() {
    223         if (mActionMenuPresenter != null) {
    224             return mActionMenuPresenter.isOverflowMenuShowing();
    225         }
    226         return false;
    227     }
    228 
    229     @Override
    230     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
    231         // Used by custom views if they don't supply layout params. Everything else
    232         // added to an ActionBarContextView should have them already.
    233         return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    234     }
    235 
    236     @Override
    237     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
    238         return new MarginLayoutParams(getContext(), attrs);
    239     }
    240 
    241     @Override
    242     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    243         final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    244         if (widthMode != MeasureSpec.EXACTLY) {
    245             throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
    246                     "with android:layout_width=\"match_parent\" (or fill_parent)");
    247         }
    248 
    249         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    250         if (heightMode == MeasureSpec.UNSPECIFIED) {
    251             throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
    252                     "with android:layout_height=\"wrap_content\"");
    253         }
    254 
    255         final int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
    256 
    257         int maxHeight = mContentHeight > 0 ?
    258                 mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
    259 
    260         final int verticalPadding = getPaddingTop() + getPaddingBottom();
    261         int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight();
    262         final int height = maxHeight - verticalPadding;
    263         final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
    264 
    265         if (mClose != null) {
    266             availableWidth = measureChildView(mClose, availableWidth, childSpecHeight, 0);
    267             MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams();
    268             availableWidth -= lp.leftMargin + lp.rightMargin;
    269         }
    270 
    271         if (mMenuView != null && mMenuView.getParent() == this) {
    272             availableWidth = measureChildView(mMenuView, availableWidth,
    273                     childSpecHeight, 0);
    274         }
    275 
    276         if (mTitleLayout != null && mCustomView == null) {
    277             if (mTitleOptional) {
    278                 final int titleWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    279                 mTitleLayout.measure(titleWidthSpec, childSpecHeight);
    280                 final int titleWidth = mTitleLayout.getMeasuredWidth();
    281                 final boolean titleFits = titleWidth <= availableWidth;
    282                 if (titleFits) {
    283                     availableWidth -= titleWidth;
    284                 }
    285                 mTitleLayout.setVisibility(titleFits ? VISIBLE : GONE);
    286             } else {
    287                 availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0);
    288             }
    289         }
    290 
    291         if (mCustomView != null) {
    292             ViewGroup.LayoutParams lp = mCustomView.getLayoutParams();
    293             final int customWidthMode = lp.width != LayoutParams.WRAP_CONTENT ?
    294                     MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
    295             final int customWidth = lp.width >= 0 ?
    296                     Math.min(lp.width, availableWidth) : availableWidth;
    297             final int customHeightMode = lp.height != LayoutParams.WRAP_CONTENT ?
    298                     MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
    299             final int customHeight = lp.height >= 0 ?
    300                     Math.min(lp.height, height) : height;
    301             mCustomView.measure(MeasureSpec.makeMeasureSpec(customWidth, customWidthMode),
    302                     MeasureSpec.makeMeasureSpec(customHeight, customHeightMode));
    303         }
    304 
    305         if (mContentHeight <= 0) {
    306             int measuredHeight = 0;
    307             final int count = getChildCount();
    308             for (int i = 0; i < count; i++) {
    309                 View v = getChildAt(i);
    310                 int paddedViewHeight = v.getMeasuredHeight() + verticalPadding;
    311                 if (paddedViewHeight > measuredHeight) {
    312                     measuredHeight = paddedViewHeight;
    313                 }
    314             }
    315             setMeasuredDimension(contentWidth, measuredHeight);
    316         } else {
    317             setMeasuredDimension(contentWidth, maxHeight);
    318         }
    319     }
    320 
    321     @Override
    322     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    323         final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this);
    324         int x = isLayoutRtl ? r - l - getPaddingRight() : getPaddingLeft();
    325         final int y = getPaddingTop();
    326         final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
    327 
    328         if (mClose != null && mClose.getVisibility() != GONE) {
    329             MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams();
    330             final int startMargin = (isLayoutRtl ? lp.rightMargin : lp.leftMargin);
    331             final int endMargin = (isLayoutRtl ? lp.leftMargin : lp.rightMargin);
    332             x = next(x, startMargin, isLayoutRtl);
    333             x += positionChild(mClose, x, y, contentHeight, isLayoutRtl);
    334             x = next(x, endMargin, isLayoutRtl);
    335         }
    336 
    337         if (mTitleLayout != null && mCustomView == null && mTitleLayout.getVisibility() != GONE) {
    338             x += positionChild(mTitleLayout, x, y, contentHeight, isLayoutRtl);
    339         }
    340 
    341         if (mCustomView != null) {
    342             x += positionChild(mCustomView, x, y, contentHeight, isLayoutRtl);
    343         }
    344 
    345         x = isLayoutRtl ? getPaddingLeft() : r - l - getPaddingRight();
    346 
    347         if (mMenuView != null) {
    348             x += positionChild(mMenuView, x, y, contentHeight, !isLayoutRtl);
    349         }
    350     }
    351 
    352     @Override
    353     public boolean shouldDelayChildPressedState() {
    354         return false;
    355     }
    356 
    357     @Override
    358     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    359         if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
    360             // Action mode started
    361             event.setSource(this);
    362             event.setClassName(getClass().getName());
    363             event.setPackageName(getContext().getPackageName());
    364             event.setContentDescription(mTitle);
    365         } else {
    366             super.onInitializeAccessibilityEvent(event);
    367         }
    368     }
    369 
    370     public void setTitleOptional(boolean titleOptional) {
    371         if (titleOptional != mTitleOptional) {
    372             requestLayout();
    373         }
    374         mTitleOptional = titleOptional;
    375     }
    376 
    377     public boolean isTitleOptional() {
    378         return mTitleOptional;
    379     }
    380 }
    381