3

Background

Long pressing an action-item of a Toolbar, you get a temporary Toast-like message, telling you what it is:

enter image description here

It's floating above the rest of the UI, but gets removed automatically after a very short time, and it seems that if you try to press it, it gets removed and the touch goes to what was behind it.

The problem

I wanted to have something similar to this but for other Views, when I long press them, so I used this tip of how to position a simple Toast near a View:

https://stackoverflow.com/a/21026866/878126

@SuppressLint("RtlHardcoded")
fun positionToast(toast: Toast, view: View, activity: Activity, offsetX: Int, offsetY: Int) {
    val toastView = toast.view ?: return
    val rect = Rect()
    val window = activity.window
    window.decorView.getWindowVisibleDisplayFrame(rect)
    val viewLocation = IntArray(2)
    view.getLocationInWindow(viewLocation)
    val viewLeft = viewLocation[0] - rect.left
    val viewTop = viewLocation[1] - rect.top
    val windowMetricsSize = Utils.getWindowMetricsSize(activity)
    val widthMeasureSpec = MeasureSpec.makeMeasureSpec(windowMetricsSize.x, MeasureSpec.UNSPECIFIED)
    val heightMeasureSpec = MeasureSpec.makeMeasureSpec(windowMetricsSize.y, MeasureSpec.UNSPECIFIED)
    toastView.measure(widthMeasureSpec, heightMeasureSpec)
    val toastWidth = toastView.measuredWidth
    val toastHeight = toastView.measuredHeight
    val toastX = viewLeft + (view.width - toastWidth) / 2 + offsetX
    val toastY = viewTop + (view.height - toastHeight) / 2 + offsetY
    toast.setGravity(Gravity.LEFT or Gravity.TOP, toastX, toastY)
}

It works by measuring the Toast's view and then decide how to put it near the given View.

This worked perfectly fine for all Android versions, but sadly once you target Android R (API 31 - AKA Android 11), it won't work for you, and the reason is this:

This method was deprecated in API level 30. Custom toast views are deprecated. Apps can create a standard text toast with the makeText(android.content.Context, java.lang.CharSequence, int) method, or use a Snackbar when in the foreground. Starting from Android Build.VERSION_CODES#R, apps targeting API level Build.VERSION_CODES#R or higher that are in the background will not have custom toast views displayed.

This method was deprecated in API level 30. Custom toast views are deprecated. Apps can create a standard text toast with the makeText(android.content.Context, java.lang.CharSequence, int) method, or use a Snackbar when in the foreground. Starting from Android Build.VERSION_CODES#R, apps targeting API level Build.VERSION_CODES#R or higher that are in the background will not have custom toast views displayed.

Meaning you can't get the View of the Toast to measure its size and then decide how to position it nicely, and you can't even set a custom View for it, so that then you could measure it:

What I've tried

I've noticed that even though those restrictions do apply to Toast, they don't for the Toolbar's action items, and that's because it's probably not really a toast. Maybe it was never a toast, even on old Android versions.

I tried to analyze the layout of the Toast-like View when long pressing the Toolbar action item (tried with Toast too but failed for some reason), to search more about it. The Layout Analyzer tool is a bit buggy, but I got that this message View is using 2 layout files:

C:/android/Android Studio Beta/plugins/android/lib/layoutlib/data/framework_res.jar!/res/layout/screen_simple_overlay_action_mode.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
</FrameLayout>

And it uses this:

C:/Users/user/AppData/Local/Android/Sdk/platforms/android-30/data/res/layout/action_mode_bar.xml

<com.android.internal.widget.ActionBarContextView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:visibility="gone"
     style="?android:attr/actionModeStyle" />

But, this is just the layout files. I want to know how to also make it have the same behavior, of being on top temporarily, and if you try to touch it, it should be gone and you actually press what's behind.

So I tried to search how it's being used. I found 2 cases when searching for "screen_simple_overlay_action_mode" :

  • One is in createSubDecor function somewhere in AppCompatDelegateImpl class, but sadly it's not public and it's very hard to understand what's going on. Plus it's not quite the same layout file. It's abc_screen_simple_overlay_action_mode instead.

  • Another is in PhoneWindow class inside a long function generateLayout, and it's even harder to understand there what's going on. The class itself is quite long (almost 4000 lines of code).

The question

How can I mimic the same Toast-like View on other places, just as it's used for Toolbar's action items?

Is there any known way to do it out-of-the-box? Whether inside the android-x library, or somewhere else?

android developer
  • 114,585
  • 152
  • 739
  • 1,270

0 Answers0