Android中用Toast.cancel()方法优化toast内容的显示

本文介绍了一个针对Android Toast显示问题的解决方案。通过自定义ToastUtil类并利用Handler与synchronized关键字,确保快速连续点击按钮时,Toast能正确显示而不重叠,解决了Toast cancel()方法失效的问题。

产品在测试过程中发现一个bug,就是测试人员不停的疯狂的点击某个按钮,触发了toast以后,toast内容会一直排着队的显示出来,不能很快的消失。这样可能会影响用户的使用。

看到Toast有一个cancel()方法:

<nobr>void</nobr> <nobr><span class="sympad" style="margin-right:2px"><a href="file:///D:/Program%20Files/Android/android-sdk/docs/reference/android/widget/Toast.html#cancel()" style="color:rgb(0,102,153); text-decoration:none">cancel</a></span>()</nobr>
Close the view if it's showing, or don't show it if it isn't showing yet.
做程序员的,基本一看api就知道,用这个可以取消上一个toast的显示,然后显示下一个,这样就能解决出现的问题。可是在测试的过程中,发现却没有想象中的那么简单,不信可以百度一下,很多很多人发现toast的cancel()方法不起作用。还是不讲具体过程,只讲结果吧。

我把toast做成了一个应用类,方便使用,大家可以直接用:

package com.arui.framework.android.util;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;
/** 
* Toast util class. 
* 
* @author http://blog.youkuaiyun.com/arui319 
* @version 2011/11/30 
* 
*/ 
public class ToastUtil {

	private static Handler handler = new Handler(Looper.getMainLooper());

	private static Toast toast = null;
	
	private static Object synObj = new Object();

	public static void showMessage(final Context act, final String msg) {
		showMessage(act, msg, Toast.LENGTH_SHORT);
	}

	public static void showMessage(final Context act, final int msg) {
		showMessage(act, msg, Toast.LENGTH_SHORT);
	}

	public static void showMessage(final Context act, final String msg,
			final int len) {
		new Thread(new Runnable() {
			public void run() {
				handler.post(new Runnable() {
					@Override
					public void run() {
						synchronized (synObj) {
							if (toast != null) {
								toast.cancel();
								toast.setText(msg);
								toast.setDuration(len);
							} else {
								toast = Toast.makeText(act, msg, len);
							}
							toast.show();
						}
					}
				});
			}
		}).start();
	}


	public static void showMessage(final Context act, final int msg,
			final int len) {
		new Thread(new Runnable() {
			public void run() {
				handler.post(new Runnable() {
					@Override
					public void run() {
						synchronized (synObj) {
							if (toast != null) {
								toast.cancel();
								toast.setText(msg);
								toast.setDuration(len);
							} else {
								toast = Toast.makeText(act, msg, len);
							}
							toast.show();
						}
					}
				});
			}
		}).start();
	}

}



代码的逻辑很简单。这里加了同步,这样做可以确保每一个toast的内容至少可以显示出来,而不是还没显示就取消掉了。这样做,是因为toast的内容不一定完全相同,如果没显示出来,也会有问题。


---------------------------------------------------------------------------

GL(arui319)

http://blog.youkuaiyun.com/arui319

<本文可以转载,但是请保留以上作者信息。谢谢。>

---------------------------------------------------------------------------

