别人家的 Toast——Toasty
来源:互联网 发布:善领专业版数据 编辑:程序博客网 时间:2024/06/11 19:38
前两天,在微博上,看到有人分享了一个 Toast 库,感觉挺好看的,就去看了下源码,然后记了一些笔记,放在这里。
工具类
为 View 设置背景
为 View 设置背景的方法十分固定:
static void setBackground(@NonNull View view, Drawable drawable) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) view.setBackground(drawable); else //noinspection deprecation view.setBackgroundDrawable(drawable);}
API >= 16 时,调用 setBackground() 方法。
setBackground() 方法就是在 API Level 16 引入的,内部实现如下:
public void setBackground(Drawable background) { //noinspection deprecation setBackgroundDrawable(background);}
可以看到,他与 setBackgroundDrawable() 方法完全等价,只是名字变短了。
所以,即使使用 setBackgroundDrawable() 方法,也完全没有问题的。
不过官方既然废弃了名字长的那个,就尽量用名字短的新的吧。
从资源文件获得 Drawable
从资源文件获得 Drawable 的方式非常固定,就是下面这个方法:
static Drawable getDrawable(@NonNull Context context, @DrawableRes int id) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { context.getResources().getDrawable(id, context.getTheme()); return context.getDrawable(id); } else { //noinspection deprecation return context.getResources().getDrawable(id); }}
API Level >= 21 时,调用 context.getDrawable(id)
。
context.getDrawable() 就是在 API Level 21 引入的,内部实现是:
@Nullablepublic final Drawable getDrawable(@DrawableRes int id) { return getResources().getDrawable(id, getTheme());}
所以这个方法等价于,在外部调用 context.getResources().getDrawable(id, context.getTheme());
。
而 getDrawable (int, Resources.Theme)
方法,也是 API 21 引入的。
也因此,API Level < 21 时,只有调用 context.getResources().getDrawable(id);
,没有其他选择。
顺便简单介绍下 Drawable。
Drawable 是一个抽象类,代表那些可以被绘制的东西。它可以把资源文件变成能显示在屏幕上的东西。
这些资源文件不止是 .png、.jpg 这些图片文件,还包括 Vector、Shape、Layers 这些东西。
两篇官方指南:Canvas and Drawables 和 可绘制对象资源。
为 NinePatchDrawable 着色
之前说过,Drawable 是抽象类,其中有一个抽象方法:
public abstract void setColorFilter(@Nullable ColorFilter colorFilter);
这个方法可以为 Drawable 着色。NinePatchDrawable 实现了 Drawable 类,当然也重写了这个方法。
为 NinePatchDrawable 着色的过程很简单,首先调用刚刚的 getDrawale() 方法获得 Drawable 对象。
final Drawable toastDrawable = getDrawable(context, R.drawable.toast_frame);
toast_frame.9.png 是一张 9 patch 图片,所以 getDrawable() 会获得 NinePatchDrawable 对象。
接下来创建 ColorFilter 对象,传入颜色值,再把 ColorFilter 对象传入 setColorFilter() 方法即可。
toastDrawable.setColorFilter(new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN));
工具类的写法
工具类其实没必要创建对象,所以最好写个私有的构造方法,这样就不会在外部被调用了。
工具类也不需要被继承,所以 final 类就行了。
工具类如果不希望被外部调用,连 public 都可以省略。
工具类里的方法如果只想让库自己用,也不需要 public,所以不一定每次都是 public static 的。
最后把前面的工具类贴在这里,方便使用和查找。
final class ToastyUtils { private ToastyUtils() { } static Drawable tint9PatchDrawableFrame(@NonNull Context context, @ColorInt int tintColor) { final Drawable toastDrawable = getDrawable(context, R.drawable.toast_frame); toastDrawable.setColorFilter(new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN)); return toastDrawable; } static void setBackground(@NonNull View view, Drawable drawable) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) view.setBackground(drawable); else //noinspection deprecation view.setBackgroundDrawable(drawable); } static Drawable getDrawable(@NonNull Context context, @DrawableRes int id) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { context.getResources().getDrawable(id, context.getTheme()); return context.getDrawable(id); } else { //noinspection deprecation return context.getResources().getDrawable(id); } }}
Toasty 的设计
方法的层次
为什么要自定义 Toast,默认的 Toast 有什么不好么?
Toasty 的意思是,希望在发生不同的事件时,Toast 能根据事件的级别,给出不同级别的提示。比如成功的提示绿色,警告的提示为黄色,错误的提示为红色。
分级别提示,是 Toasty 的目标。
所以具体是哪些级别?
Toasty 分了四个级别:
- 错误 红色
- 提示 蓝色
- 成功 绿色
- 警告 黄色
所以首先要定义四种颜色的值:
@ColorInt private static final int ERROR_COLOR = Color.parseColor("#D50000");@ColorInt private static final int INFO_COLOR = Color.parseColor("#3F51B5");@ColorInt private static final int SUCCESS_COLOR = Color.parseColor("#388E3C");@ColorInt private static final int WARNING_COLOR = Color.parseColor("#FFA900");
Toasty 的设计是,背景色随着级别而改变,而默认的文字色一直是白色:
@ColorInt private static final int DEFAULT_TEXT_COLOR = Color.parseColor("#FFFFFF");
Toasty 是允许自定义其他样式的,所以构造 Toast 时,除了每次都传的 context、message、duration,还要传入文字颜色和背景色。
Toasty 的通知是由文字和图标的,所以需要决定是否显示图标以及图标的资源。
由于 Toasty 也算是个工具类,所以构造方法 private 就行了。
接下来就是方法层次的设计了。
对应于四个级别,每个级别都应该有一组方法,而且结构应该是完全一样的,所以我们只需要思考其中的一组是如何设计即可。比如考虑错误时的提示。
从使用的角度来说,我们希望传入的参数尽量少,而最少也要有 context 和 message 这两个参数,duration 其实不同每次都传,默认为 Toast.LENGTH_SHORT 即可。
所以,我们需要为参数分一下级别,目前为止,有 8 个参数:
- 最高:context、message
- 其次:duration(默认为 Toast.LENGTH_SHORT)
- 再次:是否有 icon(默认为 true),如果有,icon的资源是啥
- 最后:是否有背景色、如果有,背景色是啥,以及文字颜色、
所以每组方法分为了三个方法:
public static @CheckResult Toast error(@NonNull Context context, @NonNull String message) { return error(context, message, Toast.LENGTH_SHORT, true);}public static @CheckResult Toast error(@NonNull Context context, @NonNull String message, int duration) { return error(context, message, duration, true);}public static @CheckResult Toast error(@NonNull Context context, @NonNull String message, int duration, boolean withIcon) { return custom(context, message, duration, withIcon, ToastyUtils.getDrawable(context, R.drawable.ic_clear_white_48dp), true,ERROR_COLOR, DEFAULT_TEXT_COLOR);}
这里默认 duration 为 Toast.LENGTH_SHORT,第四个参数 true,表示需要带图标。
这三个方法基本已经概括了 Toast 大部分的使用场景:分级别的文字、控制时长、控制是否显示 icon。
其他三组方法也是一样的,就不都贴过来了。
另外,为了方便用户设置自己的等级,再加一组 normal 方法。
//只想显示短时间的文字public static @CheckResult Toast normal(@NonNull Context context, @NonNull String message) { return normal(context, message, Toast.LENGTH_SHORT, false, null);}//想显示长时间的文字public static @CheckResult Toast normal(@NonNull Context context, @NonNull String message, int duration) { return normal(context, message, duration, false, null);}//想显示短时间的文字和iconpublic static @CheckResult Toast normal(@NonNull Context context, @NonNull String message, Drawable icon) { return normal(context, message, Toast.LENGTH_SHORT, true, icon);}//想显示长时间的文字和iconpublic static @CheckResult Toast normal(@NonNull Context context, @NonNull String message, int duration, Drawable icon) { return normal(context, message, duration, true, icon);}private static @CheckResult Toast normal(@NonNull Context context, @NonNull String message, int duration, boolean withIcon, Drawable icon) { return custom(context, message, duration, withIcon, icon, false, -1, DEFAULT_TEXT_COLOR);}
更全面的控制:
//想显示长时间的文字、icon、并控制背景色public static @CheckResult Toast custom(@NonNull Context context, @NonNull String message, int duration, Drawable icon, @ColorInt int tintColor) { return custom(context, message, duration, true, icon, true, tintColor, DEFAULT_TEXT_COLOR);}//想显示长时间的文字、icon、并控制背景色和文字颜色public static @CheckResult Toast custom(@NonNull Context context, @NonNull String message, int duration, Drawable icon, @ColorInt int tintColor, @ColorInt int textColor) { return custom(context, message, duration, true, icon, true, tintColor, textColor);}
构造过程
我们使用原生 Toast 时,都是直接一行代码:
Toast.makeText(context,message,Toast.LENGTH_SHORT).show();
而要自定义 Toast,实际上只需要自定义 makeText() 里的逻辑。显示 Toast 直接用原来的 show() 方法就行了。
先看下 makeText() 是怎么构造 Toast 的:
public static Toast makeText(Context context, CharSequence text, @Duration int duration) { Toast result = new Toast(context); 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;}
首先,用 context 构造了一个 Toast 对象。
接下来,处理界面。
获得 LayoutInflater 对象,用它来 inflate 布局。
再得到布局内部的 TextView,把输入的文字设置在 TextView 上。
然后,把 Toast 的 root View 和传入的时间赋值给 Toast 内部的两个成员变量。
最后返回创建的 Toast 对象。
整个构造过程就是这样,非常简单。
现在,只需要根据刚刚的步骤执行就行了:
public static @CheckResult Toast custom(@NonNull Context context, @NonNull String message, int duration, boolean withIcon, Drawable icon, boolean shouldTint, @ColorInt int tintColor, @ColorInt int textColor) { final Toast currentToast = new Toast(context); final View toastLayout = ((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate( R.layout.toast_layout, null); final ImageView toastIcon = (ImageView) toastLayout.findViewById(R.id.toast_icon); final TextView toastTextView = (TextView) toastLayout.findViewById(R.id.toast_text); Drawable drawableFrame; if (shouldTint) { drawableFrame = ToastyUtils.tint9PatchDrawableFrame(context, tintColor); } else { drawableFrame = ToastyUtils.getDrawable(context, R.drawable.toast_frame); } ToastyUtils.setBackground(toastLayout, drawableFrame); if (withIcon) { if (icon == null) { throw new IllegalArgumentException( "Avoid passing 'icon' as null if 'withIcon' is set to true"); } ToastyUtils.setBackground(toastIcon, icon); } else { toastIcon.setVisibility(View.GONE); } toastTextView.setTextColor(textColor); toastTextView.setText(message); toastTextView.setTypeface(Typeface.create(TOAST_TYPEFACE, Typeface.NORMAL)); currentToast.setView(toastLayout); currentToast.setDuration(duration); return currentToast;}
- 别人家的 Toast——Toasty
- 别人家的孩子
- 别人家的孩子
- 别人家的孩子
- 别人家的代码
- 看看别人家的孩子
- 别人家的自定义listview
- 别人家的面试题
- 别人家的reset.less
- 20170302 别人家的代码
- 别人家的代码审查
- 别人家的kalman 一
- 别人家的kalman 二
- 第三方开源库:Toast工具:Toasty
- 教你实现别人家的动画
- 别人家的oracle utl_smtp 发送邮件
- Gradle 加载别人家的项目
- 好东西 别人家的代码模板
- BZOJ 1455: 罗马游戏 左偏树 or pb_ds
- VS Code折腾记 - (3) 多图解VSCode基础功能
- Linux下的网络环境配置
- 问题 B: 加油站(贪心+模拟)
- JZOJ4957. 【WC模拟】B君的宴请
- 别人家的 Toast——Toasty
- 01.19读写三行文件编译
- Mysql单文件存储删除数据Bug文件容量不会减少
- cross tool制作交叉编译工具链,编译binutils error
- uCos的多任务实现
- POJ 2184 Cow Exhibition(01背包变形)
- 【WC模拟】Equation
- 【UOJ】#242. 【UR #16】破坏蛋糕
- 微信公众号平台的泛泛之谈