Introduce ViewCompat.setTooltip
This calls View.setTooltip on API26+. The fallback
implementation is a Toast-based tooltip.
Use the new method in ActionMenuItemView,
MediaRouteButton, ScrollingTabContainerView and
TabLayout.
Bug: 31516506
Test: manual in Support7Demos and SupportDesignDemos
Change-Id: I23832a2fd5b589769225a0542c2a0ce4778e673c
diff --git a/api/current.txt b/api/current.txt
index 50ba649..60b09bc 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6858,6 +6858,7 @@
method public static void setScaleY(android.view.View, float);
method public static void setScrollIndicators(android.view.View, int);
method public static void setScrollIndicators(android.view.View, int, int);
+ method public static void setTooltip(android.view.View, java.lang.CharSequence);
method public static void setTransitionName(android.view.View, java.lang.String);
method public static void setTranslationX(android.view.View, float);
method public static void setTranslationY(android.view.View, float);
diff --git a/compat/Android.mk b/compat/Android.mk
index ba5b958..9d8d258 100644
--- a/compat/Android.mk
+++ b/compat/Android.mk
@@ -42,6 +42,7 @@
$(call all-java-files-under,api22) \
$(call all-java-files-under,api23) \
$(call all-java-files-under,api24) \
+ $(call all-java-files-under,api26) \
$(call all-java-files-under,java) \
$(call all-Iaidl-files-under,java)
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
diff --git a/compat/api26/android/support/v4/view/ViewCompatApi26.java b/compat/api26/android/support/v4/view/ViewCompatApi26.java
new file mode 100644
index 0000000..3e49404
--- /dev/null
+++ b/compat/api26/android/support/v4/view/ViewCompatApi26.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (C) 2016 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.v4.view;
+
+import android.support.annotation.RequiresApi;
+import android.view.View;
+
+@RequiresApi(26)
+class ViewCompatApi26 {
+ public static void setTooltip(View view, CharSequence tooltip) {
+ view.setTooltip(tooltip);
+ }
+}
diff --git a/compat/build.gradle b/compat/build.gradle
index 5722998..696c321 100644
--- a/compat/build.gradle
+++ b/compat/build.gradle
@@ -44,6 +44,7 @@
'api22',
'api23',
'api24',
+ 'api26',
'java'
]
main.aidl.srcDirs = ['java']
diff --git a/compat/java/android/support/v4/internal/view/TooltipCompat.java b/compat/java/android/support/v4/internal/view/TooltipCompat.java
new file mode 100644
index 0000000..aae4f48
--- /dev/null
+++ b/compat/java/android/support/v4/internal/view/TooltipCompat.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016 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.v4.internal.view;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.annotation.RestrictTo;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Toast;
+
+/**
+ * Toast-based popup used to emulate a tooltip on platform versions prior to API 26.
+ * Unlike the platform version of the tooltip, it is invoked only by a long press, but not
+ * mouse hover.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class TooltipCompat {
+
+ /**
+ * Set a tooltip on a view. The tooltip text will be displayed on long click,
+ * in a toast aligned with the view.
+ * @param view View to align to
+ * @param tooltip Tooltip text
+ */
+ public static void setTooltip(View view, final CharSequence tooltip) {
+ if (TextUtils.isEmpty(tooltip)) {
+ view.setOnLongClickListener(null);
+ view.setLongClickable(false);
+ } else {
+ view.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ showTooltipToast(v, tooltip);
+ return true;
+ }
+ });
+ }
+ }
+
+ private static void showTooltipToast(View v, CharSequence tooltip) {
+ final int[] screenPos = new int[2];
+ final Rect displayFrame = new Rect();
+ v.getLocationOnScreen(screenPos);
+ v.getWindowVisibleDisplayFrame(displayFrame);
+
+ final Context context = v.getContext();
+ final int width = v.getWidth();
+ final int height = v.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 toast = Toast.makeText(context, tooltip, Toast.LENGTH_SHORT);
+ if (midy < displayFrame.height()) {
+ // Show along the top; follow action buttons
+ toast.setGravity(Gravity.TOP | GravityCompat.END, referenceX,
+ screenPos[1] + height - displayFrame.top);
+ } else {
+ // Show along the bottom center
+ toast.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
+ }
+ toast.show();
+ }
+}
diff --git a/compat/java/android/support/v4/view/ViewCompat.java b/compat/java/android/support/v4/view/ViewCompat.java
index 434b850..4dab42c 100644
--- a/compat/java/android/support/v4/view/ViewCompat.java
+++ b/compat/java/android/support/v4/view/ViewCompat.java
@@ -31,6 +31,7 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
+import android.support.v4.internal.view.TooltipCompat;
import android.support.v4.os.BuildCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
@@ -488,6 +489,7 @@
void offsetLeftAndRight(View view, int offset);
void setPointerIcon(View view, PointerIconCompat pointerIcon);
Display getDisplay(View view);
+ void setTooltip(View view, CharSequence tooltip);
}
static class BaseViewCompatImpl implements ViewCompatImpl {
@@ -1161,6 +1163,11 @@
public Display getDisplay(View view) {
return ViewCompatBase.getDisplay(view);
}
+
+ @Override
+ public void setTooltip(View view, CharSequence tooltip) {
+ TooltipCompat.setTooltip(view, tooltip);
+ }
}
static class HCViewCompatImpl extends BaseViewCompatImpl {
@@ -1815,10 +1822,19 @@
}
}
+ static class Api26ViewCompatImpl extends Api24ViewCompatImpl {
+ @Override
+ public void setTooltip(View view, CharSequence tooltip) {
+ ViewCompatApi26.setTooltip(view, tooltip);
+ }
+ }
+
static final ViewCompatImpl IMPL;
static {
final int version = android.os.Build.VERSION.SDK_INT;
- if (BuildCompat.isAtLeastN()) {
+ if (BuildCompat.isAtLeastO()) {
+ IMPL = new Api26ViewCompatImpl();
+ } else if (version >= 24) {
IMPL = new Api24ViewCompatImpl();
} else if (version >= 23) {
IMPL = new MarshmallowViewCompatImpl();
@@ -3581,5 +3597,20 @@
return IMPL.getDisplay(view);
}
+ /**
+ * Sets the tooltip for the view.
+ * <p>
+ * Compatibility:
+ * <ul>
+ * <li>API < 26: Sets or clears (when tooltip is null) the view's OnLongClickListener.
+ * Creates a Toast on long click.
+ * </ul>
+ *
+ * @param tooltip the tooltip text
+ */
+ public static void setTooltip(@NonNull View view, @Nullable CharSequence tooltip) {
+ IMPL.setTooltip(view, tooltip);
+ }
+
protected ViewCompat() {}
}
diff --git a/design/src/android/support/design/widget/TabLayout.java b/design/src/android/support/design/widget/TabLayout.java
index 23a15e1..4d392f2 100755
--- a/design/src/android/support/design/widget/TabLayout.java
+++ b/design/src/android/support/design/widget/TabLayout.java
@@ -28,7 +28,6 @@
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Paint;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.ColorInt;
@@ -65,7 +64,6 @@
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
-import android.widget.Toast;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1485,7 +1483,7 @@
}
}
- class TabView extends LinearLayout implements OnLongClickListener {
+ class TabView extends LinearLayout {
private Tab mTab;
private TextView mTextView;
private ImageView mIconView;
@@ -1751,44 +1749,7 @@
iconView.requestLayout();
}
}
-
- if (!hasText && !TextUtils.isEmpty(contentDesc)) {
- setOnLongClickListener(this);
- } else {
- setOnLongClickListener(null);
- setLongClickable(false);
- }
- }
-
- @Override
- public boolean onLongClick(final View v) {
- 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, mTab.getContentDescription(),
- Toast.LENGTH_SHORT);
- if (midy < displayFrame.height()) {
- // Show below the tab view
- 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;
+ ViewCompat.setTooltip(this, hasText ? null : contentDesc);
}
public Tab getTab() {
diff --git a/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java b/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java
index 77f5f17..5f7aa3e 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java
@@ -22,12 +22,10 @@
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.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;
@@ -35,18 +33,15 @@
import android.support.v7.widget.ForwardingListener;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
-import android.widget.Toast;
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
public class ActionMenuItemView extends AppCompatTextView
- implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener,
- ActionMenuView.ActionMenuChildView {
+ implements MenuView.ItemView, View.OnClickListener, ActionMenuView.ActionMenuChildView {
private static final String TAG = "ActionMenuItemView";
@@ -87,7 +82,6 @@
mMaxIconSize = (int) (MAX_ICON_SIZE * density + 0.5f);
setOnClickListener(this);
- setOnLongClickListener(this);
mSavedPaddingLeft = -1;
setSaveEnabled(false);
@@ -190,6 +184,9 @@
(mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat));
setText(visible ? mTitle : null);
+
+ // Show the tooltip for items that do not already show text.
+ ViewCompat.setTooltip(this, visible ? null : mTitle);
}
public void setIcon(Drawable icon) {
@@ -242,40 +239,6 @@
}
@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) {
diff --git a/v7/appcompat/src/android/support/v7/widget/ScrollingTabContainerView.java b/v7/appcompat/src/android/support/v7/widget/ScrollingTabContainerView.java
index ea536d2..3e729da 100644
--- a/v7/appcompat/src/android/support/v7/widget/ScrollingTabContainerView.java
+++ b/v7/appcompat/src/android/support/v7/widget/ScrollingTabContainerView.java
@@ -46,7 +46,6 @@
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
-import android.widget.Toast;
/**
* This widget implements the dynamic action bar tab behavior that can change across different
@@ -377,7 +376,7 @@
// no-op
}
- private class TabView extends LinearLayoutCompat implements OnLongClickListener {
+ private class TabView extends LinearLayoutCompat {
private final int[] BG_ATTRS = {
android.R.attr.background
};
@@ -511,36 +510,10 @@
if (mIconView != null) {
mIconView.setContentDescription(tab.getContentDescription());
}
-
- if (!hasText && !TextUtils.isEmpty(tab.getContentDescription())) {
- setOnLongClickListener(this);
- } else {
- setOnLongClickListener(null);
- setLongClickable(false);
- }
+ ViewCompat.setTooltip(this, hasText ? null : tab.getContentDescription());
}
}
- @Override
- public boolean onLongClick(View v) {
- final int[] screenPos = new int[2];
- getLocationOnScreen(screenPos);
-
- final Context context = getContext();
- final int width = getWidth();
- final int height = getHeight();
- final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
-
- Toast cheatSheet = Toast.makeText(context, mTab.getContentDescription(),
- Toast.LENGTH_SHORT);
- // Show under the tab
- cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
- (screenPos[0] + width / 2) - screenWidth / 2, height);
-
- cheatSheet.show();
- return true;
- }
-
public ActionBar.Tab getTab() {
return mTab;
}
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
index 150a3fd..ae74f95 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
@@ -22,24 +22,20 @@
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
-import android.graphics.Rect;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.graphics.drawable.DrawableCompat;
-import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.support.v7.mediarouter.R;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.Gravity;
-import android.view.HapticFeedbackConstants;
import android.view.SoundEffectConstants;
import android.view.View;
-import android.widget.Toast;
/**
* The media route button allows the user to select routes and to control the
@@ -95,7 +91,6 @@
private Drawable mRemoteIndicator;
private boolean mRemoteActive;
- private boolean mCheatSheetEnabled;
private boolean mIsConnecting;
private ColorStateList mButtonTint;
@@ -141,7 +136,6 @@
updateContentDescription();
setClickable(true);
- setLongClickable(true);
}
/**
@@ -280,7 +274,8 @@
* button when the button is long pressed.
*/
void setCheatSheetEnabled(boolean enable) {
- mCheatSheetEnabled = enable;
+ ViewCompat.setTooltip(this,
+ enable ? getContext().getString(R.string.mr_button_content_description) : null);
}
@Override
@@ -294,42 +289,6 @@
}
@Override
- public boolean performLongClick() {
- if (super.performLongClick()) {
- return true;
- }
-
- if (!mCheatSheetEnabled) {
- 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;
- final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
-
- Toast cheatSheet = Toast.makeText(context, R.string.mr_button_content_description,
- Toast.LENGTH_SHORT);
- if (midy < displayFrame.height()) {
- // Show along the top; follow action buttons
- cheatSheet.setGravity(Gravity.TOP | GravityCompat.END,
- screenWidth - screenPos[0] - width / 2, height);
- } else {
- // Show along the bottom center
- cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
- }
- cheatSheet.show();
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- return true;
- }
-
- @Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);