package com.weishitechsub.kdcxqwb.fragment.Adapter; import android.Manifest; import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import com.chad.library.adapter.base.BaseQuickAdapter; import com.chad.library.adapter.base.viewholder.BaseViewHolder; import com.weishitechsub.kdcxqwb.R; import com.weishitechsub.kdcxqwb.bean.ListBean; import com.weishitechsub.kdcxqwb.utils.PackageNotificationSender; import com.weishitechsub.kdcxqwb.utils.ReminderManager; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class MyCourierAdapter extends BaseQuickAdapter<ListBean, BaseViewHolder> { private static final String TAG = "MyCourierAdapter"; private List<ListBean> mTrackList; private Context context; // 存储:每个单号最后一次被提醒到的状态(用于去重通知) private final Map<String, String> remindedStates = new ConcurrentHashMap<>(); private Handler mainHandler = new Handler(Looper.getMainLooper()); int type; // 缓存当前列表数据 private List<ListBean> currentList; public MyCourierAdapter(List<ListBean> list, Context context, int type) { super(R.layout.my_courier_adapter, list); this.context = context; this.type = type; ReminderManager.getInstance(context); // 初始化单例 } @Override protected void convert(@NonNull BaseViewHolder baseViewHolder, ListBean dataBean) { String number = dataBean.getNum(); baseViewHolder.setText(R.id.tv_number, number); baseViewHolder.setText(R.id.tv_address, dataBean.getContext()); if (type == 1) { baseViewHolder.getView(R.id.tv_remind).setVisibility(View.GONE); baseViewHolder.getView(R.id.iv_remind).setVisibility(View.GONE); } else { baseViewHolder.getView(R.id.tv_remind).setVisibility(View.VISIBLE); baseViewHolder.getView(R.id.iv_remind).setVisibility(View.VISIBLE); } // 设置快递公司信息和图标 if (dataBean.getType() != null) { String trackName = getTrackName(dataBean.getType()); baseViewHolder.setText(R.id.tv_type, trackName); ImageView logo = baseViewHolder.getView(R.id.iv_logo); LinearLayout line = baseViewHolder.getView(R.id.line); setCompanyLogo(trackName, logo, line); } // ===== 获取状态文本 ===== String state = dataBean.getState(); String stateText = getStateText(state); baseViewHolder.setText(R.id.tv_state, stateText != null ? stateText : "未知状态"); ImageView ivRemind = baseViewHolder.getView(R.id.iv_remind); // === 情况1:状态无效 === if (state == null || TextUtils.isEmpty(stateText)) { ivRemind.setEnabled(false); ivRemind.setAlpha(0.4f); refreshRemindIcon(ivRemind, false); ivRemind.setOnClickListener(v -> { Toast.makeText(context.getApplicationContext(), "该快递状态异常,暂不支持提醒功能", Toast.LENGTH_SHORT).show(); }); } // === 情况2:终止状态(签收、退回、拒签)=== else if (isTerminatedState(state)) { ivRemind.setEnabled(true); ivRemind.setAlpha(0.5f); boolean isReminded = ReminderManager.getInstance(context).isReminded(number); refreshRemindIcon(ivRemind, isReminded); ivRemind.setOnClickListener(v -> { String tip; if (state.startsWith("3")) { tip = getSignReceivedTip(state); } else if (state.startsWith("4") || "203".equals(state)) { tip = "该快递已拒签,订单结束。"; } else if (state.startsWith("6")) { tip = "该快递已被退回。"; } else { tip = "该快递已完成,无需提醒。"; } Toast.makeText(context.getApplicationContext(), tip, Toast.LENGTH_SHORT).show(); }); // 自动关闭提醒(如果是之前开启的) if (isReminded) { ReminderManager.getInstance(context).disableReminder(number); } } // === 情况3:正常可提醒状态(如在途、派件中等)=== else { ivRemind.setEnabled(true); ivRemind.setAlpha(1.0f); boolean isReminded = ReminderManager.getInstance(context).isReminded(number); refreshRemindIcon(ivRemind, isReminded); ivRemind.setOnClickListener(v -> { // 每次点击都重新获取当前状态!不能用外面缓存的 isReminded 变量 boolean nowEnabled = ReminderManager.getInstance(context).isReminded(number); if (nowEnabled) { // 已开启 → 关闭 ReminderManager.getInstance(context).disableReminder(number); Toast.makeText(context, "已关闭提醒: " + number, Toast.LENGTH_SHORT).show(); refreshRemindIcon(ivRemind, false); } else { // 未开启 → 开启 enableReminderWithPermissionCheck(dataBean); // 注意:enableReminderWithPermissionCheck 内部会调用 enableReminder() // 所以图标要在外面设为 true,但在权限弹窗场景下可能不会立即生效 refreshRemindIcon(ivRemind, true); // 假设成功(实际应在回调中处理更严谨) } }); } // 更新缓存列表 currentList = getData(); } @Override public void onViewRecycled(@NonNull BaseViewHolder holder) { // 防止 RecyclerView 复用时保留旧的 OnClickListener ImageView ivRemind = holder.getView(R.id.iv_remind); ivRemind.setOnClickListener(null); super.onViewRecycled(holder); } // 检查权限后开启提醒,并发送通知 private void enableReminderWithPermissionCheck(ListBean dataBean) { String number = dataBean.getNum(); String state = dataBean.getState(); String type = dataBean.getType(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { showNotificationPermissionDialog(); return; } } // 启用本地提醒记录 ReminderManager.getInstance(context).enableReminder(number); Toast.makeText(context, "已开启提醒: " + number, Toast.LENGTH_SHORT).show(); // 发送一条当前状态的通知 String companyName = getTrackName(type); sendStatusNotification(number, companyName, getStateText(state), getNotifyContent(state)); } // 刷新提醒按钮图标 private void refreshRemindIcon(ImageView ivRemind, boolean isEnabled) { if (isEnabled) { ivRemind.setImageResource(R.mipmap.button_on); } else { ivRemind.setImageResource(R.mipmap.button_off); } } // ======== 增强版:精确状态文本映射 ========= private String getStateText(String state) { if (state == null || state.isEmpty()) return null; switch (state) { // --- 在途系列 --- case "0": case "1002": // 干线运输 return "在途"; case "1001": // 到达派件城市 return "到达派件城市"; case "1003": // 转递 return "转递中"; // --- 揽收系列 --- case "1": case "101": // 已下单 return "已下单"; case "102": // 待揽收 return "待揽收"; case "103": // 已揽收 return "已揽收"; // --- 派件系列 --- case "5": return "派件中"; case "501": // 投柜/驿站 return "已投柜/驿站"; // --- 转投系列 --- case "7": return "转投"; // --- 疑难系列 --- case "2": return "疑难"; case "201": return "超时未签收"; case "202": return "超时未更新"; case "203": return "拒收"; case "204": return "派件异常"; // --- 签收系列 --- case "3": return "签收"; // --- 退签 --- case "4": return "退签"; // --- 退回 --- case "6": return "退回"; // --- 清关 --- default: if (state.startsWith("8") || state.equals("10") || state.equals("11") || state.equals("12") || state.equals("13")) { return getDetailClearanceText(state); } return null; } } // 清关详情 private String getDetailClearanceText(String state) { switch (state) { case "8": return "清关"; case "10": return "待清关"; case "11": return "清关中"; case "12": return "已清关"; case "13": return "清关异常"; default: return "清关"; } } // ======== 是否为终止状态(不能再提醒)======== private boolean isTerminatedState(String state) { if (state == null || state.isEmpty()) return false; char firstChar = state.charAt(0); // 主状态为 3(签收)、4(退签)、6(退回) 的都算终止 if (firstChar == '3' || firstChar == '4' || firstChar == '6') { return true; } // 特殊子状态:203 拒收 → 也视为终止 return "203".equals(state); } // ======== 签收提示语 ======== private String getSignReceivedTip(String state) { switch (state) { case "301": return "该快递已由本人签收,无需提醒。"; case "302": return "该快递在派件异常后完成签收。"; case "303": return "该快递已被他人代签,请注意查收。"; case "304": return "该快递已投入快递柜或驿站签收。"; default: return "该快递已签收,无需提醒。"; } } // ======== 通知内容(更精准)======== private String getNotifyContent(String state) { if (state == null) return "快递状态已更新,请注意查看。"; // 签收类统一处理 if (state.startsWith("3")) { return getSignReceivedTip(state); } switch (state) { // --- 在途 --- case "0": case "1002": return "您的快递已在运输途中,正在路上~"; case "1001": return "快递已到达派件城市,即将开始派送!"; case "1003": return "快递正在转递至下一个中转站点。"; // --- 揽收 --- case "1": case "101": return "已生成运单,请等待快递员上门揽收。"; case "102": return "快递员即将上门揽收,请准备好包裹。"; case "103": return "包裹已被成功揽收,进入运输流程。"; // --- 派件 --- case "5": return "快递员正在为您派送,请注意查收!"; case "501": return "快递已投入智能快递柜或驿站,请及时取件。"; // --- 转投 --- case "7": return "因地址问题,快递已转投其他网点处理。"; // --- 疑难 --- case "201": return "快递长时间未签收,请联系发件人确认。"; case "202": return "物流信息长时间未更新,请留意异常情况。"; case "203": return "您已申请拒收,快递将按流程退回。"; case "204": return "快递派送过程中出现异常,请关注后续更新。"; // --- 退回 & 退签 --- case "6": return "很遗憾,您的快递因故被退回。"; case "4": return "您已拒签该快递,订单已完成。"; default: return "快递状态已更新,请注意查看最新动态。"; } } // 发送状态变更通知 private void sendStatusNotification(String number, String company, String titleSuffix, String content) { PackageNotificationSender sender = new PackageNotificationSender(context); int id = ("status_" + number).hashCode() & Integer.MAX_VALUE; id = (id % 1_000_000) + 2000; // 避免 ID 冲突 sender.sendCustomNotification(id, "📌 快递" + titleSuffix, content, number, company); } // 根据 type 查找快递公司名称 private String getTrackName(String type) { if (mTrackList != null) { for (ListBean item : mTrackList) { if (item.getNum() != null && type != null && TextUtils.equals(item.getNum(), type)) { return item.getCom(); } } } return ""; } // 设置外部数据源 public void setTrackList(List<ListBean> list) { this.mTrackList = list; } // 缓存当前列表 @Override public void setNewInstance(List<ListBean> list) { super.setNewInstance(list); this.currentList = list; } // 获取所有开启提醒的条目 public List<ListBean> getRemindedItems() { List<ListBean> result = new ArrayList<>(); if (currentList == null) return result; Set<String> remindedNumbers = ReminderManager.getInstance(context).getRemindedNumbers(); for (ListBean bean : currentList) { if (remindedNumbers.contains(bean.getNum())) { result.add(bean); } } return result; } /** * 根据快递公司名称设置对应的 Logo 图标 */ private void setCompanyLogo(String name, ImageView logo, LinearLayout line) { if (name == null || name.isEmpty()) { logo.setVisibility(View.GONE); line.setVisibility(View.VISIBLE); return; } switch (name) { case "邮政快递包裹": logo.setImageResource(R.mipmap.img_yz_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "京东物流": logo.setImageResource(R.mipmap.img_jd_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "圆通快递": logo.setImageResource(R.mipmap.img_yt_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "中通快递": logo.setImageResource(R.mipmap.img_zt_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "顺丰速运": logo.setImageResource(R.mipmap.img_sf_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "韵达快递": logo.setImageResource(R.mipmap.img_yd_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "申通快递": logo.setImageResource(R.mipmap.img_st_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "EMS": logo.setImageResource(R.mipmap.img_ems_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; default: logo.setVisibility(View.GONE); line.setVisibility(View.VISIBLE); break; } } private void showNotificationPermissionDialog() { if (context == null) return; new AlertDialog.Builder(context) .setTitle("需要通知权限") .setMessage("请允许本应用发送快递状态提醒通知。\n\n进入【设置】→【通知】中开启权限。") .setPositiveButton("去开启", (dialog, which) -> { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.fromParts("package", context.getPackageName(), null)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); }) .setNegativeButton("取消", (dialog, which) -> { Toast.makeText(context, "未授予通知权限,无法开启提醒", Toast.LENGTH_SHORT).show(); }) .create() .show(); } } package com.weishitechsub.kdcxqwb.utils; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.Build; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import com.weishitechsub.kdcxqwb.R; import com.weishitechsub.kdcxqwb.MainActivity; public class PackageNotificationSender { private static final String CHANNEL_ID = "delivery_reminder_channel"; private final Context context; public PackageNotificationSender(Context context) { this.context = context.getApplicationContext(); createNotificationChannel(); } private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { CharSequence name = "快递提醒"; String description = "当快递到达或状态变更时发出通知"; int importance = NotificationManager.IMPORTANCE_HIGH; NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); channel.setDescription(description); NotificationManager notificationManager = context.getSystemService(NotificationManager.class); notificationManager.createNotificationChannel(channel); } } // 原来的取件提醒(用于开启提醒和轮询到达) public void sendPickupReminder(String number, String company) { sendCustomNotification( 1000 + Math.abs(number.hashCode() % 1000000), "📦 快递待取件", "您的快递 " + number + "(" + company + ")已到达,请及时取件!", number, company ); } // 新增:通用通知方法(用于状态变化) public void sendCustomNotification(int id, String title, String content, String number, String company) { Intent intent = new Intent(context, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.mipmap.img_icon) .setContentTitle(title) .setContentText(content) .setPriority(NotificationCompat.PRIORITY_HIGH) .setContentIntent(pendingIntent) .setAutoCancel(true) .setDefaults(NotificationCompat.DEFAULT_ALL); NotificationManagerCompat.from(context).notify(id, builder.build()); } } 设置关闭提醒在系统通知框里也取消
最新发布
11-29
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值