别人家的 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 分了四个级别:

  1. 错误 红色
  2. 提示 蓝色
  3. 成功 绿色
  4. 警告 黄色

所以首先要定义四种颜色的值:

@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 个参数:

  1. 最高:context、message
  2. 其次:duration(默认为 Toast.LENGTH_SHORT)
  3. 再次:是否有 icon(默认为 true),如果有,icon的资源是啥
  4. 最后:是否有背景色、如果有,背景色是啥,以及文字颜色、

所以每组方法分为了三个方法:

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;}
1 0
原创粉丝点击