添加通知栏

This commit is contained in:
ZhanGSKen
2025-09-30 17:05:34 +08:00
parent edaf7007d6
commit 236339d256
4 changed files with 171 additions and 20 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue Sep 30 08:01:34 GMT 2025
#Tue Sep 30 09:04:55 GMT 2025
stageCount=3
libraryProject=
baseVersion=15.0
publishVersion=15.0.2
buildCount=47
buildCount=59
baseBetaVersion=15.0.3

View File

@@ -32,6 +32,7 @@ import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import cc.winboll.studio.positions.utils.NotificationUtils;
public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
// 常量定义(不变)
@@ -151,6 +152,11 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
bindRealDistance(distanceView, targetModel);
}
}
for (PositionTaskModel task : mAllPositionTasks) {
if (task.isBingo() && task.isEnable()) {
NotificationUtils.show(mContext, task.getTaskId(), task.getPositionId(), task.getTaskDescription());
}
}
}
});
}
@@ -467,11 +473,11 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
// 根据任务条件判断新触发状态
if (task.isGreaterThan()) {
newBingoState = distanceM > task.getDiscussDistance();
newBingoState = task.isEnable() && distanceM > task.getDiscussDistance();
} else if (task.isLessThan()) {
newBingoState = distanceM < task.getDiscussDistance();
newBingoState = task.isEnable() && distanceM < task.getDiscussDistance();
} else {
newBingoState = true;
newBingoState = task.isEnable() && true;
}
// 仅当状态变化时,才更新任务状态并标记需要通知

View File

