The origin of

When ToastUtil is invoked on an Android 11 Mi 10 phone with targetSdkVersion of 30, an error appears:

null cannot be cast to non-null type android.widget.LinearLayout
Copy the code

Let’s look at the known conditions:

  • targetSdkVersion 30
  • Android 11
  • Millet 10

The Android 11 adaptation manual is attached at the end of the document

Location problem

Ok, we have a problem. Locate it quickly. I repackaged the old Toast call, ToastUtil.

So the problem was quickly identified

    private fun createToast(msg: String) {
        if (toast == null) {
            toast = Toast.makeText(YUtils.getApp().applicationContext, msg, Toast.LENGTH_SHORT)
        } else{ toast!! .setText(msg) }vallinearLayout = toast!! .viewas LinearLayout
        val messageTextView = linearLayout.getChildAt(0) as TextView
        messageTextView.textSize = 15ftoast!! .show() }Copy the code

That’s right, that’s the translation:

vallinearLayout = toast!! .viewas LinearLayout
Copy the code

The code is pretty simple, you get the view and you just set the font size.

To see why, look at the next source code analysis (very simple).

The source code parsing

Our usual call would look something like this:

Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
Copy the code

One line of code, and it’s easy to get to the point — makeText, yes, and that’s where you start

CompileSdkVersion 30 before

For example, compileSdkVersion 28, the makeText source code:

    public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
            @NonNull CharSequence text, @Duration int duration) {
        Toast result = new Toast(context, looper);

        LayoutInflater inflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
        tv.setText(text);

        result.mNextView = v;
        result.mDuration = duration;

        return result;
    }
Copy the code

What’s the point of these lines of code? Here it is:

View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
Copy the code

A layout is referenced to display the information

The layout is also very simple:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="? android:attr/toastFrameBackground">

    <TextView
        android:id="@android:id/message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layout_marginHorizontal="24dp"
        android:layout_marginVertical="15dp"
        android:layout_gravity="center_horizontal"
        android:textAppearance="@style/TextAppearance.Toast"
        android:textColor="@color/primary_text_default_materiaal_light"/>

</LinearLayout>
Copy the code

The root layout LinearLayout and TextView display text.

That’s where the previous error came in:

vallinearLayout = toast!! .viewas LinearLayout
Copy the code

Now it looks like there’s nothing wrong with it, and in fact it runs on Android11 or below.

SetView and getView are also fine

    /**
     * Set the view to show.
     * @see #getView
     */
    public void setView(View view) {
        mNextView = view;
    }

    /**
     * Return the view.
     * @see #setView
     */
    public View getView() {
        return mNextView;
    }
Copy the code

Author: yechaoa

After compileSdkVersion 30

The point is that after compileSdkVersion 30, the source code has changed

Let’s go straight to the point makeText:

    public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
            @NonNull CharSequence text, @Duration int duration) {
        if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
            Toast result = new Toast(context, looper);
            result.mText = text;
            result.mDuration = duration;
            return result;
        } else {
            Toast result = new Toast(context, looper);
            View v = ToastPresenter.getTextToastView(context, text);
            result.mNextView = v;
            result.mDuration = duration;

            returnresult; }}Copy the code

Huh? View access has changed. View access was inflate, but now it is

View v = ToastPresenter.getTextToastView(context, text);
Copy the code

Ok, continue to see ToastPresenter getTextToastView

public class ToastPresenter {...@VisibleForTesting
    public static final int TEXT_TOAST_LAYOUT = R.layout.transient_notification;

    /**
     * Returns the default text toast view for message {@code text}.
     */
    public static View getTextToastView(Context context, CharSequence text) {
        View view = LayoutInflater.from(context).inflate(TEXT_TOAST_LAYOUT, null);
        TextView textView = view.findViewById(com.android.internal.R.id.message);
        textView.setText(text);
        returnview; }}Copy the code

This is a bit familiar. Yes, it’s the same source code as in compileSdkVersion 28, but the layout is constant and has the @visibleForTesting annotation, but the XML code is the same.

And setView and getView are deprecated

    /**
     * Set the view to show.
     *
     * @see #getView
     * @deprecated Custom toast views are deprecated. Apps can create a standard text toast with the
     *      {@link #makeText(Context, CharSequence, int)} method, or use a
     *      <a href="{@docRoot}reference/com/google/android/material/snackbar/Snackbar">Snackbar</a>
     *      when in the foreground. Starting from Android {@link Build.VERSION_CODES#R}, apps
     *      targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background
     *      will not have custom toast views displayed.
     */
    @Deprecated
    public void setView(View view) {
        mNextView = view;
    }

    /**
     * Return the view.
     *
     * <p>Toasts constructed with {@link #Toast(Context)} that haven't called {@link #setView(View)}
     * with a non-{@code null} view will return {@code null} here.
     *
     * <p>Starting from Android {@link Build.VERSION_CODES#R}, in apps targeting API level {@link
     * Build.VERSION_CODES#R} or higher, toasts constructed with {@link #makeText(Context,
     * CharSequence, int)} or its variants will also return {@code null} here unless they had called
     * {@link #setView(View)} with a non-{@code null} view. If you want to be notified when the
     * toast is shown or hidden, use {@link #addCallback(Callback)}.
     *
     * @see #setView
     * @deprecated Custom toast views are deprecated. Apps can create a standard text toast with the
     *      {@link #makeText(Context, CharSequence, int)} method, or use a
     *      <a href="{@docRoot}reference/com/google/android/material/snackbar/Snackbar">Snackbar</a>
     *      when in the foreground. Starting from Android {@link Build.VERSION_CODES#R}, apps
     *      targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background
     *      will not have custom toast views displayed.
     */
    @Deprecated
    @Nullable public View getView() {
        return mNextView;
    }
Copy the code

Let’s get straight to the point:

@deprecated Custom toast views are deprecated. Apps can create a standard text toast with the

 {@link #makeText(Context, CharSequence, int)} method, or use a <a href="{@docRoot}reference/com/google/android/material/snackbar/Snackbar">Snackbar</a>
 when in the foreground. Starting from Android {@link Build.VERSION_CODES#R}, apps
 targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background
 will not have custom toast views displayed.
Copy the code

Custom Toast view is deprecated, you can create a standard toast, or use Snackbar. Starting with Android R, self-positioned Toast Views will no longer be displayed.

Android R is also Android11, the specific version of the corresponding relationship to view

There are students might have some ideas, since getView abandoned, that I could like system through ToastPresenter getTextToastView to obtain, unfortunately, is not enough, ToastPresenter is @ hide..

Adaptation scheme

To sum up, the adaptation scheme is clear.

Plan a

Use the standard TOAST

Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
Copy the code

Scheme 2

Use the Snackbar

Snackbar is used in much the same way as Toast, check this out more.

Snackbar.make(view, "Added to itinerary", Snackbar.LENGTH_SHORT).show()
Copy the code

Plan 3

Don’t use the system toast, but use it to write a custom view

General idea:

  • Initialize a reference to a custom layout
  • Write some public set and get properties
  • Add in and out animation
  • Start/end display countdown

I’ll fix this when I have time.

Android 11 development manual

Android 11 Developer Manual

The last

Writing is not easy, if it works for you, give it a like ^ _ ^