最近产品提了一个想让我拿刀的问题,一个提示文字要在其他app在顶层的时候也显示,不能干扰用户操作,文字必须是可以滚动,时长为30秒(啥啥啥都是啥),做安卓的都知道toast只能设置为2s和3.5s,其它的值都无效,API的文档虽然写的第三个参数是时间,但是Framework里作了重定义,限定了2s和3.5s这两个值,对应 Toast.LENGTH_SHORT和Toast.LENGTH_LONG,但是测试不会管你安卓是怎么限制的,实现不出就是你自己能力不行,为了保住饭碗,问遍了度娘及其他的大佬,终于搞出了一个让Toast持续显示的方案:
```
public void showMyToast(final Toast toast, final int cnt) {
final Timer timer =new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
toast.show();
}
},0,3000);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
toast.cancel();
timer.cancel();
}
}, cnt );
}
```
调用:
Toast toast=Toast.makeText(RegistActivity.this,"这是可以随意设置时间的Toast", Toast.LENGTH_LONG);showMyToast(toast,10*1000);// 设置显示时间
你每次要关的时候我就调用show()方法,直到我Timer计时完毕,网上最传广的方法,优点是全原生,只需要加个控制类就行,但是立马被测试打回来了,为啥?因为其实这只是伪显示,每3秒都是重新打开的,所以滚动文字每三秒就回到开始重新轮播,产品的需求是10秒一滚,它滚不了你就给我滚,,,好吧,既然别人是一种不要你觉得我要我觉得的态度,我也只能重新做了。。。有发现zhitaocai大佬为了解决当时MIUI4 toast不显示的问题把toast重写了一遍实现了完整的setDuration方法,(github地址:https://github.com/zhitaocai/ToastCompat_Deprecated)不过内部的view是写死的,如果只需要使用普通的toast的话只需引入依赖库之后直接ToastCompat.make(Contextcontext,Stringtext,longduration).show();
就可以愉快的使用了,但是如果需要像我一样实现滚动文字和文字前logo的话就必须对MIUItoast类进行重写,在IToast setText方法中自定义view和内容然后setview(view),具体代码如下:
```
public class MIUIToastimplements IToast {
private static HandlermHandler =new Handler();
public AutoMarqueeTextViewGeTuiToastUtilMessage;
public ImageViewGeTuiToastUtilImage;
public ViewGeTuiToastUtilView;
/**
* 维护toast的队列
*/
private static BlockingQueuemQueue =new LinkedBlockingQueue<>();
/**
* 原子操作:判断当前是否在读取{@linkplain #mQueue 队列}来显示toast
*/
protected static AtomicIntegermAtomicInteger =new AtomicInteger(0);
private WindowManagermWindowManager;
private long mDurationMillis;
private ViewmView;
private WindowManager.LayoutParamsmParams;
private ContextmContext;
public static IToastmakeText(Context context, String text, long duration) {
return new MIUIToast(context).setText(text).setDuration(duration)
.setGravity(Gravity.TOP, 0, DisplayUtil.dip2px(context, 10));
}
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.TOP ;
}
/**
* 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 IToastsetGravity(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 IToastsetDuration(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 IToastsetView(View view) {
mView = view;
return this;
}
@Override
public IToastsetMargin(float horizontalMargin, float verticalMargin) {
mParams.horizontalMargin = horizontalMargin;
mParams.verticalMargin = verticalMargin;
return this;
}
/**
* 不能和{@link #setView(View)}一起使用,要么{@link #setView(View)} 要么{@link #setView(View)}
*
* @return
*/
@Override
public IToastsetText(String text) {
// 模拟Toast的布局文件 com.android.internal.R.layout.transient_notification
// 虽然可以手动用java写,但是不同厂商系统,这个布局的设置好像是不同的,因此我们自己获取原生Toast的view进行配置
GeTuiToastUtilView = LayoutInflater.from(mContext).inflate(R.layout.toast_layout, null);
if (GeTuiToastUtilView !=null) {
GeTuiToastUtilMessage =GeTuiToastUtilView.findViewById(R.id.geTuiToastUtilMessage);
GeTuiToastUtilImage = (ImageView)GeTuiToastUtilView.findViewById(R.id.toast_image);
GeTuiToastUtilMessage.setTextColor(ScnResourceUtil.getColor(mContext, R.color.scn_color_EBEBEB));
GeTuiToastUtilMessage.setTextSize(TypedValue.COMPLEX_UNIT_PX, ScnResourceUtil.getDimen(mContext, R.dimen.dimen_12sp));
GeTuiToastUtilMessage.setMaxWidth(ScnResourceUtil.getDimen(mContext,R.dimen.dimen_574dp));
GeTuiToastUtilMessage.setScrollStep(ScnResourceUtil.getDimen(mContext,R.dimen.dimen_200dp));
GeTuiToastUtilMessage.setMaxLines(1);
GeTuiToastUtilMessage.setRndDuration(10);
GeTuiToastUtilMessage.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
setView(GeTuiToastUtilView);
GeTuiToastUtilMessage.setText(text.isEmpty() ?"null" : text);
GeTuiToastUtilMessage.post(() ->GeTuiToastUtilMessage.startMarquee());
this.setGravity(Gravity.TOP,0,20);
}
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 RunnablemShow =new Runnable() {
@Override
public void run() {
handleShow();
}
};
private final RunnablemHide =new Runnable() {
@Override
public void run() {
handleHide();
}
};
private final static RunnablemActivite =new Runnable() {
@Override
public void run() {
activeQueue();
}
};
}
```
这里感谢非花非雾--大佬的修改方案,这里用的是自定义方案三