安卓Toast能否比Toast.LENGTH_LONG时间更长?

301

当使用 setDuration() 方法设置 Toast 时,是否可以设置自定义长度或至少比 Toast.LENGTH_LONG 更长的时间?


5
@Nicolae,你为什么移除了“toast”标签?它看起来与问题相关。 - Shadow The Spring Wizard
2
@ShadowWizard 标签应该反映出广泛关注的问题主题。比如,如果标记为Android,那么Android专家将会发现这个问题。Toast对这个问题没有帮助,似乎更像是一个无用的标签。如果toast是一个好的标签,因为问题是关于Android中的标签,那么长度也是一个好的标签。事实上,问题中的每个单词都应该成为一个标签... 没有不尊重的意思,只是在表达我的观点 :) - Nicu Surdu
15
我使用 toast 标签。我认为标签的作用是帮助搜索和分类,而 toast 明显是一个常见的搜索词。androidtoast 看起来非常合适。 - ChrisWilson4
27个回答

374

如果你深入研究Android代码,会发现有几行指明我们无法更改Toast消息的持续时间。

 NotificationManagerService.scheduleTimeoutLocked() {
    ...
    long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);
    }

默认值为 duration 是

private static final int LONG_DELAY = 3500; // 3.5 seconds
private static final int SHORT_DELAY = 2000; // 2 seconds

3
谢谢您发布默认值!我担心找不到它们。 - Amplify91
1
我也在寻找默认的toast值,这是我找到的第一个结果。肯定点赞了。谢谢! - Dave
这是正确的。解决方案是寻找除了 Toast 之外的其他东西,比如对话框。 - Al Ro

157

LENGTH_SHORTLENGTH_LONG 的值分别为0和1。这意味着它们被视为标志,而不是实际的持续时间,因此我认为无法将持续时间设置为除这些值之外的任何其他值。

如果您想向用户显示更长时间的消息,请考虑使用状态栏通知。状态栏通知可以在不再相关时通过程序取消。


2
感谢关于状态栏的建议,但我会选择自定义对话框活动。 - user268481
这是一个有趣的想法,但与烤面包相比确实不同。对话框是更好的选择(就像我下面给出的例子一样)。 - Al Ro

128

你可以尝试:

for (int i=0; i < 2; i++)
{
      Toast.makeText(this, "blah", Toast.LENGTH_LONG).show();
}

将时间翻倍,如果你指定3而不是2,它将使时间增加三倍...以此类推。


75
这个解决方案不好,例如,如果你在烤面包的时间之前退出活动,它会一遍又一遍地闪烁... - dwbrito
2
在适当的位置使用Toast.cancel()可以处理取消提示框的操作。 [+1] - Devrath
1
可以实现,但是 Toast 会闪烁多少次就取决于您指定的计数。 - HendraWD
2
这是有史以来最棒的DIY答案啊哈哈 - 我没做。 - Isabelle
1
哇!我怎么没想到呢,哈哈。 - Ruchir Baronia
显示剩余6条评论

31

如果你想让一个 Toast 持久存在,我发现你可以通过使用 Timer 并调用 toast.show() 来绕过这个问题(每秒钟左右调用一次应该就可以了)。如果 Toast 已经在显示,那么调用 show() 不会出现任何问题,但是它会刷新它在屏幕上停留的时间。


3
问题在于,如果用户触摸屏幕,Android 将隐藏该弹出消息,但这时计时器会重新创建它。 - Violet Giraffe
2
@VioletGiraffe,使用类似于布尔标志的东西在您的ViewGroup OnTouch事件中处理这个问题非常简单。为了优化此过程,您应该使计时器重复尽可能接近屏幕上显示“Toast”的实际时间(长时间为3.5秒,短时间为2秒)。 - syklon

16

我已经开发了一个自定义 Toast 类,在其中你可以设定要显示的 Toast 持续时间(以毫秒为单位)

import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;

public final class ToastHelper {

    private static final String TAG = ToastHelper.class.getName();

