版权声明:本文为博主原创文章,未经博主允许不得转载。
关于Toast,大家可能熟的不能再熟了,但是都知道它有一个缺点,就是没有办法控制显示时长,默认有俩种状态,
Toast.LENGTH_LONG 默认显示3.5秒
Toast.LENGTH_SHORT 默认显示2秒。
源码就不说了,直接说解决方式:
(1)、通过源码发现只要避免了让他加入系统维修的Toast队列中,那么就可以让我们来操作
他的显示还有隐藏,那么就只能通过反射来实现了:
代码:
[java] view plain copy print?在CODE上查看代码片派生到我的代码片
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();
}
}
}
上边是一个完整的Toast,直接就能使用,调用简单:
实例:
DkToast.makeDkToast(this,"显示效果",100000).show();
位置调整,已经自定义View须要自己加了
(2)第二种方式,使用过桌面悬浮窗的都应该知道,通过WindowManager可以实现类似360小火箭的悬浮效果
,那么我们也可以使用这个来进行Toast的显示:
不了解WindowManager实现悬浮窗的可以看下边资料:
http://blog.csdn.net/u012808234/article/details/52209713
Toast实现的代码:
[java] view plain copy print?在CODE上查看代码片派生到我的代码片
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/)
[java] view plain copy print?在CODE上查看代码片派生到我的代码片
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显示不灵活的问题