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

本文介绍了一种解决在Android应用程序中频繁点击按钮导致Toast消息连续显示的问题的方法。通过使用Handler和synchronized关键字来确保Toast能够正确地更新内容,避免了因快速连续显示而导致的用户体验下降。

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

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

void cancel()

做程序员的,基本一看api就知道,用这个可以取消上一个toast的显示,然后显示下一个,这样就能解决出现的问题。可是在测试的过程中,发现却没有想象中的那么简单,不信可以百度一下,很多很多人发现toast的cancel()方法不起作用。还是不讲具体过程,只讲结果吧。

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

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();
	}

}

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

http://blog.youkuaiyun.com/arui319/article/details/7022392

1.1 DatabaseHelper.java文件代码 package cn.itcast.myapplication; import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Build; import android.os.Bundle; import android.provider.Settings; import android.view.View; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private RecyclerView recyclerView; private NoteAdapter adapter; private DatabaseHelper dbHelper; private static final int NOTIFICATION_PERMISSION_CODE = 123; // Android 13的SDK版本号 private static final int ANDROID_13_SDK = 33; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); dbHelper = new DatabaseHelper(this); recyclerView = findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); findViewById(R.id.fabAdd).setOnClickListener(v -> { startActivity(new Intent(this, EditActivity.class)); }); loadNotes(); // 检查通知是否启用 if (!NotificationManagerCompat.from(this).areNotificationsEnabled()) { showNotificationSettingsDialog(); } } /** * 显示通知设置对话框 */ private void showNotificationSettingsDialog() { new AlertDialog.Builder(this) .setTitle("开启通知") .setMessage("要使记事本的提醒功能正常工作,需要在设置中启用通知。") .setPositiveButton("去设置", (dialog, which) -> { // 打开应用通知设置 Intent intent = new Intent(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS); intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); } else { intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS"); intent.putExtra("app_package", getPackageName()); intent.putExtra("app_uid", getApplicationInfo().uid); } startActivity(intent); }) .setNegativeButton("取消", null) .create() .show(); } private void loadNotes() { List<Note> notes = new ArrayList<>(); SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursor cursor = db.query(DatabaseHelper.TABLE_NOTE, null, null, null, null, null, null); if (cursor != null) { while (cursor.moveToNext()) { int id = cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_ID)); String title = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_TITLE)); String content = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_CONTENT)); String time = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_TIME)) long reminderTime = 0; // 获取提醒相关字段,默认值为0(无提醒) boolean hasReminder = false; if (cursor.getColumnIndex(DatabaseHelper.COLUMN_REMINDER_TIME) != -1) { // 检查列是否存在(适应数据库升级) reminderTime = cursor.getLong(cursor.getColumnIndex(DatabaseHelper.COLUMN_REMINDER_TIME)); } if (cursor.getColumnIndex(DatabaseHelper.COLUMN_HAS_REMINDER) != -1) { hasReminder = cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_HAS_REMINDER)) == 1; } notes.add(new Note(id, title, content, time, reminderTime, hasReminder)); } cursor.close(); } adapter = new NoteAdapter(notes, new NoteAdapter.OnItemClickListener() { @Override public void onItemClick(Note note) { Intent intent = new Intent(MainActivity.this, EditActivity.class); intent.putExtra("NOTE_ID", note.getId()); startActivity(intent); } @Override public void onItemLongClick(Note note) { showDeleteDialog(note); } }); recyclerView.setAdapter(adapter); } private void showDeleteDialog(Note note) { new AlertDialog.Builder(this) .setTitle("删除记录") .setMessage("确定要删除这条记录吗?") .setPositiveButton("删除", (dialog, which) -> { SQLiteDatabase db = dbHelper.getWritableDatabase(); db.delete(DatabaseHelper.TABLE_NOTE, DatabaseHelper.COLUMN_ID + "=?", new String[]{String.valueOf(note.getId())}) if (note.hasReminder()) { // 如果有提醒,需要取消 NotificationHelper.cancelAlarm(this, note.getId()); } loadNotes(); // 刷新列表 }) .setNegativeButton("取消", null) .show(); } @Override protected void onResume() { super.onResume(); loadNotes(); // 每次返回时刷新数据 } 1.2 EditActivity.java文件代码 package cn.itcast.myapplication; import android.app.DatePickerDialog; import android.app.TimePickerDialog; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; public class EditActivity extends AppCompatActivity { private EditText etTitle, etContent; private CheckBox cbReminder; private TextView tvReminderTime; private Button btnSetReminder; private DatabaseHelper dbHelper; private int noteId = -1; private Calendar reminderCalendar; // 用于存储提醒时间 private boolean hasReminder = false; // 是否设置了提醒 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_edit); dbHelper = new DatabaseHelper(this); etTitle = findViewById(R.id.etTitle); etContent = findViewById(R.id.etContent); cbReminder = findViewById(R.id.cbReminder); tvReminderTime = findViewById(R.id.tvReminderTime); btnSetReminder = findViewById(R.id.btnSetReminder); reminderCalendar = Calendar.getInstance(); btnSetReminder.setOnClickListener(v -> showDateTimePicker()); // 设置提醒开关的变化监听 cbReminder.setOnCheckedChangeListener((buttonView, isChecked) -> { hasReminder = isChecked; btnSetReminder.setEnabled(isChecked); if (!isChecked) { tvReminderTime.setText("未设置提醒"); } }); noteId = getIntent().getIntExtra("NOTE_ID", -1); if (noteId != -1) { SQLiteDatabase db = dbHelper.getReadableDatabase(); // 加载已有数据 Cursor cursor = db.query(DatabaseHelper.TABLE_NOTE, null, DatabaseHelper.COLUMN_ID + "=?", new String[]{String.valueOf(noteId)}, null, null, null); if (cursor != null && cursor.moveToFirst()) { etTitle.setText(cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_TITLE))); etContent.setText(cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_CONTENT))); if (cursor.getColumnIndex(DatabaseHelper.COLUMN_REMINDER_TIME) != -1 &&// 读取提醒相关信息 cursor.getColumnIndex(DatabaseHelper.COLUMN_HAS_REMINDER) != -1) { long reminderTime = cursor.getLong(cursor.getColumnIndex(DatabaseHelper.COLUMN_REMINDER_TIME)); hasReminder = cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_HAS_REMINDER)) == 1; if (hasReminder && reminderTime > 0) { reminderCalendar.setTimeInMillis(reminderTime); updateReminderTimeText(); } cbReminder.setChecked(hasReminder); btnSetReminder.setEnabled(hasReminder); } cursor.close(); } } findViewById(R.id.btnSave).setOnClickListener(v -> saveNote()); } /** * 显示日期和时间选择器 */ private void showDateTimePicker() { final Calendar currentCalendar = Calendar.getInstance(); DatePickerDialog datePickerDialog = new DatePickerDialog( this, // 创建日期选择器 (view, year, month, dayOfMonth) -> { reminderCalendar.set(Calendar.YEAR, year); reminderCalendar.set(Calendar.MONTH, month); reminderCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth); new TimePickerDialog( // 选择完日期后,显示时间选择器 EditActivity.this, (view1, hourOfDay, minute) -> { reminderCalendar.set(Calendar.HOUR_OF_DAY, hourOfDay); reminderCalendar.set(Calendar.MINUTE, minute); reminderCalendar.set(Calendar.SECOND, 0); updateReminderTimeText(); // 更新界面上的时间显示 }, currentCalendar.get(Calendar.HOUR_OF_DAY), currentCalendar.get(Calendar.MINUTE), true ).show(); }, currentCalendar.get(Calendar.YEAR), currentCalendar.get(Calendar.MONTH), currentCalendar.get(Calendar.DAY_OF_MONTH) ); // 设置最小日期为当天 datePickerDialog.getDatePicker().setMinDate(System.currentTimeMillis() - 1000); datePickerDialog.show(); } /** * 更新界面上的提醒时间文本 */ private void updateReminderTimeText() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()); tvReminderTime.setText("提醒时间:" + sdf.format(reminderCalendar.getTime())); } private void saveNote() { String title = etTitle.getText().toString().trim(); String content = etContent.getText().toString().trim(); String time = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(new Date()); if (title.isEmpty()) { Toast.makeText(this, "请输入标题", Toast.LENGTH_SHORT).show(); return; } SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(DatabaseHelper.COLUMN_TITLE, title); values.put(DatabaseHelper.COLUMN_CONTENT, content); values.put(DatabaseHelper.COLUMN_TIME, time); values.put(DatabaseHelper.COLUMN_HAS_REMINDER, hasReminder ? 1 : 0); if (hasReminder) { values.put(DatabaseHelper.COLUMN_REMINDER_TIME, reminderCalendar.getTimeInMillis()); } else { values.put(DatabaseHelper.COLUMN_REMINDER_TIME, 0); // 没有提醒,设置为0 } if (noteId == -1) { long newId = db.insert(DatabaseHelper.TABLE_NOTE, null, values); // 新增记录 noteId = (int) newId; } else { db.update(DatabaseHelper.TABLE_NOTE, values, // 更新记录 DatabaseHelper.COLUMN_ID + "=?", new String[]{String.valueOf(noteId)}); } // 如果设置了提醒,则设置闹钟 if (hasReminder && reminderCalendar.getTimeInMillis() > System.currentTimeMillis()) { NotificationHelper.setAlarm( this, noteId, title, content, reminderCalendar.getTimeInMillis() ); Toast.makeText(this, "已设置提醒", Toast.LENGTH_SHORT).show(); } else if (hasReminder) { // 如果设置的提醒时间已过期 Toast.makeText(this, "提醒时间已过期,请重新设置", Toast.LENGTH_SHORT).show(); return; } else { NotificationHelper.cancelAlarm(this, noteId); // 如果之前有提醒,现在取消了,则取消闹钟 } finish(); // 返回主界面 } } 1.3 MainActivity.java文件代码 package cn.itcast.myapplication; import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Build; import android.os.Bundle; import android.provider.Settings; import android.view.View; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private RecyclerView recyclerView; private NoteAdapter adapter; private DatabaseHelper dbHelper; private static final int NOTIFICATION_PERMISSION_CODE = 123; private static final int ANDROID_13_SDK = 33; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); dbHelper = new DatabaseHelper(this); recyclerView = findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); findViewById(R.id.fabAdd).setOnClickListener(v -> { startActivity(new Intent(this, EditActivity.class)); }); loadNotes(); if (!NotificationManagerCompat.from(this).areNotificationsEnabled()) { // 检查通知是否启用 showNotificationSettingsDialog(); } } /** * 显示通知设置对话框 */ private void showNotificationSettingsDialog() { new AlertDialog.Builder(this) .setTitle("开启通知") .setMessage("要使记事本的提醒功能正常工作,需要在设置中启用通知。") .setPositiveButton("去设置", (dialog, which) -> { Intent intent = new Intent(); // 打开应用通知设置 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS); intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); } else { intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS"); intent.putExtra("app_package", getPackageName()); intent.putExtra("app_uid", getApplicationInfo().uid); } startActivity(intent); }) .setNegativeButton("取消", null) .create() .show(); } private void loadNotes() { List<Note> notes = new ArrayList<>(); SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursor cursor = db.query(DatabaseHelper.TABLE_NOTE, null, null, null, null, null, null); if (cursor != null) { while (cursor.moveToNext()) { int id = cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_ID)); String title = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_TITLE)); String content = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_CONTENT)); String time = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_TIME)); long reminderTime = 0; // 获取提醒相关字段,默认值为0(无提醒) boolean hasReminder = false; // 检查列是否存在(适应数据库升级) if (cursor.getColumnIndex(DatabaseHelper.COLUMN_REMINDER_TIME) != -1) { reminderTime = cursor.getLong(cursor.getColumnIndex(DatabaseHelper.COLUMN_REMINDER_TIME)); } if (cursor.getColumnIndex(DatabaseHelper.COLUMN_HAS_REMINDER) != -1) { hasReminder = cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_HAS_REMINDER)) == 1; } notes.add(new Note(id, title, content, time, reminderTime, hasReminder)); } cursor.close(); } adapter = new NoteAdapter(notes, new NoteAdapter.OnItemClickListener() { @Override public void onItemClick(Note note) { Intent intent = new Intent(MainActivity.this, EditActivity.class); intent.putExtra("NOTE_ID", note.getId()); startActivity(intent); } @Override public void onItemLongClick(Note note) { showDeleteDialog(note); } }); recyclerView.setAdapter(adapter); } private void showDeleteDialog(Note note) { new AlertDialog.Builder(this) .setTitle("删除记录") .setMessage("确定要删除这条记录吗?") .setPositiveButton("删除", (dialog, which) -> { SQLiteDatabase db = dbHelper.getWritableDatabase(); db.delete(DatabaseHelper.TABLE_NOTE, DatabaseHelper.COLUMN_ID + "=?", new String[]{String.valueOf(note.getId())}); if (note.hasReminder()) { // 如果有提醒,需要取消 NotificationHelper.cancelAlarm(this, note.getId()); } loadNotes(); // 刷新列表 }) .setNegativeButton("取消", null) .show(); } @Override protected void onResume() { super.onResume(); loadNotes(); // 每次返回时刷新数据 } } 1.4 Note.java文件代码 package cn.itcast.myapplication; public class Note { private int id; private String title; private String content; private String time; private long reminderTime; // 提醒时间(毫秒) private boolean hasReminder; // 是否设置提醒 public Note(int id, String title, String content, String time, long reminderTime, boolean hasReminder) { // 构造函数、Getter 和 Setter 方法 this.id = id; this.title = title; this.content = content; this.time = time; this.reminderTime = reminderTime; this.hasReminder = hasReminder; } public int getId() { return id; } public String getTitle() { return title; } public String getContent() { return content; } public String getTime() { return time; } public long getReminderTime() { return reminderTime; } public boolean hasReminder() { return hasReminder; } public void setReminderTime(long reminderTime) { this.reminderTime = reminderTime; } public void setHasReminder(boolean hasReminder) { this.hasReminder = hasReminder; } } 1.5 NoteAdapter.java文件代码 package cn.itcast.myapplication; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import java.util.List; public class NoteAdapter extends RecyclerView.Adapter<NoteAdapter.ViewHolder> { private List<Note> notes; private OnItemClickListener listener; public interface OnItemClickListener { void onItemClick(Note note); void onItemLongClick(Note note); } public static class ViewHolder extends RecyclerView.ViewHolder { TextView tvTitle, tvTime, tvContent; public ViewHolder(View itemView) { super(itemView); tvTitle = itemView.findViewById(R.id.tvTitle); tvTime = itemView.findViewById(R.id.tvTime); tvContent = itemView.findViewById(R.id.tvContent); } } public NoteAdapter(List<Note> notes, OnItemClickListener listener) { this.notes = notes; this.listener = listener; } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_note, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { Note note = notes.get(position); holder.tvTitle.setText(note.getTitle()); holder.tvTime.setText(note.getTime()); holder.tvContent.setText(note.getContent()); holder.itemView.setOnClickListener(v -> listener.onItemClick(note)); holder.itemView.setOnLongClickListener(v -> { listener.onItemLongClick(note); return true; }); } @Override public int getItemCount() { return notes.size(); } } 1.6 NotificationHelper.java文件代码 package cn.itcast.myapplication; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.media.RingtoneManager; import android.net.Uri; import android.os.Build; import android.os.VibrationEffect; import android.os.Vibrator; import android.widget.Toast; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import android.provider.Settings; public class NotificationHelper { private static final String CHANNEL_ID = "note_reminder_channel"; private static final String CHANNEL_NAME = "记事本提醒"; private static final String CHANNEL_DESCRIPTION = "记事本应用的提醒通知"; private static final String ACTION_STOP_VIBRATION = "cn.itcast.myapplication.STOP_VIBRATION"; // 震动相关常量 private static Vibrator activeVibrator; // 用于存储当前活跃的震动器 private static int activeNoteId = -1; // 当前正在震动的笔记ID /** * 设置提醒闹钟 * @param context 上下文 * @param noteId 笔记ID * @param title 笔记标题 * @param content 笔记内容 * @param timeInMillis 提醒时间(毫秒) */ public static void setAlarm(Context context, int noteId, String title, String content, long timeInMillis) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context, ReminderReceiver.class); intent.putExtra("NOTE_ID", noteId); intent.putExtra("NOTE_TITLE", title); intent.putExtra("NOTE_CONTENT", content); // 使用笔记ID作为请求码,确保每个笔记的提醒是唯一的 PendingIntent pendingIntent = PendingIntent.getBroadcast( context, noteId, intent, PendingIntent.FLAG_UPDATE_CURRENT ); try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 设置精确闹钟 alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent); } else { alarmManager.set(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent); } Toast.makeText(context, "提醒已设置:" + formatTime(timeInMillis), Toast.LENGTH_SHORT).show(); } catch (Exception e) { Toast.makeText(context, "设置提醒失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); } } /** * 格式化时间为友好显示 */ private static String formatTime(long timeInMillis) { return android.text.format.DateFormat.format("yyyy-MM-dd HH:mm", timeInMillis).toString(); } /** * 取消提醒闹钟 * @param context 上下文 * @param noteId 笔记ID */ public static void cancelAlarm(Context context, int noteId) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context, ReminderReceiver.class); PendingIntent pendingIntent = PendingIntent.getBroadcast( context, noteId, intent, PendingIntent.FLAG_UPDATE_CURRENT ); alarmManager.cancel(pendingIntent); if (activeNoteId == noteId) { stopVibration(); // 同时关闭震动和通知 dismissNotification(context, noteId); } } /** * 创建通知渠道(Android 8.0+需要) * @param context 上下文 */ public static void createNotificationChannel(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH ); channel.setDescription(CHANNEL_DESCRIPTION); channel.enableVibration(true); channel.enableLights(true); channel.setLightColor(Color.RED); channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); channel.setShowBadge(true); NotificationManager notificationManager = context.getSystemService(NotificationManager.class); notificationManager.createNotificationChannel(channel); } } /** * 显示提醒通知 * @param context 上下文 * @param noteId 笔记ID * @param title 通知标题 * @param content 通知内容 */ public static void showNotification(Context context, int noteId, String title, String content) { activeNoteId = noteId; // 保存当前正在震动的笔记ID createNotificationChannel(context); // 创建通知渠道 Intent openIntent = new Intent(context, EditActivity.class); // 创建打开编辑活动的意图 openIntent.putExtra("NOTE_ID", noteId); openIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent openPendingIntent = PendingIntent.getActivity(context, noteId,openIntent, PendingIntent.FLAG_UPDATE_CURRENT ); Intent stopIntent = new Intent(context, StopVibrationActivity.class); // 创建停止震动的意图 stopIntent.putExtra("NOTE_ID", noteId); stopIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); PendingIntent stopPendingIntent = PendingIntent.getActivity( context, noteId + 1000, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT ); Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); //获取默认的通知声音 NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) // 构建通知 .setSmallIcon(R.drawable.ic_notification) .setContentTitle("⚠️ " + title) .setContentText(content) .setPriority(NotificationCompat.PRIORITY_MAX) // 最高优先级 .setCategory(NotificationCompat.CATEGORY_ALARM) .setSound(defaultSoundUri) .setLights(Color.RED, 1000, 500) .setOngoing(true) // 让通知不能被轻易移除 .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setContentIntent(openPendingIntent) .setAutoCancel(false); // 点击后不自动移除通知 builder.addAction( // 添加停止震动按钮 android.R.drawable.ic_delete, // 使用系统内置的删除图标 "停止震动", stopPendingIntent ); try NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); // 显示通知 if (NotificationManagerCompat.from(context).areNotificationsEnabled()) { notificationManager.notify(noteId, builder.build()); // 检查是否开启了通知 startVibration(context); // 执行持续震动 // 显示一个Toast提示,防止用户找不到通知 Toast.makeText(context, "提醒:" + title, Toast.LENGTH_LONG).show(); } else { // 没有通知权限,只执行震动和Toast提示 Toast.makeText(context, "⚠️ 提醒:" + title + "\n(无通知权限,请在设置中授予权限)", Toast.LENGTH_LONG).show(); startVibration(context); showNotificationPermissionToast(context); // 提示用户开启通知权限 } } catch (SecurityException e) { Toast.makeText(context, "无法显示通知,请在设置中授予权限", Toast.LENGTH_LONG).show(); startVibration(context); } catch (Exception e) { Toast.makeText(context, "通知显示失败:" + e.getMessage(), Toast.LENGTH_LONG).show(); startVibration(context); } } /** * 提示用户开启通知权限 */ private static void showNotificationPermissionToast(Context context) { Toast.makeText(context, "请在设置中为记事本开启通知权限,否则将无法收到提醒通知", Toast.LENGTH_LONG).show(); } /** * 关闭通知 * @param context 上下文 * @param noteId 笔记ID */ public static void dismissNotification(Context context, int noteId) { NotificationManagerCompat.from(context).cancel(noteId); } /** * 触发设备持续震动 * @param context 上下文 */ public static void startVibration(Context context) { stopVibration(); // 先停止之前的震动 Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); //创建新的震动 if (vibrator != null && vibrator.hasVibrator()) { activeVibrator = vibrator; // 震动模式:等待时间,震动时间,等待时间,震动时间...(毫秒) // 第一个数字是等待时间,设为0表示立即开始震动 long[] pattern = {0, 2000, 1000, 2000, 1000, 2000, 1000, 2000}; // 较长的震动-暂停模式 try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // 对于Android 8.0及以上设备,使用VibrationEffect // 参数REPEAT表示从pattern的第几个索引开始循环,-1表示不循环 vibrator.vibrate(VibrationEffect.createWaveform(pattern, 0)); // 从索引0开始循环 } else { // 最后一个参数表示重复模式,-1表示不重复,0表示从pattern的第0个索引开始重复 vibrator.vibrate(pattern, 0); // 从索引0开始循环 } } catch (Exception e) { // 处理震动失败的情况 Toast.makeText(context, "震动功能可能不可用", Toast.LENGTH_SHORT).show(); } } else { Toast.makeText(context, "设备不支持震动功能", Toast.LENGTH_SHORT).show(); } } /** * 停止设备震动 */ public static void stopVibration() { if (activeVibrator != null) { try { activeVibrator.cancel(); } catch (Exception e) { // 忽略停止震动时的异常 } finally { activeVibrator = null; } } } /** * 检查是否为停止震动的动作 * @param action 动作字符串 * @return 是否是停止震动动作 */ public static boolean isStopVibrationAction(String action) { return ACTION_STOP_VIBRATION.equals(action); } } 1.7 ReminderReceiver.java文件代码 package cn.itcast.myapplication; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.widget.Toast; /** * 提醒广播接收器,接收提醒闹钟触发的广播并显示通知 */ public class ReminderReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { try { String action = intent.getAction(); // 检查是否是停止震动的请求 if (NotificationHelper.isStopVibrationAction(action)) { // 停止震动 NotificationHelper.stopVibration(); Toast.makeText(context, "震动已停止", Toast.LENGTH_SHORT).show(); return; } int noteId = intent.getIntExtra("NOTE_ID", -1); // 处理正常的提醒 String title = intent.getStringExtra("NOTE_TITLE"); String content = intent.getStringExtra("NOTE_CONTENT"); if (noteId != -1 && title != null) { NotificationHelper.showNotification(context, noteId, "提醒: " + title, content); } else { // 显示通知并震动 Toast.makeText(context, "提醒失败:无效的笔记信息", Toast.LENGTH_SHORT).show(); // 参数无效 } } catch (Exception e) { Toast.makeText(context, "提醒处理失败:" + e.getMessage(), Toast.LENGTH_LONG).show(); // 捕获所有可能的异常 } } } 1.8 StopVibrationActivity.java文件代码 package cn.itcast.myapplication; import android.app.Activity; import android.os.Bundle; import android.widget.Toast; /** * 停止震动的活动 * 这个活动只是一个透明的桥梁,不会显示UI界面 * 作用是停止震动并关闭相关通知,然后立即结束自己 */ public class StopVibrationActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); int noteId = getIntent().getIntExtra("NOTE_ID", -1); // 获取笔记ID NotificationHelper.stopVibration();// 停止震动 if (noteId != -1) { NotificationHelper.dismissNotification(this, noteId); // 移除通知 Toast.makeText(this, "震动已停止", Toast.LENGTH_SHORT).show(); // 显示提示 finish(); } } 在该项目里,简要分析其中的以下代码 public void onItemClick(Note note) { Intent intent = new Intent(MainActivity.this, EditActivity.class); intent.putExtra("NOTE_ID", note.getId()); startActivity(intent); }
05-22
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、付费专栏及课程。

余额充值