blob: 2429ad4ef382a51ca66ab198b84d78c852bb3c9c [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.view.menu;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcelable;
import android.support.annotation.RestrictTo;
import android.support.v4.content.res.ConfigurationHelper;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.support.v7.appcompat.R;
import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.AppCompatTextView;
import android.support.v7.widget.ForwardingListener;
import android.support.v7.widget.ListPopupWindow;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
/**
* @hide
*/
@RestrictTo(GROUP_ID)
public class ActionMenuItemView extends AppCompatTextView
implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener,
ActionMenuView.ActionMenuChildView {
private static final String TAG = "ActionMenuItemView";
MenuItemImpl mItemData;
private CharSequence mTitle;
private Drawable mIcon;
MenuBuilder.ItemInvoker mItemInvoker;
private ForwardingListener mForwardingListener;
PopupCallback mPopupCallback;
private boolean mAllowTextWithIcon;
private boolean mExpandedFormat;
private int mMinWidth;
private int mSavedPaddingLeft;
private static final int MAX_ICON_SIZE = 32; // dp
private int mMaxIconSize;
public ActionMenuItemView(Context context) {
this(context, null);
}
public ActionMenuItemView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
final Resources res = context.getResources();
mAllowTextWithIcon = shouldAllowTextWithIcon();
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ActionMenuItemView, defStyle, 0);
mMinWidth = a.getDimensionPixelSize(
R.styleable.ActionMenuItemView_android_minWidth, 0);
a.recycle();
final float density = res.getDisplayMetrics().density;
mMaxIconSize = (int) (MAX_ICON_SIZE * density + 0.5f);
setOnClickListener(this);
setOnLongClickListener(this);
mSavedPaddingLeft = -1;
setSaveEnabled(false);
}
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mAllowTextWithIcon = shouldAllowTextWithIcon();
updateTextButtonVisibility();
}
/**
* Whether action menu items should obey the "withText" showAsAction flag. This may be set to
* false for situations where space is extremely limited. -->
*/
private boolean shouldAllowTextWithIcon() {
final Configuration config = getContext().getResources().getConfiguration();
final int widthDp = ConfigurationHelper.getScreenWidthDp(getResources());
final int heightDp = ConfigurationHelper.getScreenHeightDp(getResources());
return widthDp >= 480 || (widthDp >= 640 && heightDp >= 480)
|| config.orientation == Configuration.ORIENTATION_LANDSCAPE;
}
@Override
public void setPadding(int l, int t, int r, int b) {
mSavedPaddingLeft = l;
super.setPadding(l, t, r, b);
}
public MenuItemImpl getItemData() {
return mItemData;
}
public void initialize(MenuItemImpl itemData, int menuType) {
mItemData = itemData;
setIcon(itemData.getIcon());
setTitle(itemData.getTitleForItemView(this)); // Title only takes effect if there is no icon
setId(itemData.getItemId());
setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
setEnabled(itemData.isEnabled());
if (itemData.hasSubMenu()) {
if (mForwardingListener == null) {
mForwardingListener = new ActionMenuItemForwardingListener();
}
}
}
@Override
public boolean onTouchEvent(MotionEvent e) {
if (mItemData.hasSubMenu() && mForwardingListener != null
&& mForwardingListener.onTouch(this, e)) {
return true;
}
return super.onTouchEvent(e);
}
@Override
public void onClick(View v) {
if (mItemInvoker != null) {
mItemInvoker.invokeItem(mItemData);
}
}
public void setItemInvoker(MenuBuilder.ItemInvoker invoker) {
mItemInvoker = invoker;
}
public void setPopupCallback(PopupCallback popupCallback) {
mPopupCallback = popupCallback;
}
public boolean prefersCondensedTitle() {
return true;
}
public void setCheckable(boolean checkable) {
// TODO Support checkable action items
}
public void setChecked(boolean checked) {
// TODO Support checkable action items
}
public void setExpandedFormat(boolean expandedFormat) {
if (mExpandedFormat != expandedFormat) {
mExpandedFormat = expandedFormat;
if (mItemData != null) {
mItemData.actionFormatChanged();
}
}
}
private void updateTextButtonVisibility() {
boolean visible = !TextUtils.isEmpty(mTitle);
visible &= mIcon == null ||
(mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat));
setText(visible ? mTitle : null);
}
public void setIcon(Drawable icon) {
mIcon = icon;
if (icon != null) {
int width = icon.getIntrinsicWidth();
int height = icon.getIntrinsicHeight();
if (width > mMaxIconSize) {
final float scale = (float) mMaxIconSize / width;
width = mMaxIconSize;
height *= scale;
}
if (height > mMaxIconSize) {
final float scale = (float) mMaxIconSize / height;
height = mMaxIconSize;
width *= scale;
}
icon.setBounds(0, 0, width, height);
}
setCompoundDrawables(icon, null, null, null);
updateTextButtonVisibility();
}
public boolean hasText() {
return !TextUtils.isEmpty(getText());
}
public void setShortcut(boolean showShortcut, char shortcutKey) {
// Action buttons don't show text for shortcut keys.
}
public void setTitle(CharSequence title) {
mTitle = title;
setContentDescription(mTitle);
updateTextButtonVisibility();
}
public boolean showsIcon() {
return true;
}
public boolean needsDividerBefore() {
return hasText() && mItemData.getIcon() == null;
}
public boolean needsDividerAfter() {
return hasText();
}
@Override
public boolean onLongClick(View v) {
if (hasText()) {
// Don't show the cheat sheet for items that already show text.
return false;
}
final int[] screenPos = new int[2];
final Rect displayFrame = new Rect();
getLocationOnScreen(screenPos);
getWindowVisibleDisplayFrame(displayFrame);
final Context context = getContext();
final int width = getWidth();
final int height = getHeight();
final int midy = screenPos[1] + height / 2;
int referenceX = screenPos[0] + width / 2;
if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) {
final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
referenceX = screenWidth - referenceX; // mirror
}
Toast cheatSheet = Toast.makeText(context, mItemData.getTitle(), Toast.LENGTH_SHORT);
if (midy < displayFrame.height()) {
// Show along the top; follow action buttons
cheatSheet.setGravity(Gravity.TOP | GravityCompat.END, referenceX,
screenPos[1] + height - displayFrame.top);
} else {
// Show along the bottom center
cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
}
cheatSheet.show();
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final boolean textVisible = hasText();
if (textVisible && mSavedPaddingLeft >= 0) {
super.setPadding(mSavedPaddingLeft, getPaddingTop(),
getPaddingRight(), getPaddingBottom());
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int oldMeasuredWidth = getMeasuredWidth();
final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(widthSize, mMinWidth)
: mMinWidth;
if (widthMode != MeasureSpec.EXACTLY && mMinWidth > 0 && oldMeasuredWidth < targetWidth) {
// Remeasure at exactly the minimum width.
super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
heightMeasureSpec);
}
if (!textVisible && mIcon != null) {
// TextView won't center compound drawables in both dimensions without
// a little coercion. Pad in to center the icon after we've measured.
final int w = getMeasuredWidth();
final int dw = mIcon.getBounds().width();
super.setPadding((w - dw) / 2, getPaddingTop(), getPaddingRight(), getPaddingBottom());
}
}
private class ActionMenuItemForwardingListener extends ForwardingListener {
public ActionMenuItemForwardingListener() {
super(ActionMenuItemView.this);
}
@Override
public ShowableListMenu getPopup() {
if (mPopupCallback != null) {
return mPopupCallback.getPopup();
}
return null;
}
@Override
protected boolean onForwardingStarted() {
// Call the invoker, then check if the expected popup is showing.
if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) {
final ShowableListMenu popup = getPopup();
return popup != null && popup.isShowing();
}
return false;
}
// Do not backport the framework impl here.
// The framework's ListPopupWindow uses an animation before performing the item click
// after selecting an item. As AppCompat doesn't use an animation, the popup is
// dismissed and thus null'ed out before onForwardingStopped() has been called.
// This messes up ActionMenuItemView's onForwardingStopped() impl since it will now
// return false and make ListPopupWindow think it's still forwarding.
}
@Override
public void onRestoreInstanceState(Parcelable state) {
// This might get called with the state of ActionView since it shares the same ID with
// ActionMenuItemView. Do not restore this state as ActionMenuItemView never saved it.
super.onRestoreInstanceState(null);
}
public static abstract class PopupCallback {
public abstract ShowableListMenu getPopup();
}
}