    public static interface OnShowListener {
        public void onShow(ToastHelper toast);
    }

    public static interface OnDismissListener {
        public void onDismiss(ToastHelper toast);
    }

    private static final int WIDTH_PADDING_IN_DIP = 25;
    private static final int HEIGHT_PADDING_IN_DIP = 15;
    private static final long DEFAULT_DURATION_MILLIS = 2000L;

    private final Context context;
    private final WindowManager windowManager;
    private View toastView;

    private int gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
    private int mX;
    private int mY;
    private long duration = DEFAULT_DURATION_MILLIS;
    private CharSequence text = "";
    private int horizontalMargin;
    private int verticalMargin;
    private WindowManager.LayoutParams params;
    private Handler handler;
    private boolean isShowing;
    private boolean leadingInfinite;

    private OnShowListener onShowListener;
    private OnDismissListener onDismissListener;

    private final Runnable timer = new Runnable() {

        @Override
        public void run() {
            cancel();
        }
    };

    public ToastHelper(Context context) {
        Context mContext = context.getApplicationContext();
        if (mContext == null) {
            mContext = context;
        }
        this.context = mContext;
        windowManager = (WindowManager) mContext
                .getSystemService(Context.WINDOW_SERVICE);
        init();
    }

    private void init() {
        mY = context.getResources().getDisplayMetrics().widthPixels / 5;
        params = new WindowManager.LayoutParams();
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.width = WindowManager.LayoutParams.WRAP_CONTENT;
        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
        params.format = android.graphics.PixelFormat.TRANSLUCENT;
        params.type = WindowManager.LayoutParams.TYPE_TOAST;
        params.setTitle("ToastHelper");
        params.alpha = 1.0f;
        // params.buttonBrightness = 1.0f;
        params.packageName = context.getPackageName();
        params.windowAnimations = android.R.style.Animation_Toast;
    }

    @SuppressWarnings("deprecation")
    @android.annotation.TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private View getDefaultToastView() {
        TextView textView = new TextView(context);
        textView.setText(text);
        textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
        textView.setClickable(false);
        textView.setFocusable(false);
        textView.setFocusableInTouchMode(false);
        textView.setTextColor(android.graphics.Color.WHITE);
        // textView.setBackgroundColor(Color.BLACK);
        android.graphics.drawable.Drawable drawable = context.getResources()
                .getDrawable(android.R.drawable.toast_frame);
        if (Build.VERSION.SDK_INT < 16) {
            textView.setBackgroundDrawable(drawable);
        } else {
            textView.setBackground(drawable);
        }
        int wP = getPixFromDip(context, WIDTH_PADDING_IN_DIP);
        int hP = getPixFromDip(context, HEIGHT_PADDING_IN_DIP);
        textView.setPadding(wP, hP, wP, hP);
        return textView;
    }

