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