本帖最后由 小试锋芒 于 2014-9-1 14:11 编辑
我们在Android应用开发中经常会需要在界面上弹出一个对界面操作无影响小提示框来提示用户一些信息,一般都会使用Android原生的Toast类。
- Toast.makeText(mContext, "消息内容", Toast.LENGTH_SHORT).show();
复制代码
一开始觉得,哎,挺好用,于是有点什么消息就都用Toast显示了。
但是永久了就发现,Toast的默认样式太丑了,没关系,Toast提供了一系列方法可以修改显示的内容:
分别是直接替换视图、设置显示时长、设置边距属性、设置显示位置、设置显示文字内容。
通过setView(View)方法,可以设置一个自定义的View,甚至可以包含一些很吊炸天的内容,包括图片、多彩文字等等,加上setGravity(int, int, int)可以使Toast显示在屏幕的任意位置,这么看来好像足够了。
但是实际上却远远不够啊! 首先就是显示时间的问题,系统只提供了两个长度的时间,分别为:
- public static final int LENGTH_SHORT = 0;
复制代码
- public static final int LENGTH_LONG = 1;
复制代码
设置为LENGTH_SHORT时,Toast显示时长大约为2秒,设置为LENGTH_LONG时,Toast显示时长大约为3秒。
在3秒内的Toast,我们都可以通过toast.cancle()取消显示,但如果要显示一个时长大于3秒的Toast时就无能为力了。
显示时间问题还不是最致命的,最致命的问题,是系统原生的Toast是呈队列显示出来的,必须要等到前一条Toast消失才会显示下一条。 相信很多同学都遇到了这个问题,比如我想做一个保存按钮,点击时先检查是否有必填项没有填,有的话就弹出提示而不执行保存。咱们自己运行几遍之后OK了没问题,结果到了测试同学手上,做一个小小的压力测试:狂按保存按钮!于是Toast队列排了好长一条,提示XX项为必填的消息一直在显示,导致后来的其他提示消息还要等一两分钟才显示出来,于是测试同学就给我提了这个“BUG”。囧啊
好吧,测试的你赢了。 认真读过Toast源码之后,发现可以通过反射获取Toast内部的一个私有变量:
复制代码
Toast是通过mTN这个变量来显示指定的View,在Toast内部叫做:
复制代码
我们调用toast.setView(view); 就是修改的这个属性。
Toast通过向系统更深处(通过JNI向底层)传入mTN这个对象来显示或关闭消息。 再来看看TN这个类:
- private static class TN extends ITransientNotification.Stub {
- final Runnable mShow = new Runnable() {
- @Override
- public void run() {
- handleShow();
- }
- };
- final Runnable mHide = new Runnable() {
- @Override
- public 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() {...}
- /**
- * schedule handleShow into the right thread
- */
- @Override
- public void show() {
- if (localLOGV) Log.v(TAG, "SHOW: " + this);
- mHandler.post(mShow);
- }
- /**
- * schedule handleHide into the right thread
- */
- @Override
- public void hide() {
- if (localLOGV) Log.v(TAG, "HIDE: " + this);
- mHandler.post(mHide);
- }
- public void handleShow() {
- ...
- if (mView != mNextView) {
- // remove the old view if necessary
- handleHide();
- 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;
- }
- }
- }
复制代码
这是一个定义在Toast内部的类,继承自ITransientNotification.Stub(这种设计模式似乎是为了内部安全?)。
我把不需要注意的代码都替换成了...,留下的代码是方便寻找TN是如何运作的。 我们注意到里面频繁出现了show和hide字样,好好阅读之后发现了:
- /**
- * schedule handleShow into the right thread
- */
- @Override
- public void show() {
- if (localLOGV) Log.v(TAG, "SHOW: " + this);
- mHandler.post(mShow);
- }
- /**
- * schedule handleHide into the right thread
- */
- @Override
- public void hide() {
- if (localLOGV) Log.v(TAG, "HIDE: " + this);
- mHandler.post(mHide);
- }
复制代码
show()方法和hide()方法都带@Override的注解,说明它们是重写或实现了父类的方法,而Toast把mTN送入系统深处,也很可能是让系统来调用这两个方法来实现Toast的显示和关闭。
测试之后发现,这两个方法确实就是控制Toast显示和关闭的,拿到这两个方法之后,我就可以做自己想要做的事啦。
经过一天的开发,我终于写出了自定义的Toast——抽屉吐司DrawerToast,效果图如下: Toast消息从右边移动淡入。
显示时长可自定义,让它显示一天都没问题。
Toast消息从右边移动淡出
使用其他自定义动画,旋转着淡入,吊炸天有木有!
多条Toast消息向上层叠,自定义的下移淡出动画。
可以修改默认背景为指定资源图片。
一顿狂按...
好了,最后放Demo吧,只需要把DrawerToast.java这个类拷贝进自己的工程,一句代码获取实例:
- <font color="#333333"><font face="Arial">DrawerToast toast = DrawerToast.getInstance(getApplicationContext()); </font></font>
复制代码
然后通过toast变量点出各种show方法使用啦!
各种花式用法见Demo:链接: http://pan.baidu.com/s/18gih4 密码: ohkq
|