Android Custom Toast
Published: 2021-07-20 03:27:21

`Toast` is an easy and convenient way to display a short message to user. Its implementation is as short as one line of code as shown below:

```kotlin
Toast.makeText(this, "Toast Message", Toast.LENGTH_SHORT).show()
```

However, did you ever explore other features other than simply showing up a sentence? Developer can actually customise `Toast` by setting a custom layout and define the location within the app window. Furthermore, there is a trick on vision accessibility handling (**Talkback**)!

This article will cover totally four functions to the following attributes of `Toast`:

1. Custom layout
2. Duration
3. Position with offset
4. Horizontal and vertical margins

*In Android 11 Preview 2, there are new updates on customising `Toast` and the presentation limitation. You can understand more here:
{% medium https://itnext.io/android-11-toast-updates-7f1cd2245bc4 %}

# Property 1: Custom Layout
```kotlin
<?xml version="1.0" encoding="utf-8"?>
<layout>
  <TextView
    ...
    android:id="@+id/titleTextView"
    android:drawableStart="@drawable/correct"
    android:text="This is the message"
    android:textStyle="bold"/>
</layout>
```

```kotlin
class CustomToast(context: Context) : Toast(context) {
  init {
    // Inflate <View> By LayoutInflater
    val view = LayoutInflater.from(context).inflate(R.layout.widget_toast, null)
    
    // Inflate <View> By DataBindingUtil
    val view = DataBindingUtil.inflate<WidgetToastBinding>(LayoutInflater.from(context), R.layout.widget_toast, null, false)
    
    // Set custom view
    setView(view)
  }
}
```

`setView(view: View)` can be used to set a custom view of a `Toast`. Custom layout can be inflated by either:
1. `LayoutInflater`
2. `DataBindingUtil` (if you are using DataBinding)

# Property 2: Duration (2.0s Vs 3.5s)
![Duration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v9w9se1q8slg4vw4dznq.gif)
```kotlin
class CustomToast(context: Context) : Toast(context) {
  init {
    // ... 
    duration = Toast.LENGTH_LONG // 3.5s (3500ms)
    duration = Toast.LENGTH_SHORT // 2.0s (2000ms)
  }
}
{"mode":"full","isActive":false}
```
Duration of `Toast` is limited to either `LENGTH_SHORT` and `LENGTH_LONG`.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/742z0zi4o0b6jux3pfws.png)
 
Line 70–73 of Toast source code

Although there is no documentation about the definition of the above two choices, the [NotificationManagerService source code](https://android.googlesource.com/platform/frameworks/base/+/f76a50c/services/java/com/android/server/NotificationManagerService.java) indicates the length clearly. (Thank you for [StackOverflow information](https://stackoverflow.com/questions/2220560/can-an-android-toast-be-longer-than-toast-length-long/5079536#5079536))

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0jyoot79o1ti8pr1d9ot.png)
 
Line 75–78 of NotificationManagerService source code


# Property 3: Gravity + Offset
![1_vDd70OKmkZwVSEXEyuUThA](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mc0sd2x63eclp6jylncp.gif)
 
`Gravity` can be imagined as a force pushing a views towards that direction. It is in `Integer` type and can be set to be any bitwise combination of the following 7 constants:

1. `Gravity.START` / `Gravity.LEFT`
2. `Gravity.END` / `Gravity.RIGHT`
3. `Gravity.TOP`
4. `Gravity.BOTTOM`
5. `Gravity.CENTER`
6. `Gravity.CENTER_VERTICAL`
7. `Gravity.CENTER_HORIZONTAL`

Example codes for showing at top left corner:
```kotlin
class CustomToast(context: Context) : Toast(context) {
  init {
    // ... 
    val gravity = Gravity.START or Gravity.TOP
    val horizontalOffset = 0
    val verticalOffset = 0
    setGravity(gravity, horizontalOffset, verticalOffset)
  }
}
```
Here is a full list of mapping between `Toast` position and the corresponding `gravity` bitwise combination:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cq32a1adred3bar9sz5u.png)
 
So, what is the meaning of the `horizontalOffset` and `verticalOffset` used in the setGravity function?
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/opw7gzz8b2ku24h1bvss.png)
 
illustration of the coordinate system in Android

In Android, the origin of coordinates system is always the top left corner. Horizontal axis is represented by `x` value which increases from left to right. Vertical axis is represented by `y` value which increases from top to bottom.

Thus, these values can be set to move the Toast from its original position. See below as an example:

![1_vSLwjzPkWq3jueuMVPZnaw](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6t3x4jzc13ag3babq7s7.gif)
 
illustration of the horizontal and vertical offset in Toast

# Property 4: Margins
```kotlin
class CustomToast(context: Context) : Toast(context) {
  init {
    val horizontalMarginPercentage = 0.1f
    val verticalMarginPercentage = 0.2f
    setMargin(horizontalMarginPercentage, verticalMarginPercentage)
  }
}
```

At first glance, it is easy to overlook at the unit of the parameters used in the `setMargin` function. Unlike the [ViewGroup.MarginLayoutParams](https://developer.android.com/reference/android/view/ViewGroup.MarginLayoutParams.html#setMargins(int,%20int,%20int,%20int)) , it does not uses pixel as the unit. It actually represents the percentage of the container width or height.

`setMargin` shows a similar behaviour as the horizontal and vertical offset used in the `setGravity` function shown above. However, it applies on bi-direction, i.e. both top and bottom / both left and right.

The following extension functions can help you a lot on calculating the correct percentage:
```kotlin
// Conversion from dp to px
fun Int.toPx(): Int {
  val metrics = Resources.getSystem().displayMetrics
  val px = this * (metrics.densityDpi / 160f)
  return Math.round(px)
}

// Get screen dimension
fun Context.screenDimension(): Point {
  val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager // Either getting WindowManager from Activity or Application context
  val display = windowManager.getDefaultDisplay()
  val size = Point()
  display.getSize(size)
  return size
}
fun Context.screenWidth(): Int = screenDimension().x
fun Context.screenHeight(): Int = screenDimension().y
```
Usage:
```kotlin
// Calculate percentage of horizontal margin
val horizontalMarginInDp = 16
val horizontalMarginPercentage = horizontalMarginInDp.toPx() / screenWidth()

// Calculate percentage of vertical margin
val verticalMarginInDp = 16
val verticalMarginPercentage = verticalMarginInDp.toPx() / screenHeight()

setMargin(horizontalMarginPercentage, verticalMarginPercentage)
{"mode":"full","isActive":false}
```

# Vision accessibility issue
![1_MXu8QblSUb-Y-8Q6DSpJ_Q](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jvsrqvq64t8lujk35jbp.gif)

According to the Google official documentation, `Toast` cannot be focused by any means. In other words, visually impaired cannot be notified about the `Toast` message by Android TalkBack Accessibility Tool, which is also named as “Voice Assistant” in some devices.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7wvp55lm2e2zhfd2ntb4.png)
 
To learn more about **Talkback**, please read the [this Android official documentation](https://support.google.com/accessibility/android/answer/6007100?hl=en-GB).


# Limitation of Toast
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ue4vycydj6af3dwyvdzy.png)
 
`Toast` is a super simple widget and there are no listeners to notify developer about its state. For example, unlike `DialogFragment`, `Toast` does not have a `onCancelListener()` to notify when `Toast` is faded out from the screen. Below is the `onCancelListener` documentation of `DialogInterface` that is used in `DialogFragment`.
[Link to the Android official documentation of 
DialogInterface.OnCancelListener](https://developer.android.com/reference/android/content/DialogInterface.OnCancelListener)



# Conclusion
Toast has many features other than simply displaying a message.

**Toast can be:**
1. Shown with a custom layout, for example with an `ImageView`
2. Shown with two different durations, i.e. 2.0s and 3.5s
3. Shown with a custom position at the screen with a combination of its `gravity` and `x-y offset`
4. Shown with bi-direction margins

**Toast cannot :**
1. Be focused or clicked. This makes a huge inconvenience to visually impaired.
2. Notify application about its current status, e.g. the callback of dismiss



# Further readings:
1. Android 11 Toast Updates
{% medium https://itnext.io/android-11-toast-updates-7f1cd2245bc4 %}
2. [Toast class overview](https://developer.android.com/guide/topics/ui/notifiers/toasts)
3. [Toast function and attribute definitions](https://developer.android.com/reference/android/widget/Toast.html)
4. [Toast open source code](https://android.googlesource.com/platform/frameworks/base/+/f4bed684c939b0f8809ef404b8609fe4ef849263/core/java/android/widget/Toast.java)

You are welcome to follow me at [Twitter@myrick_chow](https://twitter.com/myrick_chow) and [Medium@myrickchow](https://myrickchow.medium.com/) for more information. Thank you for reading this article. Have a nice day! 😄