Android Toast 在项目中是普遍应用的一个控件,应该也是一个使用非常简单的控件。这里,有人会问,Toast 这么简单干嘛还要花时间去学习呢,项目里直接 Toast.makeText 就可以了啊。我的回答是:“因为简单,所以需要去学习,去了解”。
为什么我还要去做一个自定义Toast?
Android Toast 如果在某个场景交互中,一不小心多次触发了 Toast show 结果是不停地在你屏幕显示同样的 Toast 信息。我是个有时间洁癖的人,不喜欢因为你多显示一次 Toast 信息浪费我一秒钟或两秒钟,在碎片时间学习中,我更希望花一秒钟看到我更喜欢看到的知识,集中精力去得到我想要的东西。
废话少扯,进入主题。
先看看Android 系统 Toast 的实现源码:
Toast MakeText:
/**
* Make a standard toast that just contains a text view.
*
* @param context The context to use. Usually your {@link android.app.Application}
* or {@link android.app.Activity} object.
* @param text The text to show. Can be formatted text.
* @param duration How long to display the message. Either {@link #LENGTH_SHORT} or
* {@link #LENGTH_LONG}
*
*/
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
return makeText(context, null, text, duration);
}
/**
* Make a standard toast to display using the specified looper.
* If looper is null, Looper.myLooper() is used.
* @hide
*/
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;
}
再看看Toast 布局:
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/layout/transient_notification.xml
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<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_gravity="center_horizontal"
android:textAppearance="@style/TextAppearance.Toast"
android:textColor="@color/bright_foreground_dark"
android:shadowColor="#BB000000"
android:shadowRadius="2.75"
/>
</LinearLayout>
Toast show 和 cancel 方法:
/**
* Show the view for the specified duration.
*/
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
/**
* Close the view if it's showing, or don't show it if it isn't showing yet.
* You do not normally have to call this. Normally view will disappear on its own
* after the appropriate duration.
*/
public void cancel() {
mTN.cancel();
}
通过简单查看Toast 源码得知一些信息
- Toast 实现没有对同一条消息显示进行过滤,show 方法调用通知服务 enqueueToast 显示信息。
- Toast 布局很简单,就一个LinearLayout 的圆角布局和显示信息的阴影效果。
android:background="?android:attr/toastFrameBackground"
android:shadowColor="#BB000000"
android:shadowRadius="2.75"
显然,系统的Toast 不满足我的需求,我需要一个自定义一个能够过滤相同信息的 Toast 。
自定义Toast 主要思想:
- 创建对应构造器。采用设计模式 Builder 模式构造一个需要的Toast。主要针对有个性化 Toast 需求提供,如自定义背景颜色,自定义字体颜色,自定义 Toast 背景布局等。
- 在Toast 上一次显示后没有 cancel 前,界面某个位置多次触发,只显示同样的Toast 信息一次。取显示信息对应的 hashCode作为key,缓存对应的Toast 信息,如果我每次传入的信息都是同样的则不做显示,Handler 控制对应的显示时间,即使取消 Toast 显示,清除上次Toast 显示信息。
- Toast 背景圆角问题的一些思考。Android Toast 圆角布局背景调用的Android 系统 xml 布局,如果动态修改 View 布局颜色会丢失 圆角。当然,采用简单的自定义一个圆角布局就可以解决问题的,具体圆角布局代码可参照代码里 RoundLinearLayout.java,具体使用方式太简单了,这里不做细说。
自定义相关代码展示:
private void initCustomToast(CustomToastBuilder builder) {
runnable = RUNNABLES.get(text.hashCode());
if (isNull(runnable)) {
customToast = new CustomToast(mContext);
runnable = new ToastRunnable(customToast);
RUNNABLES.put(text.hashCode(), runnable);
long showTime = builder.showTime;
if (showTime > DEFAULT_SHOW_TIME){
mHander.postDelayed(runnable, showTime);
}else {
mHander.postDelayed(runnable, DEFAULT_SHOW_TIME);
}
customToast.show(builder.customToastCreator,builder.text,builder.textResId);
}
}
public void show(CharSequence text) {
if (!text.equals(TEXTS.get(text.hashCode()))) {
TEXTS.put(text.hashCode(), text);
systemToast = new SystemToast(mContext);
runnable = new ToastRunnable(systemToast);
mHander.postDelayed(runnable, DEFAULT_SHOW_TIME);
systemToast.show(text);
}
}
private class ToastRunnable implements Runnable {
private BaseToast toast;
public ToastRunnable(BaseToast toast) {
this.toast = toast;
}
@Override
public void run() {
toast.cancel();
TEXTS.remove(toast.getText().hashCode());
RUNNABLES.remove(toast.getText().hashCode());
toast = null;
runnable = null;
}
}
圆角布局:
@SuppressLint("WrongConstant")
private void drawRoundDrawable() {
Log.d(TAG, "drawRoundDrawable: "+cornesRadius);
if (null == gradientDrawable) {
return;
}
if (cornesRadius != 0) {
gradientDrawable.setCornerRadius(cornesRadius);
gradientDrawable.setGradientType(GradientDrawable.RECTANGLE);
} else if (null != radii) {
gradientDrawable.setCornerRadii(radii);
gradientDrawable.setGradientType(GradientDrawable.RECTANGLE);
}
setBackgroundDrawable();
}
自定义 Toast 如何使用,代码(Kotlin)如下:
1. 最简单的使用
JToast.getInstance(this).show("show 一个!")
2. 构造器的使用
var creator = SystemToastCreator.build()
.shadowColor(Color.parseColor("#2F4F4F"))
.shadowRadius(15f)
.setTextColor(Color.parseColor("#ffffff"))
.creator()
JToast.build()
.systemToastBuilder()
.setToastCreator(creator)
.setShowTime(2000)
.setText("show creator!")
.show(this)
var customCreator = CustomToastCreator.build()
.setCustomView(R.layout.layout_toast)
.setTextColor(Color.WHITE)
.setBackgroundRound(true)
.creator()
JToast.build()
.customToastBuilder()
.setToastCreator(customCreator)
.setShowTime(2000)
.setText("show customCreator!",R.id.message)
.show(this)
我自定义的代码都太简单了,这里就不太啰嗦了。不懂的也随时可以联系我。
总结: 我总感觉我的实现方式不太友好,并不完善,欢迎各位评论区提出你们宝贵的建议,或直接GitHub 把你们的想法提交。非常感谢各位大神的批评指正!