Toast拓展–自定义显示时间和动画

我们在Android应用开发中经常会需要在界面上弹出一个对界面操作无影响的小提示框来提示用户一些信息,这时候一般都会使用Android原生的Toast类

Toast.makeText(mContext, "消息内容", Toast.LENGTH_SHORT).show();

一开始觉得,挺好用的,就有点什么消息都用Toast显示了。
但是用久了就发现,Toast的显示和消失动画不符合自己的要求,显示时间也只有SHORT和LONG两种选择,好像不太够用。


于是,在阅读了Toast的源码后对Toast进行了拓展,原生Toast包含了以下方法给用户修改显示内容:

setView(View):void
setDuration(int):void
setMargin(float,float):void
setGravity(int,int,int):void
setText(int):void
setText(CharSequence):void

分别是直接替换视图、设置显示时长、设置边距属性、设置显示位置、设置显示文字内容。

基于原有的Toast上对其进行拓展,修改及增加以下两个方法:

setDuration(int):void
setAnimations(int):void

设置显示时长方法拓展为可以自定义显示时间,参数单位秒,提供三个默认值:LENGTH_SHORT,LENGTH_LONG,LENGTH_ALWAYS,分别对应原生Toast的LENGTH_SHORT,LENGTH_LONG,以及总是显示。要注意的是总是显示需要在合适的时候自己调用hide()方法隐藏,否则会影响其他窗口的正常显示。

下图是使用自定义动画和自定义显示时间的Toast示例


废话不多说,先上工具类源码跟example:

ExToast.java

