Android中Toast显示时间的自定义

转载自:http://www.apkbus.com/forum.php?mod=viewthread&tid=48443


Android中Toast的显示时间为特定时间且不可更改,但是有时候我们开发设计需要让Toast显示更长时间,或者自己完全控制Toast的显示和关闭。通过查看Toast类的源码,可以看出,这有点难为它了,Toast类本身并没有提供相应方法。

  但是通过源码的查看,还是可以看出点眉头。源码分析思路在这里转eoe里的一篇文章,思路较为清晰:

  转:

  Toast信息提示框之所以在显示一定时间后会自动关闭,是因为在系统中有一个Toast队列。系统会依次从队列中取(出队列)一个Toast,并 显示它。在显示一段时间后,再关闭,然后再显示下一个Toast信息提示框。

    直到Toast队列中所有Toast都显示完为止。那么有些时候需要这个Toas t信息提示框长时间显示,直到需要关闭它时通过代码来控制,而不是让系统自动来关闭Toast信息提示框。

    不过这个要求对于Toast本身来说有些过 分,因为Toast类并没有提供这个功能。虽然如此,但方法总比问题多。通过一些特殊的处理还是可以实现这个功能的,而且并不复杂。

  Toast信息提示框需要调用Toast.show方法来显示。下面来看一下show方法的源代码。

  1.   public void show() {
  2.   if (mNextView == null) {
  3.   throw new RuntimeException("setView must have been called");
  4.   }
  5.   INotificationManager service = getService();
  6.   String pkg = mContext.getPackageName();
  7.   TN tn = mTN;
  8.   try {
  9.   // 将当前Toast加入到Toast队列
  10.   service.enqueueToast(pkg, tn, mDuration);
  11.   } catch (RemoteException e) {
  12.   // Empty
  13.   }
  14.   }
复制代码

  show方法的代码并不复杂,可以很容易找到如下的代码。
 
  1.  service.enqueueToast(pkg, tn, mDuration);
复制代码

  从上面的代码可以很容易推断出它的功能是将当前的Toast加入到系统的Toast队列中。

    看到这里,各位读者应该想到。虽然show方法的表面功能是显示Toast信息提示框,但其实际的功能是将Toast加入到队列中,再由系统根据Toast队列来显示Toast信息提示框。那么我们经过更进一步地思考,可以大胆地做出一个初步的方案。

    既然系统的Toast队列可以显示Toast信息提示框,那么我们为什么不可以自己来显示它呢?

    这样不是可以自己来控制Toast的信息提示框的显示和关闭了吗!当然,这就不能再调用show方法来显示Toast信息提示框了(因为show方法会将Toast加入队列,这样我们就控制不了Toast了)。

  既然初步方案已拟定,现在就来实施它。先在Toast类找一下还有没有其他的show方法。

    结果发现了一个TN类,该类是Toast的一个内嵌类。在TN类中有一个show方法。TN是ITransientNotification.Stub的子类。从ITransientNotification和TN类中的show方法初步推断(因为Transient的中文意思是“短暂的”)系统是从Toast队列中获得了Toast对象后,利用TN对象的show方法显示Toast,再利用TN.hide方法来关闭Toast。

    首先声明,这只是假设,我们还不知道这么做是否可行!当然,这也是科学研究的一般方法,先推断或假设,然后再证明推断或假设。

  现在关键的一步是获得TN对象。遗憾的是TN被声明成private类型,外部无法访问。不过别着急。在Toast类中有一个mTN变量。虽然不是public变量,但仍然可以通过反射技术访问该变量。mTN变量会在创建Toast对象时初始化。

    因此,只要获得mTN变量,就获得了TN对象。下面的代码显示了一个永远不会自动关闭的Toast信息提示框。
  1.   // 先创建一个Toast对象  Toast toast = Toast.makeText(this, "永不消失的Toast", Toast.LENGTH_SHORT);  // 设置Toast信息提示框显示的位置(在屏幕顶部水平居中显示)  toast.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 0);  try  {
  2.   // 从Toast对象中获得mTN变量  Field field = toast.getClass().getDeclaredField("mTN");
  3.   field.setAccessible(true);
  4.   Object obj = field.get(toast);
  5.   // TN对象中获得了show方法
  6.   Method method = obj.getClass().getDeclaredMethod("show", null);
  7.   // 调用show方法来显示Toast信息提示框
  8.   method.invoke(obj, null);
  9.   }
  10.   catch (Exception e)
  11.   {
  12.   }