@@ -0,0 +1,130 @@
package cc.winboll.studio.positions.utils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/09/30 16:09
* @Describe NotificationUtils
*/
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.os.Build;
import androidx.core.app.NotificationCompat;
import cc.winboll.studio.positions.R;
import cc.winboll.studio.positions.activities.LocationActivity; // 替换为你实际的LocationActivity路径
/**
* 通知栏工具类:专注于任务相关通知的显示与点击跳转
* 核心功能显示任务描述通知点击后携带positionId/taskId跳转到LocationActivity
*/
public class NotificationUtils {
public static final String TAG = "NotificationUtils";
// 1. 通知渠道ID适配Android O及以上必须唯一
private static final String TASK_NOTIFICATION_CHANNEL_ID = "task_notification_channel_01";
// 2. 通知渠道名称(用户在系统设置中看到的名称)
private static final String TASK_NOTIFICATION_CHANNEL_NAME = "任务通知";
// 3. 通知ID确保每次显示通知的ID唯一这里用taskId哈希值避免重复
private static int getNotificationId(String taskId) {
// 用taskId生成唯一ID避免不同任务通知相互覆盖
return taskId.hashCode() & 0xFFFFFF; // 限制ID在0-16777215范围内系统推荐
}
/**
* 显示任务通知
* @param context 上下文如Activity/Fragment确保非空
* @param taskId 任务ID用于跳转传递+生成唯一通知ID
* @param positionId 位置ID用于跳转传递给LocationActivity
* @param taskDescription 通知显示内容(仅展示该文本给用户)
*/
public static void show(Context context, String taskId, String positionId, String taskDescription) {
// 安全校验:避免空指针(上下文/必要参数为空时不执行)
if (context == null || taskId == null || positionId == null) {
return;
}
// 步骤1初始化通知管理器系统服务负责发送/取消通知)
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null) {
return;
}
// 步骤2创建通知渠道Android O及以上必须否则通知不显示
createNotificationChannel(notificationManager);
// 步骤3构建“点击通知跳转”的Intent携带positionId和taskId
Intent jumpIntent = new Intent(context, LocationActivity.class);
// 传递参数用Intent的Extra携带ID键名建议定义为常量避免拼写错误
jumpIntent.putExtra("EXTRA_POSITION_ID", positionId);
jumpIntent.putExtra("EXTRA_TASK_ID", taskId);
// 配置Intent清除栈内旧Activity确保跳转后是最新页面根据需求调整
jumpIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
// 步骤4创建PendingIntent授权系统在用户点击通知时执行jumpIntent
// 注意PendingIntent.FLAG_UPDATE_CURRENT 确保参数更新时覆盖旧Intent
PendingIntent pendingIntent = PendingIntent.getActivity(
context,
getNotificationId(taskId), // 请求码与通知ID一致确保唯一
jumpIntent,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ?
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT :
PendingIntent.FLAG_UPDATE_CURRENT
);
// 步骤5构建通知内容仅显示taskDescription样式简洁
Notification notification = new NotificationCompat.Builder(context, TASK_NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher) // 通知小图标(必须设置,建议用应用图标)
.setContentTitle("任务提醒") // 通知标题(固定,用户快速识别通知类型)
.setContentText(taskDescription) // 通知内容(仅显示传入的任务描述)
.setContentIntent(pendingIntent) // 绑定点击跳转事件
.setAutoCancel(true) // 点击通知后自动消失(符合用户习惯)
.setPriority(NotificationCompat.PRIORITY_DEFAULT) // 通知优先级(默认,不抢占焦点)
.setDefaults(NotificationCompat.DEFAULT_SOUND) // 默认提示音(可选,根据需求关闭)
.build();
// 步骤6发送通知用唯一ID确保不同任务通知不覆盖
notificationManager.notify(getNotificationId(taskId), notification);
}
/**
* 创建通知渠道Android O/API 26及以上必须
* 作用:用户可在系统设置中管理该渠道的通知(如关闭声音/屏蔽)
*/
private static void createNotificationChannel(NotificationManager notificationManager) {
// 仅在Android O及以上执行低版本不需要渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 配置渠道:重要性为“默认”(不弹窗,仅通知栏显示+提示音)
NotificationChannel channel = new NotificationChannel(
TASK_NOTIFICATION_CHANNEL_ID,
TASK_NOTIFICATION_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
);
// 可选:配置渠道其他属性(根据需求调整)
channel.setDescription("接收任务相关提醒,点击可查看任务详情"); // 渠道描述(系统设置中显示)
channel.enableVibration(true); // 是否震动(默认开启,可关闭)
channel.setVibrationPattern(new long[]{0, 300}); // 震动模式0延迟震动300ms
// 注册渠道到系统(仅需注册一次,重复注册会覆盖旧配置)
notificationManager.createNotificationChannel(channel);
}
}
/**
* (可选)取消指定任务的通知
* 场景任务完成后关闭已显示的通知如bingo状态取消后
*/
public static void cancel(Context context, String taskId) {
if (context == null || taskId == null) {
return;
}
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null) {
notificationManager.cancel(getNotificationId(taskId));
}
}
}

View File