    private static int getPixFromDip(Context context, int dip) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dip, context.getResources().getDisplayMetrics());
    }

    public void cancel() {
        removeView(true);
    }

    private void removeView(boolean invokeListener) {
        if (toastView != null && toastView.getParent() != null) {
            try {
                Log.i(TAG, "Cancelling Toast...");
                windowManager.removeView(toastView);
                handler.removeCallbacks(timer);
            } finally {
                isShowing = false;
                if (onDismissListener != null && invokeListener) {
                    onDismissListener.onDismiss(this);
                }
            }
        }
    }

    public void show() {
        if (leadingInfinite) {
            throw new InfiniteLoopException(
                    "Calling show() in OnShowListener leads to infinite loop.");
        }
        cancel();
        if (onShowListener != null) {
            leadingInfinite = true;
            onShowListener.onShow(this);
            leadingInfinite = false;
        }
        if (toastView == null) {
            toastView = getDefaultToastView();
        }
        params.gravity = android.support.v4.view.GravityCompat
                .getAbsoluteGravity(gravity, android.support.v4.view.ViewCompat
                        .getLayoutDirection(toastView));
        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
            params.horizontalWeight = 1.0f;
        }
        if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
            params.verticalWeight = 1.0f;
        }
        params.x = mX;
        params.y = mY;
        params.verticalMargin = verticalMargin;
        params.horizontalMargin = horizontalMargin;

        removeView(false);
        windowManager.addView(toastView, params);
        isShowing = true;
        if (handler == null) {
            handler = new Handler();
        }
        handler.postDelayed(timer, duration);
    }

    public boolean isShowing() {
        return isShowing;
    }

    public void setDuration(long durationMillis) {
        this.duration = durationMillis;
    }

    public void setView(View view) {
        removeView(false);
        toastView = view;
    }

    public void setText(CharSequence text) {
        this.text = text;
    }

    public void setText(int resId) {
        text = context.getString(resId);
    }

    public void setGravity(int gravity, int xOffset, int yOffset) {
        this.gravity = gravity;
        mX = xOffset;
        mY = yOffset;
    }

    public void setMargin(int horizontalMargin, int verticalMargin) {
        this.horizontalMargin = horizontalMargin;
        this.verticalMargin = verticalMargin;
    }

    public long getDuration() {
        return duration;
    }

    public int getGravity() {
        return gravity;
    }

    public int getHorizontalMargin() {
        return horizontalMargin;
    }

    public int getVerticalMargin() {
        return verticalMargin;
    }

    public int getXOffset() {
        return mX;
    }

    public int getYOffset() {
        return mY;
    }

    public View getView() {
        return toastView;
    }

    public void setOnShowListener(OnShowListener onShowListener) {
        this.onShowListener = onShowListener;
    }

    public void setOnDismissListener(OnDismissListener onDismissListener) {
        this.onDismissListener = onDismissListener;
    }

    public static ToastHelper makeText(Context context, CharSequence text,
            long durationMillis) {
        ToastHelper helper = new ToastHelper(context);
        helper.setText(text);
        helper.setDuration(durationMillis);
        return helper;
    }

    public static ToastHelper makeText(Context context, int resId,
            long durationMillis) {
        String string = context.getString(resId);
        return makeText(context, string, durationMillis);
    }

    public static ToastHelper makeText(Context context, CharSequence text) {
        return makeText(context, text, DEFAULT_DURATION_MILLIS);
    }

    public static ToastHelper makeText(Context context, int resId) {
        return makeText(context, resId, DEFAULT_DURATION_MILLIS);
    }

    public static void showToast(Context context, CharSequence text) {
        makeText(context, text, DEFAULT_DURATION_MILLIS).show();
    }

    public static void showToast(Context context, int resId) {
        makeText(context, resId, DEFAULT_DURATION_MILLIS).show();
    }

    private static class InfiniteLoopException extends RuntimeException {
        private static final long serialVersionUID = 6176352792639864360L;

        private InfiniteLoopException(String msg) {
            super(msg);
        }
    }
}

1
android.view.WindowManager$BadTokenException: 无法添加窗口--令牌为空无效;您的活动是否正在运行? - Ahamadullah Saikat

14

LONG_DELAY表示显示持续时间3.5秒SHORT_DELAY表示显示持续时间2秒

Toast内部使用INotificationManager,并在每次调用Toast.show()时调用它的enqueueToast方法。

两次以SHORT_DELAY调用show()将再次排队相同的Toast。它将显示4秒(2秒+2秒)。

同样地,两次以LONG_DELAY调用show()将再次排队相同的Toast。它将显示7秒(3.5秒+3.5秒)。


这个黑客技巧似乎不再起作用了。 - Al Ro

13

谢谢,很好,但我们如何停止 onDestroy 线程呢?我尝试过了,但不确定:public static void cancel(Toast mytoast) { if (null != t) t.stop(); mytoast.cancel(); } - hornetbzz

11

我知道有点晚了,但是我采用了Regis_AG的答案,并将其封装成一个助手类,效果非常好。

public class Toaster {
  private static final int SHORT_TOAST_DURATION = 2000;

  private Toaster() {}