复制代码

  上面的代码中try{…}catch(…){…}语句中的代码是关键。先利用事先创建好的Toast对象获得了mTN变量。然后再利用反射技术获得了TN对象的show方法。

  关闭Toast和显示Toast的方法类似,只是需要获得hide方法,代码如下:
  1.   try
  2.   {
  3.   // 需要将前面代码中的obj变量变成类变量。这样在多个地方就都可以访问了
  4.   Method method = obj.getClass().getDeclaredMethod("hide", null);
  5.   method.invoke(obj, null);
  6.   }
  7.   catch (Exception e)
  8.   {
  9.   }
复制代码

  上面的代码已经很完美地实现了通过代码控制Toast信息提示框显示和关闭的功能。

但如果想实现得更完美,可以在Android SDK源代码中找一个叫ITransientNotification.aidl的文件(该文件是AIDL服务定义文件,将在后面详细介绍),并在Android工程的src目录中建一个android.app包,将这个文件放到这个包中。

    然后ADT会自动在gen目录中生成了一个android.app包,包中有一个ITransientNotification.java文件。由于Android SDK自带的ItransientNotification接口属于内部资源,外部程序无法访问,因此,只能将从Toast对象中获得的mTN变量转换成刚才生成的ITransientNotification对象了。

    这样就不需要使反射技术获得show和hide方法了。

    经过改良的显示和关闭Toast信息提示框的代码如下:
  1.   ITransientNotification notification = (ITransientNotification) field.get(toast);
  2.   // 显示Toast信息提示框
  3.   notification.show();
  4.   // 关闭Toast信息提示框
  5.   notification.hide();
复制代码

  最后整理代码如下:

  Java代码
  1.     import java.lang.reflect.Field;
  2.   import java.lang.reflect.Method;
  3.   import android.app.Activity;
  4.   import android.os.Bundle;
  5.   import android.view.Gravity;
  6.   import android.view.View;
  7.   import android.widget.Button;
  8.   import android.widget.Toast;
  9.   public class TestToastActivity extends Activity {
  10.   private Button showtoast,closetoast;
  11.   private Toast toast;
  12.   private Field field;
  13.   private Object obj;
  14.   private Method showMethod,hideMethod;
  15.   @Override
  16.   public void onCreate(Bundle savedInstanceState) {
  17.   super.onCreate(savedInstanceState);
  18.   setContentView(R.layout.main);
  19.   //初始化按钮组件
  20.   showtoast = (Button)this.findViewById(R.id.showtoast);
  21.   closetoast = (Button)this.findViewById(R.id.closetoast);
  22.   //设置组件监听
  23.   showtoast.setOnClickListener(new MyOnClickListener());
  24.   closetoast.setOnClickListener(new MyOnClickListener());
  25.   //创建Toast对象
  26.   toast = Toast.makeText(this, "Toast自定义显示时间测试", 1);
  27.   toast.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL, 0, 0);
  28.   //利用反射技术拿到mTN对象
  29.   reflectionTN();
  30.   }
  31.   class MyOnClickListener implements View.OnClickListener{
  32.   @Override
  33.   public void onClick(View v) {
  34.   switch (v.getId()) {
  35.   case R.id.showtoast:
  36.   try {
  37.   showMethod.invoke(obj, null);//调用TN对象的show()方法,显示toast
  38.   } catch (Exception e) {
  39.   e.printStackTrace();
  40.   }
  41.   break;
  42.   case R.id.closetoast:
  43.   try {
  44.   hideMethod.invoke(obj, null);//调用TN对象的hide()方法,关闭toast
  45.   } catch (Exception e) {
  46.   e.printStackTrace();
  47.   }
  48.   break;
  49.   default:
  50.   break;
  51.   }
  52.   }
  53.   }
  54.   private void reflectionTN() {
  55.   try {
  56.   field = toast.getClass().getDeclaredField("mTN");
  57.   field.setAccessible(true);
  58.   obj = field.get(toast);
  59.   showMethod = obj.getClass().getDeclaredMethod("show", null);
  60.   hideMethod = obj.getClass().getDeclaredMethod("hide", null);
  61.   } catch (Exception e) {
  62.   e.printStackTrace();
  63.   }
  64.   }
  65.   }
复制代码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值