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 ActionBarContextView(Context context)57 public ActionBarContextView(Context context) { 58 this(context, null); 59 } 60 ActionBarContextView(Context context, AttributeSet attrs)61 public ActionBarContextView(Context context, AttributeSet attrs) { 62 this(context, attrs, R.attr.actionModeStyle); 63 } 64 ActionBarContextView(Context context, AttributeSet attrs, int defStyle)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 onDetachedFromWindow()87 public void onDetachedFromWindow() { 88 super.onDetachedFromWindow(); 89 if (mActionMenuPresenter != null) { 90 mActionMenuPresenter.hideOverflowMenu(); 91 mActionMenuPresenter.hideSubMenus(); 92 } 93 } 94 95 @Override setContentHeight(int height)96 public void setContentHeight(int height) { 97 mContentHeight = height; 98 } 99 setCustomView(View view)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 setTitle(CharSequence title)115 public void setTitle(CharSequence title) { 116 mTitle = title; 117 initTitle(); 118 } 119 setSubtitle(CharSequence subtitle)120 public void setSubtitle(CharSequence subtitle) { 121 mSubtitle = subtitle; 122 initTitle(); 123 } 124 getTitle()125 public CharSequence getTitle() { 126 return mTitle; 127 } 128 getSubtitle()129 public CharSequence getSubtitle() { 130 return mSubtitle; 131 } 132 initTitle()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 initForMode(final ActionMode mode)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 closeMode()192 public void closeMode() { 193 if (mClose == null) { 194 killMode(); 195 return; 196 } 197 } 198 killMode()199 public void killMode() { 200 removeAllViews(); 201 mCustomView = null; 202 mMenuView = null; 203 } 204 205 @Override showOverflowMenu()206 public boolean showOverflowMenu() { 207 if (mActionMenuPresenter != null) { 208 return mActionMenuPresenter.showOverflowMenu(); 209 } 210 return false; 211 } 212 213 @Override hideOverflowMenu()214 public boolean hideOverflowMenu() { 215 if (mActionMenuPresenter != null) { 216 return mActionMenuPresenter.hideOverflowMenu(); 217 } 218 return false; 219 } 220 221 @Override isOverflowMenuShowing()222 public boolean isOverflowMenuShowing() { 223 if (mActionMenuPresenter != null) { 224 return mActionMenuPresenter.isOverflowMenuShowing(); 225 } 226 return false; 227 } 228 229 @Override generateDefaultLayoutParams()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 generateLayoutParams(AttributeSet attrs)237 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 238 return new MarginLayoutParams(getContext(), attrs); 239 } 240 241 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)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 onLayout(boolean changed, int l, int t, int r, int b)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 shouldDelayChildPressedState()353 public boolean shouldDelayChildPressedState() { 354 return false; 355 } 356 357 @Override onInitializeAccessibilityEvent(AccessibilityEvent event)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 setTitleOptional(boolean titleOptional)370 public void setTitleOptional(boolean titleOptional) { 371 if (titleOptional != mTitleOptional) { 372 requestLayout(); 373 } 374 mTitleOptional = titleOptional; 375 } 376 isTitleOptional()377 public boolean isTitleOptional() { 378 return mTitleOptional; 379 } 380 } 381