  public static void makeLongToast(String text, long durationInMillis) {
    final Toast t = Toast.makeText(App.context(), text, Toast.LENGTH_SHORT);
    t.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 0);

    new CountDownTimer(Math.max(durationInMillis - SHORT_TOAST_DURATION, 1000), 1000) {
      @Override
      public void onFinish() {
        t.show();
      }

      @Override
      public void onTick(long millisUntilFinished) {
        t.show();
      }
    }.start();
  }
}

在您的应用程序代码中,只需像这样做:
    Toaster.makeLongToast("Toasty!", 8000);

这真的很有效!但是你如何使它可定制和可触摸? - android developer
抱歉问这个新手问题,但是当我创建上述的Toaster类时,它在这一行中说无法解析符号“App”。最后的Toast t = Toast.makeText(App.context(), text, Toast.LENGTH_SHORT); 谢谢,再次道歉。 - Brian Fleming
哦,对不起!App.context()基本上是我编写的一段自定义代码,方便地从任何地方访问ApplicationContext而无需传递它。这不是你用于许多事情的模式,但我发现它对ApplicationContext有意义。您可能希望编辑此片段以将ApplicationContext作为参数传递到方法中。 - Chris Aitchison

9

我知道回答有点晚了..我曾经遇到过同样的问题,决定实现自己的简化版Toast,在研究了Android Toast源代码后。

基本上,您需要创建一个新的窗口管理器,并使用处理程序在所需时间内显示和隐藏窗口。

 //Create your handler
 Handler mHandler = new Handler();

//Custom Toast Layout
mLayout = layoutInflater.inflate(R.layout.customtoast, null);

//Initialisation 

mWindowManager = (WindowManager) context.getApplicationContext()
            .getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams params = new WindowManager.LayoutParams();

params.gravity = Gravity.BOTTOM
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = android.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;

初始化布局后,您可以使用自己的隐藏和显示方法。
    public void handleShow() {
    mWindowManager.addView(mLayout, mParams);
    }

    public void handleHide() {
        if (mLayout != null) {
            if (mLayout.getParent() != null) {
                mWindowManager.removeView(mLayout);
            }
                         mLayout = null;
        }

现在您只需要添加两个可运行的线程,这些线程调用 handleShow() 和 handleHide() 方法,并将其发布到 Handler 中即可。
    Runnable toastShowRunnable = new Runnable() {
        public void run() {
            handleShow();
        }
    };

 Runnable toastHideRunnable = new Runnable() {
        public void run() {
            handleHide();
        }
    }; 

并且最后的部分
public void show() {

    mHandler.post(toastShowRunnable);
    //The duration that you want
    mHandler.postDelayed(toastHideRunnable, mDuration);

}

这是一个快速而简单的实现..没有考虑任何性能问题。


1
我尝试运行它(包括调用“show()”),但没有任何显示(在Android 7.1上测试)。我认为你漏掉了一些东西。另外,这里有什么部分可以防止视图被移除,因为 toast 在短时间内消失了? - android developer

6

这是我使用上述代码制作的自定义Toast类:

import android.content.Context;
import android.os.CountDownTimer;
import android.widget.Toast;

public class CustomToast extends Toast {
    int mDuration;
    boolean mShowing = false;
    public CustomToast(Context context) {
        super(context);
        mDuration = 2;
    }


    /**
     * Set the time to show the toast for (in seconds) 
     * @param seconds Seconds to display the toast
     */
    @Override
    public void setDuration(int seconds) {
        super.setDuration(LENGTH_SHORT);
        if(seconds < 2) seconds = 2; //Minimum
        mDuration = seconds;
    }

    /**
     * Show the toast for the given time 
     */
    @Override
    public void show() {
        super.show();

        if(mShowing) return;

        mShowing = true;
        final Toast thisToast = this;
        new CountDownTimer((mDuration-2)*1000, 1000)
        {
            public void onTick(long millisUntilFinished) {thisToast.show();}
            public void onFinish() {thisToast.show(); mShowing = false;}

        }.start();  
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接