import android.content.Context;
import android.content.res.Resources;
import android.os.Handler;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;/*** Created by kj on 16-06-32.*/
public class ExToast {private static final String TAG = "ExToast";public static final int LENGTH_ALWAYS = 0;public static final int LENGTH_SHORT = 2;public static final int LENGTH_LONG = 4;private Toast toast;private Context mContext;private int mDuration = LENGTH_SHORT;private int animations = -1;private boolean isShow = false;private Object mTN;private Method show;private Method hide;private Handler handler = new Handler();public ExToast(Context context){this.mContext = context;if (toast == null) {toast = new Toast(mContext);}}private Runnable hideRunnable = new Runnable() {@Overridepublic void run() {hide();}};/*** Show the view for the specified duration.*/public void show(){if (isShow) return;initTN();try {show.invoke(mTN);} catch (InvocationTargetException | IllegalAccessException e) {e.printStackTrace();}isShow = true;//判断duration,如果大于#LENGTH_ALWAYS 则设置消失时间if (mDuration > LENGTH_ALWAYS) {handler.postDelayed(hideRunnable, mDuration * 1000);}}/*** 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 hide(){if(!isShow) return;try {hide.invoke(mTN);} catch (InvocationTargetException | IllegalAccessException e) {e.printStackTrace();}isShow = false;}public void setView(View view) {toast.setView(view);}public View getView() {return toast.getView();}/*** Set how long to show the view for.* @see #LENGTH_SHORT* @see #LENGTH_LONG* @see #LENGTH_ALWAYS*/public void setDuration(int duration) {mDuration = duration;}public int getDuration() {return mDuration;}public void setMargin(float horizontalMargin, float verticalMargin) {toast.setMargin(horizontalMargin,verticalMargin);}public float getHorizontalMargin() {return toast.getHorizontalMargin();}public float getVerticalMargin() {return toast.getVerticalMargin();}public void setGravity(int gravity, int xOffset, int yOffset) {toast.setGravity(gravity,xOffset,yOffset);}public int getGravity() {return toast.getGravity();}public int getXOffset() {return toast.getXOffset();}public int getYOffset() {return toast.getYOffset();}public static ExToast makeText(Context context, CharSequence text, int duration) {Toast toast = Toast.makeText(context,text,Toast.LENGTH_SHORT);ExToast exToast = new ExToast(context);exToast.toast = toast;exToast.mDuration = duration;return exToast;}public static ExToast makeText(Context context, int resId, int duration)throws Resources.NotFoundException {return makeText(context, context.getResources().getText(resId), duration);}public void setText(int resId) {setText(mContext.getText(resId));}public void setText(CharSequence s) {toast.setText(s);}public int getAnimations() {return animations;}public void setAnimations(int animations) {this.animations = animations;}private void initTN() {try {Field tnField = toast.getClass().getDeclaredField("mTN");tnField.setAccessible(true);mTN = tnField.get(toast);show = mTN.getClass().getMethod("show");hide = mTN.getClass().getMethod("hide");/**设置动画*/if (animations != -1) {Field tnParamsField = mTN.getClass().getDeclaredField("mParams");tnParamsField.setAccessible(true);WindowManager.LayoutParams params = (WindowManager.LayoutParams) tnParamsField.get(mTN);params.windowAnimations = animations;}/**调用tn.show()之前一定要先设置mNextView*/Field tnNextViewField = mTN.getClass().getDeclaredField("mNextView");tnNextViewField.setAccessible(true);tnNextViewField.set(mTN, toast.getView());} catch (Exception e) {e.printStackTrace();}}}

ExToast example

ExToast exToast = ExToast.makeText(context,"message",ExToast.LENGTH_ALWAYS);
exToast.setAnimations(R.style.anim_view);
exToast.show();
//使用LENGTH_ALWAYS注意在合适的时候调用hide()
exToast.hide();
//显示5秒的Toast
ExToast exToast = ExToast.makeText(context,"message",5);
exToast.show();

上面的代码可以实现自定义xml窗口动画,以及长时间显示Toast的功能。
下面看一下R.style.anim_view的内容,窗口动画可以通过@android:windowEnterAnimation@android:windowExitAnimation定义窗口进场及退场效果

style.xml(放置在res/values/style.xml文件)

<style name="anim_view"><item name="@android:windowEnterAnimation">@anim/anim_in</item><item name="@android:windowExitAnimation">@anim/anim_out</item>
</style>

anim_in.xml(放置在res/anim目录下)

<set xmlns:android="http://schemas.android.com/apk/res/android"><translate
        android:fromXDelta="0"android:fromYDelta="0"android:toXDelta="0"android:toYDelta="85"android:duration="1"/><translate
        android:fromXDelta="0"android:fromYDelta="0"android:toXDelta="0"android:toYDelta="-105"android:duration="350"android:fillAfter="true"android:interpolator="@android:anim/decelerate_interpolator"/><alpha
        android:fromAlpha="0"android:toAlpha="1"android:duration="100"/><translate
        android:fromXDelta="0"android:fromYDelta="0"android:toXDelta="0"android:toYDelta="20"android:duration="80"android:fillAfter="true"android:startOffset="350"/>
</set>

anim_out.xml(放置在res/anim目录下)

<set xmlns:android="http://schemas.android.com/apk/res/android"><alpha
        android:fromAlpha="1"android:toAlpha="0"android:duration="800"/>
</set>

以上动画是模仿小米Toast弹出动画的示例,具体动画可以根据个人喜好自定义。

拓展Toast的工具类及使用方式已经介绍完毕,下面的内容是对于该工具类的设计原理解析,不赶时间并且有兴趣的同学可以继续往下看。


ExToast原理解析

刚才讲到,Toast的使用,有很多限制,其中包括系统原生的Toast是呈队列显示出来的,必须要等到前一条Toast消失才会显示下一条。

相信很多同学都遇到过这个问题,比如我做一个按钮,点击的时候显示一个toast,然后做了个小小的压力测试:狂按保存按钮!于是toast队列排了好长一条,一直在显示,等到一两分钟才结束。

通过阅读Toast源码,可以看到里面的Toast.show()和Toast.cancel()方法:

public void show() {if (mNextView == null) {throw new RuntimeException("setView must have been called");}INotificationManager service = getService();String pkg = mContext.getPackageName();TN tn = mTN;tn.mNextView = mNextView;try {service.enqueueToast(pkg, tn, mDuration);} catch (RemoteException e) {// Empty}
}public void cancel() {mTN.hide();try {getService().cancelToast(mContext.getPackageName(), mTN);} catch (RemoteException e) {// Empty}
}

可以看到Toast的核心显示和隐藏是封装在INotificationManagerenqueueToast方法中,看到enqueue这个词就知道这是一个队列处理的函数,它的参数分别是packageName,tn对象,持续时间。结合Toast的显示效果我们可以猜测这个方法内部实现是队列显示和隐藏每一个传入的Toast。packageName和持续时间我们都很清楚是什么,剩下的重点就在这个tn对象上了。那tn对象到底是什么?

继续阅读Toast源码,可以知道Toast其实是系统虚浮窗的一种具体表现形式,它的核心在于它的一个私有静态内部类class TN,它处理了Toast的显示以及隐藏。所以,我们可以通过反射获取这个TN对象,主动处理Toast的显示和隐藏,而不经过系统Service

TN类源码:

private static class TN extends ITransientNotification.Stub {final Runnable mShow = new Runnable() {@Overridepublic void run() {handleShow();}};final Runnable mHide = new Runnable() {@Overridepublic void run() {handleHide();// Don't do this in handleHide() because it is also invoked by handleShow()mNextView = null;}};...final Handler mHandler = new Handler();...View mView;View mNextView;WindowManager mWM;TN() {final WindowManager.LayoutParams params = mParams;params.height = WindowManager.LayoutParams.WRAP_CONTENT;params.width = WindowManager.LayoutParams.WRAP_CONTENT;params.format = PixelFormat.TRANSLUCENT;params.windowAnimations = com.android.internal.R.style.Animation_Toast;params.type = WindowManager.LayoutParams.TYPE_TOAST;params.setTitle("Toast");params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;}/*** schedule handleShow into the right thread*/@Overridepublic void show() {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.post(mShow);}/*** schedule handleHide into the right thread*/@Overridepublic void hide() {if (localLOGV) Log.v(TAG, "HIDE: " + this);mHandler.post(mHide);}public void handleShow() {...if (mView != mNextView) {// remove the old view if necessaryhandleHide();mView = mNextView;Context context = mView.getContext().getApplicationContext();if (context == null) {context = mView.getContext();}mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);...if (mView.getParent() != null) {if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);mWM.removeView(mView);}...mWM.addView(mView, mParams);...}}private void trySendAccessibilityEvent() {...}public void handleHide() {...if (mView != null) {// note: checking parent() just to make sure the view has// been added...  i have seen cases where we get here when// the view isn't yet added, so let's try not to crash.if (mView.getParent() != null) {...mWM.removeView(mView);}mView = null;}}
}

好吧,上面的代码太长不想看,那就把核心的代码挑出来

public void show(){...WindowManager mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);mWN.addView(mView, mParams);
}public void hide(){...WindowManager mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);mWN.removeView(mView);
}

核心代码可以明显看出,Toast的机制就是往WindowManager添加以及移除view,那只要获得TN对象,重新封装一次show()和hide()方法就可以实现自定义显示时间。

private void initTN() {try {Field tnField = toast.getClass().getDeclaredField("mTN");tnField.setAccessible(true);mTN = (ITransientNotification) tnField.get(toast);/**调用tn.show()之前一定要先设置mNextView*/Field tnNextViewField = mTN.getClass().getDeclaredField("mNextView");tnNextViewField.setAccessible(true);tnNextViewField.set(mTN, toast.getView());} catch (Exception e) {e.printStackTrace();}
}public show(){initTN();mTN.show();
}

代码中mTN就是从Toast中利用反射获取的对象,类型是ITransientNotification,这是从android源码中拿出来的aidl接口,匹配TN的类型。主动调用mTN.show()方法后就会神奇的发现,Toast长时间存在屏幕中,即使离开了app它依然存在,直到调用mTN.hide()后才消失。


Toast显示时间拓展的问题已经解决了,剩下一个自定义动画的问题。现在回过头再看TN类的初始化方法代码,里面初始化了一个WindowManager.LayoutParams对象,做过悬浮窗功能的同学应该都接触过它,下面这一句代码就是定义窗口动画的关键,如果能修改params.windowAnimations就能够修改窗口动画。

params.windowAnimations = com.android.internal.R.style.Animation_Toast;

很不幸的是,params并不是一个公有的属性,那就暴力点继续用反射获取并且修改窗口动画

private void initTN() {try {Field tnField = toast.getClass().getDeclaredField("mTN");tnField.setAccessible(true);mTN = (ITransientNotification) tnField.get(toast);/**调用tn.show()之前一定要先设置mNextView*/Field tnNextViewField = mTN.getClass().getDeclaredField("mNextView");tnNextViewField.setAccessible(true);tnNextViewField.set(mTN, toast.getView());/**获取params后重新定义窗口动画*/Field tnParamsField = mTN.getClass().getDeclaredField("mParams");tnParamsField.setAccessible(true);WindowManager.LayoutParams params = (WindowManager.LayoutParams) tnParamsField.get(mTN);params.windowAnimations = R.style.anim_view;} catch (Exception e) {e.printStackTrace();}
}

至此,ExToast的工作原理已经基本解释完毕。对于本篇反复讲到的利用Java反射获取类里面的私有属性以及方法,是一个很实用的技能,本篇不详细解释Java反射知识,如果不熟悉的同学可以自行查找Java反射相关资料了解。了解完后应该会对ExToast工具类的设计原理很清楚。

对于Toast的更多应用,请期待下一篇文章。转载请注明出处,谢谢!

Toast拓展--自定义显示时间和动画相关推荐

  1. Android 自定义带图标Toast,工具方法,Toast自定义显示时间

    带图标Toast工具方法1 样式 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:an ...

  2. android toast 自定义时间,Android Toast自定义显示时间

    Toast是Android中使用频率较高的弹窗提示手段,使用起来简单.方便.常规使用方法这里不做说明,继前一篇博客<Android中Toast全屏显示> ,其中抛砖引玉的给出一个简单的实现 ...

  3. Andorid Toast倒计时,自定义显示时间

    1.toast做倒计时,自定义显示时间,不抢占焦点,不过其他事件占了焦点后,他就不能在前台显示了.如果有这个需求,看第2种方式. //倒计时 CountDownTimer countDown ; pu ...

  4. Android Toast 自定义显示时长

    Android Toast 只支持两种时间 LENGTH_SHORT 2 秒,LENGTH_LONG 3.5 秒,但是有场景需要自定义显示时长就会有问题,所以需要自定义实现,下边是自定义的类,通过定时 ...

  5. html位置插入透明动画文字,视频加移动水印 视频添加图片加文字水印 设置透明漂浮移动并控制显示时间...

    有没有小伙伴平时在看一些视频的时候,视频里会有一张图片然而图片里面有文字,然后是透明的图片,并且还漂浮移动在视频画面里,过了一会儿就自动消失了.这也是一直添加水印的方法,不过是把图片设置了半透明的样子 ...

  6. Linux如何在任务栏显示时间,在MFC[转载]在MFC状态栏显示时间 状态栏显示时间

    c/c++ vc 在mfc状态栏显示时间,在VC的控件中有个Status bar可以在窗体状态栏中添加日期和时间.其实通过简单的代码,你就能创建一个有时钟显示的状态栏,并且还能设置时钟栏的显示方式.举 ...

  7. android中自定义 toast,android 自定义Toast样式和显示方式

    问题: 1.android 开发中如果不停的触发显示Toast,会造成Toast一个接一个的弹出,非常影响用户体验. 2.android设备有千万个,每个设备的Toast的背景有可能不一样,造成在应用 ...

  8. iOS 自定义页面的切换动画与交互动画 By Swift

    iOS7之前,开发者为了寻求自定义Navigation Controller的Push/Pop动画,只能受限于子类化一个UINavigationController,或是用自定义的动画去覆盖它.但是随 ...

  9. 51定时中断系统控制LED点阵屏显示逐帧动画

    写在前面 最近回头看之前写的文章感到一种很浓的公式感,我确实是提前写好了模板每次都套用,整篇看下来感觉就像是在交老师布置的实验报告,看起来很成熟但实际上背离了自己的初衷,接下来我会尽可能的复现自己在做 ...

最新文章

  1. linux shell 的 for 循环
  2. 请简述什么是spring的ioc和di_绿茶用什么茶叶罐储存?有6种茶叶罐适合
  3. python一些常用方法_python 的一些常用方法
  4. [VC]WindowProc和DefWindowProc函数
  5. 第 6 章 —— 装饰模式
  6. 全部都显示服务器已加扰,特殊字符在浏览器中正确显示,但在phpMyAdmin中加扰...
  7. 纯PHP实现定时器任务(Timer)
  8. k8s学习:WordPress + MySQL + PVC 构建一个博客网站
  9. oppo k10 Pro和iqooneo6se哪个性能更强 哪个值得买呢
  10. 北斗导航 | 基于最小二乘残差法与奇偶矢量法的RAIM算法(附代码)
  11. 图片无损压缩软件哪个好用:试试完全免费的JPG-C 图片批量修整压缩减肥工具吧 | 最新jpg批量修整工具下载
  12. 使用python代码控制Maxon电机运行及读取电机数据
  13. 反恐精英代码_Steam永久降价通知!绝地求生史低促销!CS:GO源代码泄漏!
  14. OC 5217欧创芯原装芯片一款连续电感电流导通模 式的降压型 LED 恒流驱动器,SOT23-5 封装
  15. 使用uniapp微信公众号和小程序踩坑全过程
  16. oppo手机出现android什么坏了,OPPO手机提示“停止运行”怎么办 oppo停止运行的解决方法...
  17. CATIA V6二次开发——Automation之对象
  18. 架构师技能5:如何做code review 代码简洁之道
  19. C. Edgy Trees(并查集+细节)
  20. 数字音频接口(I2S,PCM/TDM)

热门文章

  1. 商城项目02_环境搭建、安装VAGRANT、DOCKER、MYSQL、REDIS、从0搭建各个微服务项目、数据库初始化、安装NGINX
  2. 实例003 输出名言
  3. 一个简单的登录注册网页的实现
  4. 云编译DSM引导(学习记录)
  5. Unix Domain Sockets
  6. pikachu漏洞平台靶场练习 总结 wp
  7. 【优化求解】基于NSGAII算法求解含约束多目标优化问题matlab代码
  8. CBOW(Continous Bag of Words)模型学习(2020-08-19)
  9. win10如何截屏_win10使用技巧分享!
  10. 战神笔记本如何打开/关闭关机状态下USB供电