大家好,欢迎来到IT知识分享网。
起源
在targetSdkVersion为30的情况下,在Android 11的小米10手机上运行,调用ToastUtil的时候闪退报错:
null cannot be cast to non-null type android.widget.LinearLayout
且看已知条件:
- targetSdkVersion 30
- Android 11
- 小米10
文末附Android 11适配手册
定位问题
ok,遇到问题,迅速定位。我在原有的Toast调用上重新封装了一下,即ToastUtil。
所以很快就定位到问题所在了
private fun createToast(msg: String) { if (toast == null) { toast = Toast.makeText(YUtils.getApp().applicationContext, msg, Toast.LENGTH_SHORT) } else { toast!!.setText(msg) } val linearLayout = toast!!.view as LinearLayout val messageTextView = linearLayout.getChildAt(0) as TextView messageTextView.textSize = 15f toast!!.show() }
没错,就是这句进行了转换:
val linearLayout = toast!!.view as LinearLayout
代码也比较简单,拿到view之后只是设置了一下字体大小。
为什么这么写呢,且看接下来源码分析(非常简单)。
源码解析
我们一般的调用是这么写的:
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
一行代码,也很容易能找到重点——makeText,没错,接下来从这里开始分析
compileSdkVersion 30之前
以compileSdkVersion 28为例,makeText源码:
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; }
这几行的代码重点在哪呢,在这:
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
引用了一个布局来显示信息
这个layout也非常的简单:
<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>
根布局LinearLayout 和TextView显示文本。
所以才有了前面报错的这行代码:
val linearLayout = toast!!.view as LinearLayout
现在看来其实是没有错的,事实上运行在Android11以下也确实没问题。
setView、getView也是没问题的
/ * Set the view to show. * @see #getView */ public void setView(View view) { mNextView = view; } / * Return the view. * @see #setView */ public View getView() { return mNextView; }
author:yechaoa
compileSdkVersion 30之后
重点来了,在compileSdkVersion 30之后,源码是有改动的
还是直接看重点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; return result; } }
嗯?view的获取方式变了,原来是inflate的方式,现在是
View v = ToastPresenter.getTextToastView(context, text);
ok,继续看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); return view; } }
到这里是不是有点熟悉了,没错,跟compileSdkVersion 28中的源码差不多,但是layout变成常量了,且有@VisibleForTesting注解,不过xml代码还是一样的。
而且setView、getView也弃用的
/ * 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; }
直接来看注释的重点:
@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}, appstargeting API level {@link Build.VERSION_CODES#R} or higher that are in the backgroundwill not have custom toast views displayed.
大意:自定义toast view已经弃用,你可以创建一个标准的toast,或者用Snackbar。从Android R开始,将不再显示自定位toast view。
Android R 也就是Android11,具体的版本对应关系查看
这里有同学可能会有一些想法,既然getView弃用了,那我可不可以像系统一样通过ToastPresenter.getTextToastView来获取呢,很遗憾,是不行的,ToastPresenter是@hide。。
适配方案
综上所诉,适配方案也了然于心了。
方案一
使用标准的toast
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
方案二
使用Snackbar
Snackbar的使用跟Toast差不多,更多查看这个。
Snackbar.make(view, "已加入行程", Snackbar.LENGTH_SHORT).show()
方案三
不使用系统的toast,但可以借鉴来写一个自定义view
大致思路:
- 初始化引用自定义布局
- 编写一些公开的set、get属性
- 加上进入进出动画
- 开始/结束显示倒计时
等有空了再来补一下这个。。
Android 11开发手册
《Android 11 开发者手册》
最后
写作不易,如果对你有用,点个赞呗 ^ _ ^
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/91320.html