@@ -212,14 +212,14 @@ public class PositionTaskListView extends LinearLayout {
// 核心:根据 isBingo 控制红点显示true=显示false=隐藏)
simpleHolder.vBingoDot.setVisibility(task.isBingo() ? View.VISIBLE : View.GONE);
}
// 编辑模式:沿用原有绑定逻辑(不变
// 编辑模式:沿用原有绑定逻辑(核心修复在此处
else if (holder instanceof TaskContentViewHolder) {
TaskContentViewHolder contentHolder = (TaskContentViewHolder) holder;
bindTaskData(contentHolder, task, position);
}
}
// ---------------------- 原有编辑模式绑定逻辑(完全不变 ----------------------
// ---------------------- 核心修复:编辑模式绑定逻辑(解决布局中通知异常 ----------------------
private void bindTaskData(final TaskContentViewHolder holder, final PositionTaskModel task, final int position) {
String taskDesc = (task.getTaskDescription() == null) ? "未设置描述" : task.getTaskDescription();
holder.tvTaskDesc.setText(String.format("任务:%s", taskDesc));
@@ -227,6 +227,8 @@ public class PositionTaskListView extends LinearLayout {
String distanceCondition = task.isGreaterThan() ? "大于" : "小于";
holder.tvTaskDistance.setText(String.format("条件:%s %d 米", distanceCondition, task.getDiscussDistance()));
// 修复点1先移除开关监听再设置状态避免设值时触发回调导致异常
holder.cbTaskEnable.setOnCheckedChangeListener(null);
holder.cbTaskEnable.setChecked(task.isEnable());
holder.cbTaskEnable.setEnabled(mCurrentViewMode == VIEW_MODE_EDIT);
@@ -234,6 +236,7 @@ public class PositionTaskListView extends LinearLayout {
holder.btnEditTask.setVisibility(View.VISIBLE);
holder.btnDeleteTask.setVisibility(View.VISIBLE);
// 删除按钮逻辑(不变,本身在点击时执行,不涉及布局中通知)
holder.btnDeleteTask.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -247,6 +250,7 @@ public class PositionTaskListView extends LinearLayout {
}
});
// 编辑按钮逻辑(不变,弹窗保存时修复通知时机)
holder.btnEditTask.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -254,15 +258,21 @@ public class PositionTaskListView extends LinearLayout {
}
});
holder.cbTaskEnable.setOnCheckedChangeListener(null);
// 修复点2开关监听-用 RecyclerView.post 延迟执行 notify避免布局中调用
holder.cbTaskEnable.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
task.setIsEnable(isChecked);
notifyItemChanged(position);
if (mOnTaskUpdatedListener != null && mBindPositionId != null) {
mOnTaskUpdatedListener.onTaskUpdated(mBindPositionId, new ArrayList<PositionTaskModel>(mData));
}
task.setIsEnable(isChecked); // 先更新数据源(必须执行)
// 关键:通过 mRvTasks.post 延迟通知,确保在布局计算/滚动结束后执行
mRvTasks.post(new Runnable() {
@Override
public void run() {
notifyItemChanged(position);
if (mOnTaskUpdatedListener != null && mBindPositionId != null) {
mOnTaskUpdatedListener.onTaskUpdated(mBindPositionId, new ArrayList<PositionTaskModel>(mData));
}
}
});
}
});
} else {
@@ -272,7 +282,7 @@ public class PositionTaskListView extends LinearLayout {
}
}
// ---------------------- 原有编辑弹窗逻辑(完全不变 ----------------------
// ---------------------- 修复:编辑弹窗保存逻辑(延迟通知,避免极端场景异常 ----------------------
private void showTaskEditDialog(final PositionTaskModel task, final int position) {
final Context context = getContext();
View dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_edit_task, null);
@@ -335,11 +345,16 @@ public class PositionTaskListView extends LinearLayout {
task.setIsGreaterThan(isGreater);
task.setPositionId(mBindPositionId);
notifyItemChanged(position);
if (mOnTaskUpdatedListener != null && mBindPositionId != null) {
mOnTaskUpdatedListener.onTaskUpdated(mBindPositionId, new ArrayList<PositionTaskModel>(mData));
}
// 修复点3弹窗保存后延迟通知同开关逻辑避免列表滚动时异常
mRvTasks.post(new Runnable() {
@Override
public void run() {
notifyItemChanged(position);
if (mOnTaskUpdatedListener != null && mBindPositionId != null) {
mOnTaskUpdatedListener.onTaskUpdated(mBindPositionId, new ArrayList<PositionTaskModel>(mData));
}
}
});
dialog.dismiss();
Toast.makeText(context, "任务已更新", Toast.LENGTH_SHORT).show();
@@ -347,7 +362,7 @@ public class PositionTaskListView extends LinearLayout {
});
}
// ---------------------- ViewHolder 定义(新增简单模式 Holder适配红点 ----------------------
// ---------------------- ViewHolder 定义(完全不变 ----------------------
// 基础抽象 ViewHolder不变
public abstract class TaskViewHolder extends RecyclerView.ViewHolder {
public TaskViewHolder(@NonNull View itemView) {