关于Toast,大家可能熟的不能再熟了,但是都知道它有一个缺点,就是没有办法控制显示时长,默认有俩种状态,
Toast.LENGTH_LONG 默认显示3.5秒
Toast.LENGTH_SHORT 默认显示2秒。
源码就不说了,直接说解决方式:
(1)、通过源码发现只要避免了让他加入系统维修的Toast队列中,那么就可以让我们来操作
他的显示还有隐藏,那么就只能通过反射来实现了:代码:
上边是一个完整的Toast,直接就能使用,调用简单:package com.duoku.platform.single.view; import android.content.Context; import android.graphics.Color; import android.os.Build; import android.os.Handler; import android.os.Message; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; import android.widget.Toast; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * Created by chenpengfei_d on 2016/9/1. */ public class DkToast { private static Toast mToast; private static Context mContext; private static TextView mTextView; private static long duration; private static DkToast mDkToast; private static final int SHOW = 1; private static final int HIDE = 0; private static Object mTN; private static Method mShow; private static Method mHide; private static Field mViewFeild; private static Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ case SHOW: handler.sendEmptyMessageDelayed(HIDE,duration); break; case HIDE: hide(); break; } } }; public static DkToast makeDkToast(Context context,String msg,long dur) { mDkToast = new DkToast(); mContext = context; duration = dur; mToast = new Toast(mContext); mTextView = new TextView(mContext); mTextView.setText(msg); mTextView.setTextSize(18); mTextView.setTextColor(Color.RED); mToast.setView(mTextView); mToast.setGravity(Gravity.CENTER,0,0); reflectToast(); return mDkToast; } public static void reflectToast(){ Field field = null; try { field = mToast.getClass().getDeclaredField("mTN"); field.setAccessible(true); mTN = field.get(mToast); mShow = mTN.getClass().getDeclaredMethod("show"); mHide = mTN.getClass().getDeclaredMethod("hide"); mViewFeild = mTN.getClass().getDeclaredField("mNextView"); mViewFeild.setAccessible(true); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (NoSuchMethodException e1) { e1.printStackTrace(); } } public static void show(){ try { //android4.0以上就要以下处理 if(Build.VERSION.SDK_INT >14) { Field mNextViewField = mTN.getClass().getDeclaredField("mNextView"); mNextViewField.setAccessible(true); LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = mToast.getView(); mNextViewField.set(mTN, v); Method method = mTN.getClass().getDeclaredMethod("show", null); method.invoke(mTN, null); } mShow.invoke(mTN, null); }catch (Exception e){ e.printStackTrace(); } handler.sendEmptyMessage(SHOW); } private static void hide(){ try { mHide.invoke(mTN, null); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
实例:
DkToast.makeDkToast(this,"显示效果",100000).show();位置调整,已经自定义View须要自己加了(2)第二种方式,使用过桌面悬浮窗的都应该知道,通过WindowManager可以实现类似360小火箭的悬浮效果
,那么我们也可以使用这个来进行Toast的显示:
不了解WindowManager实现悬浮窗的可以看下边资料:
http://blog.csdn.net/u012808234/article/details/52209713
Toast实现的代码:
package com.duoku.platform.demo.single; import android.content.Context; import android.graphics.Color; import android.graphics.PixelFormat; import android.os.Handler; import android.os.Message; import android.view.Gravity; import android.view.View; import android.view.WindowManager; import android.widget.TextView; /** * Created by chenpengfei_d on 2016/9/2. */ public class WindowToast { private WindowManager mWindowManager; private View view; WindowManager.LayoutParams params; private static final int SHOW = 0; private static final int HIDE = 1; private int duration ; private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ case SHOW: handler.sendEmptyMessageDelayed(HIDE,duration); break; case HIDE: hide(); break; } } }; public WindowToast makeToast(Context context ,String msg ,int duration){ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); params = new WindowManager.LayoutParams(); params.type = WindowManager.LayoutParams.TYPE_TOAST; params.windowAnimations = android.R.style.Animation_Toast; params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; params.format = PixelFormat.TRANSLUCENT; params.width = WindowManager.LayoutParams.WRAP_CONTENT; params.height = WindowManager.LayoutParams.WRAP_CONTENT; params.gravity = Gravity.BOTTOM; view = new TextView(context); TextView textView = (TextView) view; textView.setText(msg); textView.setTextColor(Color.BLACK); textView.setTextSize(18); this.duration = duration; return this; } public WindowToast setGravity(int type){ params.gravity = type; return this; } public WindowToast setView(View view) { this.view = view; return this; } public void show(){ if(view == null){ throw new RuntimeException("setView must have been called"); } mWindowManager.addView(view,params); handler.sendEmptyMessage(SHOW); } public void hide(){ mWindowManager.removeView(view); } }
简单的实现了一个Toast的功能,可以自定义显示时长。
关于4.0以上手机反射不起作用,可以使用这个。
(3)第三种,其实跟第二种很类似,只是我们仿照Toast源码来自己构建Toast队列,进行Toast
的整体控制,当然Toast的实现还是WindowManager(代码不难就不自己写了,网上一位大神解决MIUI系统不显示写的):(地址:http://caizhitao.com/2016/02/09/android-toast-compat/)package io.github.zhitaocai.toastcompat.toastcompat; import android.annotation.TargetApi; import android.content.Context; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.os.Build; import android.os.Handler; import android.view.Gravity; import android.view.View; import android.view.WindowManager; import android.widget.TextView; import android.widget.Toast; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; import io.github.zhitaocai.toastcompat.util.DisplayUtil; /** * @author zhitao * @since 2016-01-21 14:33 */ public class MIUIToast implements IToast { private static Handler mHandler = new Handler(); /** * 维护toast的队列 */ private static BlockingQueue<MIUIToast> mQueue = new LinkedBlockingQueue<MIUIToast>(); /** * 原子操作:判断当前是否在读取{@linkplain #mQueue 队列}来显示toast */ protected static AtomicInteger mAtomicInteger = new AtomicInteger(0); private WindowManager mWindowManager; private long mDurationMillis; private View mView; private WindowManager.LayoutParams mParams; private Context mContext; public static IToast makeText(Context context, String text, long duration) { return new MIUIToast(context).setText(text).setDuration(duration) .setGravity(Gravity.BOTTOM, 0, DisplayUtil.dip2px(context, 64)); } public MIUIToast(Context context) { mContext = context; mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mParams = new WindowManager.LayoutParams(); mParams.height = WindowManager.LayoutParams.WRAP_CONTENT; mParams.width = WindowManager.LayoutParams.WRAP_CONTENT; mParams.format = PixelFormat.TRANSLUCENT; mParams.windowAnimations = android.R.style.Animation_Toast; mParams.type = WindowManager.LayoutParams.TYPE_TOAST; mParams.setTitle("Toast"); mParams.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; // 默认小米Toast在下方居中 mParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; } /** * Set the location at which the notification should appear on the screen. * * @param gravity * @param xOffset * @param yOffset */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override public IToast setGravity(int gravity, int xOffset, int yOffset) { // We can resolve the Gravity here by using the Locale for getting // the layout direction final int finalGravity; if (Build.VERSION.SDK_INT >= 14) { final Configuration config = mView.getContext().getResources().getConfiguration(); finalGravity = Gravity.getAbsoluteGravity(gravity, config.getLayoutDirection()); } else { finalGravity = gravity; } mParams.gravity = finalGravity; if ((finalGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { mParams.horizontalWeight = 1.0f; } if ((finalGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { mParams.verticalWeight = 1.0f; } mParams.y = yOffset; mParams.x = xOffset; return this; } @Override public IToast setDuration(long durationMillis) { if (durationMillis < 0) { mDurationMillis = 0; } if (durationMillis == Toast.LENGTH_SHORT) { mDurationMillis = 2000; } else if (durationMillis == Toast.LENGTH_LONG) { mDurationMillis = 3500; } else { mDurationMillis = durationMillis; } return this; } /** * 不能和{@link #setText(String)}一起使用,要么{@link #setView(View)} 要么{@link #setView(View)} * * @param view * * @return */ @Override public IToast setView(View view) { mView = view; return this; } @Override public IToast setMargin(float horizontalMargin, float verticalMargin) { mParams.horizontalMargin = horizontalMargin; mParams.verticalMargin = verticalMargin; return this; } /** * 不能和{@link #setView(View)}一起使用,要么{@link #setView(View)} 要么{@link #setView(View)} * * @return */ @Override public IToast setText(String text) { // 模拟Toast的布局文件 com.android.internal.R.layout.transient_notification // 虽然可以手动用java写,但是不同厂商系统,这个布局的设置好像是不同的,因此我们自己获取原生Toast的view进行配置 View view = Toast.makeText(mContext, text, Toast.LENGTH_SHORT).getView(); if (view != null) { TextView tv = (TextView) view.findViewById(android.R.id.message); tv.setText(text); setView(view); } return this; } @Override public void show() { // 1. 将本次需要显示的toast加入到队列中 mQueue.offer(this); // 2. 如果队列还没有激活,就激活队列,依次展示队列中的toast if (0 == mAtomicInteger.get()) { mAtomicInteger.incrementAndGet(); mHandler.post(mActivite); } } @Override public void cancel() { // 1. 如果队列已经处于非激活状态或者队列没有toast了,就表示队列没有toast正在展示了,直接return if (0 == mAtomicInteger.get() && mQueue.isEmpty()) { return; } // 2. 当前显示的toast是否为本次要取消的toast,如果是的话 // 2.1 先移除之前的队列逻辑 // 2.2 立即暂停当前显示的toast // 2.3 重新激活队列 if (this.equals(mQueue.peek())) { mHandler.removeCallbacks(mActivite); mHandler.post(mHide); mHandler.post(mActivite); } //TODO 如果一个Toast在队列中的等候展示,当调用了这个toast的取消时,考虑是否应该从对队列中移除,看产品需求吧 } private void handleShow() { if (mView != null) { if (mView.getParent() != null) { mWindowManager.removeView(mView); } mWindowManager.addView(mView, mParams); } } private 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) { mWindowManager.removeView(mView); // 同时从队列中移除这个toast mQueue.poll(); } mView = null; } } private static void activeQueue() { MIUIToast miuiToast = mQueue.peek(); if (miuiToast == null) { // 如果不能从队列中获取到toast的话,那么就表示已经暂时完所有的toast了 // 这个时候需要标记队列状态为:非激活读取中 mAtomicInteger.decrementAndGet(); } else { // 如果还能从队列中获取到toast的话,那么就表示还有toast没有展示 // 1. 展示队首的toast // 2. 设置一定时间后主动采取toast消失措施 // 3. 设置展示完毕之后再次执行本逻辑,以展示下一个toast mHandler.post(miuiToast.mShow); mHandler.postDelayed(miuiToast.mHide, miuiToast.mDurationMillis); mHandler.postDelayed(mActivite, miuiToast.mDurationMillis); } } private final Runnable mShow = new Runnable() { @Override public void run() { handleShow(); } }; private final Runnable mHide = new Runnable() { @Override public void run() { handleHide(); } }; private final static Runnable mActivite = new Runnable() { @Override public void run() { activeQueue(); } }; }
总结: (1)解决了Toast 无法自定义时长的问题(2)解决了4.0以上手机使用反射自定义时长不生效的问题(3)解决了Toast显示不灵活的问题