1+ package info .papdt .express .helper .support ;
2+
3+ import android .content .Context ;
4+ import android .content .res .Configuration ;
5+ import android .graphics .Rect ;
6+ import android .text .TextUtils ;
7+ import android .view .Gravity ;
8+ import android .view .View ;
9+ import android .widget .TextView ;
10+ import android .widget .Toast ;
11+ import info .papdt .express .helper .R ;
12+
13+ /**
14+ * Helper class for showing cheat sheets (tooltips) for icon-only UI elements on long-press. This is
15+ * already default platform behavior for icon-only {@link android.app.ActionBar} items and tabs.
16+ * This class provides this behavior for any other such UI element.
17+ *
18+ * <p>Based on the original action bar implementation in <a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/com/android/internal/view/menu/ActionMenuItemView.java">
19+ * ActionMenuItemView.java</a>.
20+ */
21+ public class CheatSheet {
22+ /**
23+ * The estimated height of a toast, in dips (density-independent pixels). This is used to
24+ * determine whether or not the toast should appear above or below the UI element.
25+ */
26+ private static final int ESTIMATED_TOAST_HEIGHT_DIPS = 48 ;
27+
28+ /**
29+ * Sets up a cheat sheet (tooltip) for the given view by setting its {@link
30+ * android.view.View.OnLongClickListener}. When the view is long-pressed, a {@link Toast} with
31+ * the view's {@link android.view.View#getContentDescription() content description} will be
32+ * shown either above (default) or below the view (if there isn't room above it).
33+ *
34+ * @param view The view to add a cheat sheet for.
35+ */
36+ public static void setup (View view ) {
37+ view .setOnLongClickListener (new View .OnLongClickListener () {
38+ @ Override
39+ public boolean onLongClick (View view ) {
40+ return showCheatSheet (view , view .getContentDescription ());
41+ }
42+ });
43+ }
44+
45+ /**
46+ * Sets up a cheat sheet (tooltip) for the given view by setting its {@link
47+ * android.view.View.OnLongClickListener}. When the view is long-pressed, a {@link Toast} with
48+ * the given text will be shown either above (default) or below the view (if there isn't room
49+ * above it).
50+ *
51+ * @param view The view to add a cheat sheet for.
52+ * @param textResId The string resource containing the text to show on long-press.
53+ */
54+ public static void setup (View view , final int textResId ) {
55+ view .setOnLongClickListener (new View .OnLongClickListener () {
56+ @ Override
57+ public boolean onLongClick (View view ) {
58+ return showCheatSheet (view , view .getContext ().getString (textResId ));
59+ }
60+ });
61+ }
62+
63+ /**
64+ * Sets up a cheat sheet (tooltip) for the given view by setting its {@link
65+ * android.view.View.OnLongClickListener}. When the view is long-pressed, a {@link Toast} with
66+ * the given text will be shown either above (default) or below the view (if there isn't room
67+ * above it).
68+ *
69+ * @param view The view to add a cheat sheet for.
70+ * @param text The text to show on long-press.
71+ */
72+ public static void setup (View view , final CharSequence text ) {
73+ view .setOnLongClickListener (new View .OnLongClickListener () {
74+ @ Override
75+ public boolean onLongClick (View view ) {
76+ return showCheatSheet (view , text );
77+ }
78+ });
79+ }
80+
81+ /**
82+ * Removes the cheat sheet for the given view by removing the view's {@link
83+ * android.view.View.OnLongClickListener}.
84+ *
85+ * @param view The view whose cheat sheet should be removed.
86+ */
87+ public static void remove (final View view ) {
88+ view .setOnLongClickListener (null );
89+ }
90+
91+ /**
92+ * Internal helper method to show the cheat sheet toast.
93+ */
94+ private static boolean showCheatSheet (View view , CharSequence text ) {
95+ if (TextUtils .isEmpty (text )) {
96+ return false ;
97+ }
98+
99+ final int [] screenPos = new int [2 ]; // origin is device display
100+ final Rect displayFrame = new Rect (); // includes decorations (e.g. status bar)
101+ view .getLocationOnScreen (screenPos );
102+ view .getWindowVisibleDisplayFrame (displayFrame );
103+
104+ final Context context = view .getContext ();
105+ final int viewWidth = view .getWidth ();
106+ final int viewHeight = view .getHeight ();
107+ final int viewCenterX = screenPos [0 ] + viewWidth / 2 ;
108+ final int screenWidth = context .getResources ().getDisplayMetrics ().widthPixels ;
109+ final int estimatedToastHeight = (int ) (ESTIMATED_TOAST_HEIGHT_DIPS
110+ * context .getResources ().getDisplayMetrics ().density );
111+
112+ Toast cheatSheet = Toast .makeText (context , text , Toast .LENGTH_SHORT );
113+ int currentNightMode = context .getResources ().getConfiguration ().uiMode & Configuration .UI_MODE_NIGHT_MASK ;
114+ boolean isNightMode = currentNightMode == Configuration .UI_MODE_NIGHT_YES ;
115+ cheatSheet .getView ().setBackgroundResource (
116+ !isNightMode ?
117+ android .support .v7 .appcompat .R .drawable .tooltip_frame_dark :
118+ android .support .v7 .appcompat .R .drawable .tooltip_frame_light );
119+ TextView textView = cheatSheet .getView ().findViewById (android .R .id .message );
120+ textView .setTextAppearance (context , R .style .TextAppearance_AppCompat_Body1 );
121+ textView .setTextColor (context .getResources ().getColor (R .color .white_in_dark ));
122+ int dp16 = (int ) ScreenUtils .dpToPx (context , 16 );
123+ textView .setPaddingRelative (dp16 , 0 , dp16 , 0 );
124+ boolean showBelow = screenPos [1 ] < estimatedToastHeight ;
125+ if (showBelow ) {
126+ // Show below
127+ // Offsets are after decorations (e.g. status bar) are factored in
128+ cheatSheet .setGravity (Gravity .TOP | Gravity .CENTER_HORIZONTAL ,
129+ viewCenterX - screenWidth / 2 ,
130+ screenPos [1 ] - displayFrame .top + viewHeight );
131+ } else {
132+ // Show above
133+ // Offsets are after decorations (e.g. status bar) are factored in
134+ // NOTE: We can't use Gravity.BOTTOM because when the keyboard is up
135+ // its height isn't factored in.
136+ cheatSheet .setGravity (Gravity .TOP | Gravity .CENTER_HORIZONTAL ,
137+ viewCenterX - screenWidth / 2 ,
138+ screenPos [1 ] - displayFrame .top - estimatedToastHeight );
139+ }
140+
141+ cheatSheet .show ();
142+ return true ;
143+ }
144+
145+ }
0 commit comments