@@ -3,7 +3,7 @@ package cc.winboll.studio.positions.views;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/09/30 08:09
* @Describe 位置任务列表视图(支持简单/编辑模式,含 isBingo 红点标识 )
* @Describe 位置任务列表视图(适配MainService唯一数据源+同步任务状态+ 支持简单/编辑模式)
*/
import android.content.Context ;
import android.util.AttributeSet ;
@@ -22,8 +22,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable ;
import androidx.recyclerview.widget.LinearLayoutManager ;
import androidx.recyclerview.widget.RecyclerView ;
import cc.winboll.studio.libappbase.LogUtils ;
import cc.winboll.studio.positions.R ;
import cc.winboll.studio.positions.models.PositionTaskModel ;
import cc.winboll.studio.positions.services.MainService ;
import java.util.ArrayList ;
import java.util.List ;
@@ -32,21 +34,21 @@ public class PositionTaskListView extends LinearLayout {
public static final int VIEW_MODE_SIMPLE = 1 ;
public static final int VIEW_MODE_EDIT = 2 ;
// 核心成员变量
// 核心成员变量( 新增MainService引用, 作为唯一数据源)
private String mBindPositionId ;
private ArrayList < PositionTaskModel > mTaskList ;
private MainService mMainService ; // 持有MainService实例, 所有任务数据从服务获取
private int mCurrentViewMode ;
private TaskListAdapter mTaskAdapter ;
private RecyclerView mRvTasks ;
// 任务修改回调接口
// 任务修改回调接口( 保留, 用于通知外部同步UI)
public interface OnTaskUpdatedListener {
void onTaskUpdated ( String positionId , ArrayList < PositionTaskModel > updatedTasks ) ;
}
private OnTaskUpdatedListener mOnTaskUpdatedListener ;
// ---------------------- 构造函数 ----------------------
// ---------------------- 构造函数( 不变, 新增MainService空校验) ----------------------
public PositionTaskListView ( Context context ) {
super ( context ) ;
initView ( context ) ;
@@ -62,7 +64,7 @@ public class PositionTaskListView extends LinearLayout {
initView ( context ) ;
}
// 初始化视图(绑定控件+设置布局)
// 初始化视图(绑定控件+设置布局, Adapter初始化为空数据)
private void initView ( Context context ) {
setOrientation ( VERTICAL ) ;
LayoutInflater . from ( context ) . inflate ( R . layout . view_position_task_list , this , true ) ;
@@ -70,94 +72,204 @@ public class PositionTaskListView extends LinearLayout {
mRvTasks = ( RecyclerView ) findViewById ( R . id . rv_position_tasks ) ;
mRvTasks . setLayoutManager ( new LinearLayoutManager ( context ) ) ;
mTaskList = new ArrayList < PositionTaskModel > ( ) ;
mTaskAdapter = new TaskListAdapter ( mTaskList ) ;
// 初始化为空列表( 数据后续从MainService同步)
mTaskAdapter = new TaskListAdapter ( new ArrayList < PositionTaskModel > ( ) ) ;
mRvTasks . setAdapter ( mTaskAdapter ) ;
mCurrentViewMode = VIEW_MODE_SIMPLE ;
LogUtils . d ( TAG , " 视图初始化完成( 等待绑定MainService和位置ID) " ) ;
}
// ---------------------- 对外API ----------------------
public void init ( ArrayList < PositionTaskModel > taskList , String positionId ) {
// ---------------------- 对外API( 核心调整: 绑定MainService+从服务同步数据) ----------------------
/**
* 初始化: 绑定MainService+关联位置ID( 必须先调用此方法, 否则无数据)
* @param mainService MainService实例( 从Activity传入, 确保唯一数据源)
* @param positionId 关联的位置ID( 只加载该位置下的任务)
*/
public void init ( MainService mainService , String positionId ) {
if ( mainService = = null ) {
LogUtils . e ( TAG , " init失败: MainService实例为空( 需从Activity传入有效服务实例) " ) ;
showToast ( " 任务列表初始化失败:服务未就绪 " ) ;
return ;
}
if ( positionId = = null | | positionId . trim ( ) . isEmpty ( ) ) {
LogUtils . e ( TAG , " init失败: 位置ID为空( 需关联有效位置) " ) ;
showToast ( " 任务列表初始化失败:未关联位置 " ) ;
return ;
}
// 绑定服务实例+位置ID
this . mMainService = mainService ;
this . mBindPositionId = positionId ;
if ( this . mTaskList . isEmpty ( ) ) {
ArrayList < PositionTaskModel > matchedTasks = new ArrayList < PositionTaskModel > ( ) ;
if ( taskList ! = null & & ! taskList . isEmpty ( ) ) {
for ( PositionTaskModel task : taskList ) {
if ( task ! = null & & positionId . equals ( task . getPositionId ( ) ) ) {
matchedTasks . add ( task ) ;
}
}
}
mTaskList . clear ( ) ;
mTaskList . addAll ( matchedTasks ) ;
}
mTaskAdapter . notifyDataSetChanged ( ) ;
LogUtils . d ( TAG , " 已绑定MainService和位置ID: " + positionId ) ;
// 从MainService同步当前位置的任务( 核心: 数据来源改为服务)
syncTasksFromMainService ( ) ;
}
/**
* 从MainService同步当前位置的任务( 核心方法, 所有数据加载入口)
* 作用: 1. 清空本地缓存→2. 从服务获取全量任务→3. 筛选当前位置任务→4. 刷新Adapter
*/
public void syncTasksFromMainService ( ) {
// 安全校验(服务未绑定/位置ID为空, 不执行同步)
if ( mMainService = = null | | mBindPositionId = = null | | mBindPositionId . trim ( ) . isEmpty ( ) ) {
LogUtils . w ( TAG , " 同步任务失败: MainService未绑定或位置ID无效 " ) ;
return ;
}
try {
// 1. 从MainService获取全量任务( 服务是唯一数据源, 避免本地缓存不一致)
ArrayList < PositionTaskModel > allServiceTasks = mMainService . getAllTasks ( ) ;
LogUtils . d ( TAG , " 从MainService获取全量任务数: " + ( allServiceTasks = = null ? 0 : allServiceTasks . size ( ) ) ) ;
// 2. 筛选当前位置关联的任务( 只保留与mBindPositionId匹配的任务)
ArrayList < PositionTaskModel > currentPosTasks = new ArrayList < PositionTaskModel > ( ) ;
if ( allServiceTasks ! = null & & ! allServiceTasks . isEmpty ( ) ) {
for ( PositionTaskModel task : allServiceTasks ) {
if ( isTaskMatchedWithPosition ( task ) ) {
currentPosTasks . add ( task ) ;
}
}
}
LogUtils . d ( TAG , " 筛选后当前位置任务数: " + currentPosTasks . size ( ) ) ;
// 3. 更新Adapter数据( 直接替换数据源, 避免本地缓存)
mTaskAdapter . updateData ( currentPosTasks ) ;
LogUtils . d ( TAG , " 从MainService同步任务完成( Adapter已刷新) " ) ;
} catch ( Exception e ) {
LogUtils . d ( TAG , " 同步任务失败( MainService调用异常) : " + e . getMessage ( ) ) ;
showToast ( " 任务同步失败,请重试 " ) ;
}
}
// 视图模式切换( 不变, 刷新Adapter触发视图类型变更)
public void setViewStatus ( int viewMode ) {
if ( viewMode ! = VIEW_MODE_SIMPLE & & viewMode ! = VIEW_MODE_EDIT ) {
LogUtils . w ( TAG , " 设置视图模式失败:无效模式(仅支持简单/编辑模式) " ) ;
return ;
}
mCurrentViewMode = viewMode ;
mTaskAdapter . notifyDataSetChanged ( ) ;
LogUtils . d ( TAG , " 已切换视图模式: " + ( viewMode = = VIEW_MODE_SIMPLE ? " 简单模式 " : " 编辑模式 " ) ) ;
}
// 保留回调接口( 用于通知外部UI刷新, 如Activity更新列表)
public void setOnTaskUpdatedListener ( OnTaskUpdatedListener listener ) {
this . mOnTaskUpdatedListener = listener ;
LogUtils . d ( TAG , " 已设置任务更新回调监听 " ) ;
}
public ArrayList < PositionTaskModel > getAllTasks ( ) {
return new ArrayList < PositionTaskModel > ( mTaskList ) ;
/**
* 获取当前位置的任务( 从Adapter数据源获取, 而非本地缓存)
* @return 当前位置任务列表(新集合,避免外部修改数据源)
*/
public ArrayList < PositionTaskModel > getCurrentPosTasks ( ) {
return new ArrayList < PositionTaskModel > ( mTaskAdapter . getAdapterData ( ) ) ;
}
/**
* 清空数据(解绑服务+清空位置ID+重置Adapter)
* 场景:视图销毁/切换位置时调用,避免数据残留
*/
public void clearData ( ) {
mTaskList . clear ( ) ;
if ( mTaskAdapter ! = null & & mTaskAdapter . mData ! = null ) {
mTaskAdapter . mData . clear ( ) ;
}
mTaskAdapter . notifyDataSetChanged ( ) ;
mBindPositionId = null ;
// 1. 清空Adapter数据源
mTaskAdapter . updateData ( new ArrayList < PositionTaskModel > ( ) ) ;
// 2. 解绑服务+位置ID( 避免下次使用时引用旧数据)
this . mMainService = null ;
this . mBindPositionId = null ;
// 3. 重置视图模式
mCurrentViewMode = VIEW_MODE_SIMPLE ;
LogUtils . d ( TAG , " 数据已清空(解绑服务+重置视图) " ) ;
}
/**
* 主动触发任务同步(强制从服务拉取最新数据,刷新视图)
* 场景:外部操作后(如新增任务),调用此方法更新列表
*/
public void triggerTaskSync ( ) {
LogUtils . d ( TAG , " 主动触发任务同步( 从MainService拉取最新数据) " ) ;
syncTasksFromMainService ( ) ;
// 通知外部( 如Activity) 任务已更新( 可选, 根据业务需求)
if ( mOnTaskUpdatedListener ! = null & & mBindPositionId ! = null ) {
mOnTaskUpdatedListener . onTaskUpdated ( mBindPositionId , new ArrayList < PositionTaskModel > ( mTaskList ) ) ;
mOnTaskUpdatedListener . onTaskUpdated ( mBindPositionId , getCurrentPosTasks ( ) ) ;
}
}
// ---------------------- 内部工具方法 ----------------------
// ---------------------- 内部工具方法(新增服务空校验) ----------------------
private static final String TAG = " PositionTaskListView " ;
/**
* 校验任务是否与当前绑定的位置匹配
* @param task 待校验的任务
* @return true=匹配( 任务位置ID=当前绑定位置ID) , false=不匹配
*/
private boolean isTaskMatchedWithPosition ( PositionTaskModel task ) {
if ( task = = null | | mBindPositionId = = null | | mBindPositionId . trim ( ) . isEmpty ( ) ) {
return false ;
}
// 严格匹配任务的位置ID( 确保只加载当前位置的任务)
return mBindPositionId . equals ( task . getPositionId ( ) ) ;
}
// ---------------------- 内部Adapter: 适配 isBingo 红点(核心调整) ----------------------
private class TaskListAdapter extends RecyclerView . Adapter < TaskListAdapter . TaskViewHolder > {
private final List < PositionTaskModel > mData ;
public TaskListAdapter ( List < PositionTaskModel > data ) {
this . mData = data ;
/**
* 显示Toast( 简化调用, 避免重复代码)
*/
private void showToast ( String content ) {
if ( getContext ( ) = = null ) return ;
Toast . makeText ( getContext ( ) , content , Toast . LENGTH_SHORT ) . show ( ) ;
}
// ---------------------- 内部Adapter: 适配MainService数据源( 核心调整) ----------------------
private class TaskListAdapter extends RecyclerView . Adapter < TaskListAdapter . TaskViewHolder > {
// Adapter数据源( 仅保留一份, 直接从MainService同步, 无本地冗余)
private List < PositionTaskModel > mAdapterData ;
// 初始化Adapter( 空数据源)
public TaskListAdapter ( List < PositionTaskModel > data ) {
this . mAdapterData = new ArrayList < PositionTaskModel > ( data ) ; // 防御性拷贝,避免外部修改
}
/**
* 更新Adapter数据源( 核心: 从MainService同步后调用, 替换数据源并刷新)
* @param newData 从MainService筛选后的当前位置任务列表
*/
public void updateData ( List < PositionTaskModel > newData ) {
if ( newData = = null ) {
this . mAdapterData . clear ( ) ;
} else {
this . mAdapterData = new ArrayList < PositionTaskModel > ( newData ) ; // 防御性拷贝
}
notifyDataSetChanged ( ) ; // 刷新列表(数据源已替换,确保显示最新数据)
}
/**
* 获取Adapter当前数据源( 对外提供, 避免直接操作mAdapterData)
* @return 当前数据源(新集合,避免外部修改)
*/
public List < PositionTaskModel > getAdapterData ( ) {
return new ArrayList < PositionTaskModel > ( mAdapterData ) ;
}
// getItemCount: 空列表显示1个“空提示”项, 非空显示任务数
@Override
public int getItemCount ( ) {
return mData . isEmpty ( ) ? 1 : mData . size ( ) ;
return mAdapter Data . isEmpty ( ) ? 1 : mAdapter Data . size ( ) ;
}
// 调整:根据“是否空列表”+“视图模式”区分视图类型(确保简单/编辑模式加载对应布局 )
// getItemViewType: 按“空列表/简单模式/编辑模式”区分视图类型(不变 )
@Override
public int getItemViewType ( int position ) {
if ( mData . isEmpty ( ) ) {
return 0 ; // 0=空提示
if ( mAdapter Data . isEmpty ( ) ) {
return 0 ; // 0=空提示视图
} else {
return mCurrentViewMode ; // 1=简单模式, 2=编辑模式(复用视图模式常量)
return mCurrentViewMode ; // 1=简单模式视图 , 2=编辑模式视图
}
}
// 调整 :按视图类型加载布局(简单模式加载带红点的布局,编辑模式加载原有布局 )
// onCreateViewHolder :按视图类型加载对应 布局(不变,确保布局与模式匹配 )
@NonNull
@Override
public TaskViewHolder onCreateViewHolder ( @NonNull ViewGroup parent , int viewType ) {
@@ -165,150 +277,219 @@ public class PositionTaskListView extends LinearLayout {
LayoutInflater inflater = LayoutInflater . from ( context ) ;
if ( viewType = = 0 ) {
// 空提示布局
// 空提示布局(无任务时显示)
View emptyView = inflater . inflate ( R . layout . item_task_empty , parent , false ) ;
return new EmptyViewHolder ( emptyView ) ;
} else if ( viewType = = VIEW_MODE_SIMPLE ) {
// 简单模式布局(带 isBingo 红点)
// 简单模式布局( 带isBingo红点,仅展示 )
View simpleTaskView = inflater . inflate ( R . layout . item_position_task_simple , parent , false ) ;
return new SimpleTaskViewHolder ( simpleTaskView ) ;
} else {
// 编辑模式布局(原有布局不变 )
// 编辑模式布局(带编辑/删除按钮+启用开关,支持修改 )
View editTaskView = inflater . inflate ( R . layout . item_task_content , parent , false ) ;
return new TaskContentViewHolder ( editTaskView ) ;
}
}
// 调整:按视图类型绑定数据(简单模式绑定红点+文本,编辑模式绑定原有逻辑 )
// onBindViewHolder: 按视图类型绑定数据( 核心调整: 操作后同步MainService )
@Override
public void onBindViewHolder ( @NonNull TaskViewHolder holder , int position ) {
// 空提示处理(不变 )
// 1. 空提示视图绑定(根据模式显示不同提示文案 )
if ( holder instanceof EmptyViewHolder ) {
EmptyViewHolder emptyHolder = ( EmptyViewHolder ) holder ;
TextView tvEmptyTip = ( TextView ) emptyHolder . itemView . findViewById ( R . id . tv_task_empty_tip ) ;
tvEmptyTip . setText ( mCurrentViewMode = = VIEW_MODE_EDIT ? " 暂无任务,点击 \" 添加新任务 \" 创建 " : " 暂无启用的任务 " ) ;
TextView tvEmptyTip = emptyHolder . itemView . findViewById ( R . id . tv_task_empty_tip ) ;
tvEmptyTip . setText ( mCurrentViewMode = = VIEW_MODE_EDIT
? " 暂无任务,点击 \" 添加新任务 \" 创建 "
: " 暂无启用的任务 " ) ;
return ;
}
// 任务项有效性校验(不变 )
if ( position > = mData . size ( ) ) {
// 2. 任务项有效性校验(避免越界/空数据 )
if ( position > = mAdapter Data . size ( ) ) {
LogUtils . w ( TAG , " 绑定任务数据失败: 位置索引越界( position= " + position + " ,数据量= " + mAdapterData . size ( ) + " ) " ) ;
return ;
}
final PositionTaskModel task = mData . get ( position ) ;
final PositionTaskModel task = mAdapter Data . get ( position ) ;
if ( task = = null ) {
LogUtils . w ( TAG , " 绑定任务数据失败:第 " + position + " 项任务为空 " ) ;
return ;
}
// 简单模式: 绑定红点( isBingo) 和文本数据
// 3. 简单模式绑定(仅展示,无修改操作,不变)
if ( holder instanceof SimpleTaskViewHolder ) {
SimpleTaskViewHolder simpleHolder = ( SimpleTaskViewHolder ) holder ;
// 绑定 任务描述
simpleHolder . tvSimpleTaskDesc . s etText ( String . format ( " 任务:%s " , task . getTaskDescription ( ) ) ) ;
// 绑定距离条件
// 任务描述
String taskDesc = task . g etTaskDescription ( ) = = null ? " 未设置描述 " : task . getTaskDescription ( ) ;
simpleHolder . tvSimpleTaskDesc . setText ( String . format ( " 任务:%s " , taskDesc ) ) ;
// 距离条件(大于/小于+距离值)
String distanceCond = task . isGreaterThan ( ) ? " 大于 " : " 小于 " ;
simpleHolder . tvSimpleDistanceCond . setText ( String . format ( " 条件:距离 %s %d 米 " , distanceCond , task . getDiscussDistance ( ) ) ) ;
// 绑定 启用状态
// 启用状态
simpleHolder . tvSimpleIsEnable . setText ( task . isEnable ( ) ? " 状态:已启用 " : " 状态:已禁用 " ) ;
// 核心:根据 isBingo 控制红点显示( true=显示, false= 隐藏)
// isBingo红点(任务触发时显示,未触发时 隐藏)
simpleHolder . vBingoDot . setVisibility ( task . isBingo ( ) ? View . VISIBLE : View . GONE ) ;
}
// 编辑模式:沿用原有绑定逻辑(核心修复在此处)
// 4. 编辑模式绑定( 核心调整: 所有修改操作后同步MainService)
else if ( holder instanceof TaskContentViewHolder ) {
TaskContentViewHolder contentHolder = ( TaskContentViewHolder ) holder ;
bindTaskData ( contentHolder , task , position ) ;
bindEditMode Task ( contentHolder , task , position ) ;
}
}
// ---------------------- 核心修复:编辑模式绑定逻辑(解决布局中通知异常) ----------------------
private void bindTaskData ( final TaskContentViewHolder holder , final PositionTaskModel task , final int position ) {
String taskDesc = ( task . getTaskDescription ( ) = = null ) ? " 未设置描述 " : task . getTaskDescription ( ) ;
/**
* 编辑模式任务绑定( 核心: 修改操作→更新MainService→刷新Adapter)
*/
private void bindEditModeTask ( final TaskContentViewHolder holder , final PositionTaskModel task , final int position ) {
// 4.1 绑定基础数据(描述+距离条件)
String taskDesc = task . getTaskDescription ( ) = = null ? " 未设置描述 " : task . getTaskDescription ( ) ;
holder . tvTaskDesc . setText ( String . format ( " 任务:%s " , taskDesc ) ) ;
String distanceCond = task . isGreaterThan ( ) ? " 大于 " : " 小于 " ;
holder . tvTaskDistance . setText ( String . format ( " 条件:%s %d 米 " , distanceCond , task . getDiscussDistance ( ) ) ) ;
String distanceCondition = task . isGreaterThan ( ) ? " 大于 " : " 小于 " ;
holder . tvTaskDistance . setText ( String . format ( " 条件:%s %d 米 " , distanceCondition , task . getDiscussDistance ( ) ) ) ;
// 修复点1: 先移除开关监听, 再设置状态( 避免设值时触发回调导致异常)
// 4.2 绑定“启用开关”(修复:先解绑监听→设值→再绑定监听,避免设值触发回调)
holder . cbTaskEnable . setOnCheckedChangeListener ( null ) ;
holder . cbTaskEnable . setChecked ( task . isEnable ( ) ) ;
holder . cbTaskEnable . setEnabled ( mCurrentViewMode = = VIEW_MODE_EDIT ) ;
holder . cbTaskEnable . setEnabled ( mCurrentViewMode = = VIEW_MODE_EDIT ) ; // 编辑模式才允许操作
if ( mCurrentViewMode = = VIEW_MODE_EDIT ) {
// 4.3 编辑模式特有:显示编辑/删除按钮(仅编辑模式可见)
holder . btnEditTask . setVisibility ( View . VISIBLE ) ;
holder . btnDeleteTask . setVisibility ( View . VISIBLE ) ;
// 删除按钮逻辑(不变,本身在点击时执行,不涉及布局中通知 )
// 4.4 删除按钮逻辑( 核心: 删除→同步MainService→刷新Adapter )
holder . btnDeleteTask . setOnClickListener ( new View . OnClickListener ( ) {
@Override
public void onClick ( View v ) {
mData . remove ( position ) ;
notifyItemRemoved ( position ) ;
notifyItemRangeChanged ( position , mData . size ( ) ) ;
if ( mOnTaskUpdatedListener ! = null & & mBindPositionId ! = null ) {
mOnTaskUpdatedListener . onTaskUpdated ( mBindPositionId , new ArrayList < PositionTaskModel > ( mData ) ) ;
if ( mMainService = = null ) {
showToast ( " 删除失败:服务未就绪 " ) ;
LogUtils . e ( TAG , " 删除任务失败: MainService实例为空 " ) ;
return ;
}
try {
// 步骤1: 调用MainService删除任务( 服务是唯一数据源, 确保数据一致性)
mMainService . deleteTask ( task . getTaskId ( ) ) ; // 需在MainService中实现deleteTask()方法(删除服务内全量任务列表中的对应项)
LogUtils . d ( TAG , " 调用MainService删除任务: ID= " + task . getTaskId ( ) + " ( 位置ID=" + mBindPositionId + " ) " ) ;
// 步骤2: 从Adapter数据源移除任务( 避免等待同步, 立即反馈UI)
mAdapterData . remove ( position ) ;
// 步骤3: 刷新Adapter( 局部刷新+范围通知,避免列表错乱)
notifyItemRemoved ( position ) ;
notifyItemRangeChanged ( position , mAdapterData . size ( ) ) ;
LogUtils . d ( TAG , " Adapter已移除任务, 刷新列表( 位置索引= " + position + " ) " ) ;
// 步骤4: 通知外部( 如Activity) 任务已更新
if ( mOnTaskUpdatedListener ! = null & & mBindPositionId ! = null ) {
mOnTaskUpdatedListener . onTaskUpdated ( mBindPositionId , new ArrayList < PositionTaskModel > ( mAdapterData ) ) ;
}
showToast ( " 任务已删除(已同步至服务) " ) ;
} catch ( Exception e ) {
LogUtils . d ( TAG , " 删除任务失败(服务调用/Adapter刷新异常) : " + e . getMessage ( ) ) ;
showToast ( " 删除失败,请重试 " ) ;
// 异常时重新同步数据( 确保Adapter与服务一致)
syncTasksFromMainService ( ) ;
}
Toast . makeText ( getContext ( ) , " 任务已删除 " , Toast . LENGTH_SHORT ) . show ( ) ;
}
} ) ;
// 编辑按钮逻辑(不变,弹窗保存时修复通知时机 )
// 4.5 编辑按钮逻辑(核心: 修改后同步MainService→刷新Adapter )
holder . btnEditTask . setOnClickListener ( new View . OnClickListener ( ) {
@Override
public void onClick ( View v ) {
showTaskEditDialog ( task , position ) ;
if ( mMainService = = null ) {
showToast ( " 编辑失败:服务未就绪 " ) ;
LogUtils . e ( TAG , " 编辑任务失败: MainService实例为空 " ) ;
return ;
}
// 弹出编辑弹窗(修改后同步服务)
showEditTaskDialog ( task , position ) ;
}
} ) ;
// 修复点2: 开关监听-用 RecyclerView.post 延迟执行 notify( 避免布局中调用 )
// 4.6 启用开关逻辑( 核心: 状态变更→同步MainService→刷新Adapter )
holder . cbTaskEnable . setOnCheckedChangeListener ( new CompoundButton . OnCheckedChangeListener ( ) {
@Override
public void onCheckedChanged ( CompoundButton buttonView , boolean isChecked ) {
task . setIsEnable ( isChecked ) ; // 先更新数据源(必须执行)
// 关键:通过 mRvTasks.post 延迟通知,确保在布局计算/滚动结束后执行
public void onCheckedChanged ( CompoundButton buttonView , final boolean isChecked ) {
if ( mMainService = = null ) {
showToast ( " 状态修改失败:服务未就绪 " ) ;
LogUtils . e ( TAG , " 修改任务启用状态失败: MainService实例为空 " ) ;
// 回滚开关状态( 避免UI与服务不一致)
buttonView . setChecked ( ! isChecked ) ;
return ;
}
try {
// 步骤1: 更新任务状态( 先改内存数据, 确保UI反馈及时)
task . setIsEnable ( isChecked ) ;
LogUtils . d ( TAG , " 更新任务启用状态: ID= " + task . getTaskId ( ) + " ,新状态= " + ( isChecked ? " 启用 " : " 禁用 " ) ) ;
// 步骤2: 调用MainService同步状态( 服务是唯一数据源, 持久化变更)
mMainService . updateTaskStatus ( task ) ; // 需在MainService中实现updateTaskStatus()方法( 更新服务内任务的isEnable字段)
LogUtils . d ( TAG , " 调用MainService同步任务状态( 已 " + ( isChecked ? " 启用 " : " 禁用 " ) + " ) " ) ;
// 步骤3: 延迟刷新Adapter( 避免列表滚动/布局计算时异常)
mRvTasks . post ( new Runnable ( ) {
@Override
public void run ( ) {
notifyItemChanged ( position ) ;
// 通知外部任务已更新
if ( mOnTaskUpdatedListener ! = null & & mBindPositionId ! = null ) {
mOnTaskUpdatedListener . onTaskUpdated ( mBindPositionId , new ArrayList < PositionTaskModel > ( mData ) ) ;
mOnTaskUpdatedListener . onTaskUpdated ( mBindPositionId , new ArrayList < PositionTaskModel > ( mAdapter Data ) ) ;
}
}
} ) ;
} catch ( Exception e ) {
LogUtils . d ( TAG , " 修改任务启用状态失败: " + e . getMessage ( ) ) ;
showToast ( " 状态修改失败,请重试 " ) ;
// 回滚状态( UI与服务保持一致)
buttonView . setChecked ( ! isChecked ) ;
task . setIsEnable ( ! isChecked ) ;
// 重新同步数据(修复可能的不一致)
syncTasksFromMainService ( ) ;
}
}
} ) ;
} else {
holder . btnEditTask . setVisibility ( View . GONE ) ;
holder . btnDeleteTask . setVisibility ( View . GONE ) ;
holder . cbTaskEnable . setOnCheckedChangeListener ( null ) ;
}
}
// ---------------------- 修复:编辑弹窗保存逻辑(延迟通知,避免极端场景异常) ----------------------
private void showTaskEditDialog ( final PositionTaskModel task , final int position ) {
final Context context = getContext ( ) ;
/**
* 编辑任务弹窗( 核心: 保存修改→同步MainService→刷新Adapter)
*/
private void showEditTaskDialog ( final PositionTaskModel task , final int position ) {
Context context = getContext ( ) ;
if ( context = = null ) {
LogUtils . w ( TAG , " 编辑弹窗无法显示:上下文为空 " ) ;
return ;
}
// 加载弹窗布局
View dialogView = LayoutInflater . from ( context ) . inflate ( R . layout . dialog_edit_task , null ) ;
final EditText etEditDesc = dialogView . findViewById ( R . id . et_edit_task_desc ) ;
final RadioGroup rgDistanceCondition = dialogView . findViewById ( R . id . rg_distance_condition ) ;
final EditText etEditDistance = dialogView . findViewById ( R . id . et_edit_distance ) ;
Button btnCancel = dialogView . findViewById ( R . id . btn_dialog_cancel ) ;
Button btnSave = dialogView . findViewById ( R . id . btn_dialog_save ) ;
final EditText etEditDesc = ( EditText ) dialogView . findViewById ( R . id . et_edit_task_desc ) ;
final RadioGroup rgDistanceCondition = ( RadioGroup ) dialogView . findViewById ( R . id . rg_distance_condition ) ;
final EditText etEditDistance = ( EditText ) dialogView . findViewById ( R . id . et_edit_distance ) ;
Button btnCancel = ( Button ) dialogView . findViewById ( R . id . btn_dialog_cancel ) ;
Button btnSave = ( Button ) dialogView . findViewById ( R . id . btn_dialog_save ) ;
etEditDesc . setText ( task . getTaskDescription ( ) ) ;
etEditDesc . setSelection ( etEditDesc . getText ( ) . length ( ) ) ;
// 初始化弹窗数据(填充当前任务信息)
etEditDesc . setText ( task . getTaskDescription ( ) = = null ? " " : task . getTaskDescription ( ) ) ;
etEditDesc . setSelection ( etEditDesc . getText ( ) . length ( ) ) ; // 光标定位到末尾
// 初始化距离条件(大于/小于)
if ( task . isGreaterThan ( ) ) {
rgDistanceCondition . check ( R . id . rb_greater_than ) ;
} else {
rgDistanceCondition . check ( R . id . rb_less_than ) ;
}
etEditDistance . setText ( String . valueOf ( task . getDiscussDistance ( ) ) ) ; // 填充当前距离
etEditDistance . setText ( String . valueOf ( task . getDiscussDistance ( ) ) ) ;
// 创建并显示弹窗
final android . app . AlertDialog dialog = new android . app . AlertDialog . Builder ( context )
. setView ( dialogView )
. setCancelable ( false ) // 不允许点击外部关闭(避免未保存退出)
. create ( ) ;
dialog . show ( ) ;
// 取消按钮:关闭弹窗(不做操作)
btnCancel . setOnClickListener ( new View . OnClickListener ( ) {
@Override
public void onClick ( View v ) {
@@ -316,14 +497,17 @@ public class PositionTaskListView extends LinearLayout {
}
} ) ;
// 保存按钮: 校验输入→更新任务→同步服务→刷新UI
btnSave . setOnClickListener ( new View . OnClickListener ( ) {
@Override
public void onClick ( View v ) {
// 1. 输入校验(避免无效数据)
String newDesc = etEditDesc . getText ( ) . toString ( ) . trim ( ) ;
String distanceStr = etEditDistance . getText ( ) . toString ( ) . trim ( ) ;
if ( distanceStr . isEmpty ( ) ) {
Toast . makeText ( context , " 请输入有效距离 " , Toast . LENGTH_SHORT ) . show ( ) ;
show Toast( " 请输入有效距离( 1米及以上) " ) ;
etEditDistance . requestFocus ( ) ;
return ;
}
@@ -331,62 +515,80 @@ public class PositionTaskListView extends LinearLayout {
try {
newDistance = Integer . parseInt ( distanceStr ) ;
if ( newDistance < 1 ) {
Toast . makeText ( context , " 距离不能小于1米 " , Toast . LENGTH_SHORT ) . show ( ) ;
show Toast( " 距离不能小于1米 " ) ;
etEditDistance . requestFocus ( ) ;
return ;
}
} catch ( NumberFormatException e ) {
Toast . makeText ( context , " 距离请输入数字 " , Toast . LENGTH_SHORT ) . show ( ) ;
show Toast( " 距离请输入数字 " ) ;
etEditDistance . requestFocus ( ) ;
return ;
}
task . setTaskDescription ( newDesc ) ;
task . setDiscussDistance ( newDistance ) ;
// 2. 收集新数据(更新任务对象)
task . setTaskDescription ( newDesc ) ; // 新描述
task . setDiscussDistance ( newDistance ) ; // 新距离
boolean isGreater = rgDistanceCondition . getCheckedRadioButtonId ( ) = = R . id . rb_greater_than ;
task . setIsGreaterThan ( isGreater ) ;
task . setPositionId ( mBindPositionId ) ;
task . setIsGreaterThan ( isGreater ) ; // 新距离条件(大于/小于)
task . setPositionId ( mBindPositionId ) ; // 确保位置ID不变( 防止错位)
// 修复点3: 弹窗保存后延迟通知( 同开关逻辑, 避免列表滚动时异常)
try {
// 3. 调用MainService同步修改( 服务是唯一数据源, 持久化变更)
mMainService . updateTask ( task ) ; // 需在MainService中实现updateTask()方法(更新服务内任务的字段)
LogUtils . d ( TAG , " 调用MainService更新任务: ID= " + task . getTaskId ( ) + " (描述= " + newDesc + " ,距离= " + newDistance + " 米) " ) ;
// 4. 更新Adapter数据源( 立即反馈UI)
mAdapterData . set ( position , task ) ;
// 5. 延迟刷新Adapter( 避免弹窗未关闭时布局异常)
mRvTasks . post ( new Runnable ( ) {
@Override
public void run ( ) {
notifyItemChanged ( position ) ;
// 通知外部任务已更新
if ( mOnTaskUpdatedListener ! = null & & mBindPositionId ! = null ) {
mOnTaskUpdatedListener . onTaskUpdated ( mBindPositionId , new ArrayList < PositionTaskModel > ( mData ) ) ;
mOnTaskUpdatedListener . onTaskUpdated ( mBindPositionId , new ArrayList < PositionTaskModel > ( mAdapter Data ) ) ;
}
}
} ) ;
dialog . dismiss ( ) ;
Toast . makeText ( context , " 任务已更新 " , Toast . LENGTH_SHORT ) . show ( ) ;
show Toast( " 任务已更新(已同步至服务) " ) ;
} catch ( Exception e ) {
LogUtils . d ( TAG , " 保存任务修改失败: " + e . getMessage ( ) ) ;
showToast ( " 保存失败,请重试 " ) ;
// 重新同步数据(修复可能的不一致)
syncTasksFromMainService ( ) ;
}
}
} ) ;
}
// ---------------------- ViewHolder 定义(完全不变 ) ----------------------
// 基础抽象 ViewHolder( 不变 )
// ---------------------- ViewHolder 定义(完全适配布局,无修改 ) ----------------------
// 基础抽象ViewHolder( 统一父类,适配多视图类型 )
public abstract class TaskViewHolder extends RecyclerView . ViewHolder {
public TaskViewHolder ( @NonNull View itemView ) {
super ( itemView ) ;
}
}
// 空提示 Holder( 不变 )
// 空提示View Holder( 对应 item_task_empty.xml )
public class EmptyViewHolder extends TaskViewHolder {
public EmptyViewHolder ( @NonNull View itemView ) {
super ( itemView ) ;
}
}
// 新增: 简单模式 Holder( 绑定带红点的布局控件 )
// 简单模式View Holder( 对应 item_position_task_simple.xml, 带isBingo红点 )
public class SimpleTaskViewHolder extends TaskViewHolder {
TextView tvSimpleTaskDesc ; // 任务描述
TextView tvSimpleDistanceCond ; // 距离条件
TextView tvSimpleIsEnable ; // 启用状态
View vBingoDot ; // isBingo 红点控件
TextView tvSimpleDistanceCond ; // 距离条件(大于/小于+距离)
TextView tvSimpleIsEnable ; // 启用状态(已启用/已禁用)
View vBingoDot ; // isBingo红点(任务触发时显示)
public SimpleTaskViewHolder ( @NonNull View itemView ) {
super ( itemView ) ;
// 绑定简单模式布局中的 控件(与 item_task_simple.xml 完全 对应)
// 绑定简单模式布局控件(与XML控件ID严格 对应)
tvSimpleTaskDesc = itemView . findViewById ( R . id . tv_simple_task_desc ) ;
tvSimpleDistanceCond = itemView . findViewById ( R . id . tv_simple_distance_cond ) ;
tvSimpleIsEnable = itemView . findViewById ( R . id . tv_simple_is_enable ) ;
@@ -394,22 +596,68 @@ public class PositionTaskListView extends LinearLayout {
}
}
// 编辑模式 Holder( 原有逻辑,完全不变 )
// 编辑模式View Holder( 对应 item_task_content.xml, 带编辑/删除/开关 )
public class TaskContentViewHolder extends TaskViewHolder {
TextView tvTaskDesc ;
TextView tvTaskDistance ;
CompoundButton cbTaskEnable ;
Button btnEditTask ;
Button btnDeleteTask ;
TextView tvTaskDesc ; // 任务描述
TextView tvTaskDistance ; // 距离条件
CompoundButton cbTaskEnable ; // 启用开关
Button btnEditTask ; // 编辑按钮
Button btnDeleteTask ; // 删除按钮
public TaskContentViewHolder ( @NonNull View itemView ) {
super ( itemView ) ;
tvTaskDesc = ( TextView ) itemView . findViewById ( R . id . tv_task_desc ) ;
tvTaskDistance = ( TextView ) itemView . findViewById ( R . id . tv_task_distance ) ;
cb TaskEnabl e = ( CompoundButton ) itemView . findViewById ( R . id . cb _task_enabl e) ;
btnEditTask = ( Button ) itemView . findViewById ( R . id . btn_edit_task ) ;
btnDelete Task = ( Button ) itemView . findViewById ( R . id . btn_delete _task ) ;
}
// 绑定编辑模式布局控件( 与XML控件ID严格对应)
tvTaskDesc = itemView . findViewById ( R . id . tv_task_desc ) ;
tv TaskDistanc e = itemView . findViewById ( R . id . tv _task_distanc e) ;
cbTaskEnable = itemView . findViewById ( R . id . cb_task_enable ) ;
btnEdit Task = itemView . findViewById ( R . id . btn_edit _task ) ;
btnDeleteTask = itemView . findViewById ( R . id . btn_delete_task ) ;
}
}
}
// ---------------------- 新增: 外部调用“新增任务”方法( 适配MainService) ----------------------
/**
* 新增任务( 对外提供, 如Activity调用添加任务)
* @param newTask 待新增的任务( 需关联当前位置ID)
*/
public void addNewTask ( PositionTaskModel newTask ) {
if ( mMainService = = null ) {
showToast ( " 新增任务失败:服务未就绪 " ) ;
LogUtils . e ( TAG , " 新增任务失败: MainService实例为空 " ) ;
return ;
}
if ( newTask = = null ) {
showToast ( " 新增任务失败:任务数据为空 " ) ;
LogUtils . e ( TAG , " 新增任务失败:待新增任务对象为空 " ) ;
return ;
}
if ( mBindPositionId = = null | | mBindPositionId . trim ( ) . isEmpty ( ) ) {
showToast ( " 新增任务失败:未关联位置 " ) ;
LogUtils . e ( TAG , " 新增任务失败: 未绑定位置ID( 需先调用init()方法) " ) ;
return ;
}
try {
// 1. 关联任务到当前位置(确保任务属于当前位置)
newTask . setPositionId ( mBindPositionId ) ;
// 2. 调用MainService新增任务( 服务是唯一数据源, 持久化数据)
mMainService . addTask ( newTask ) ; // 需在MainService中实现addTask()方法(添加到服务内全量任务列表)
LogUtils . d ( TAG , " 调用MainService新增任务: ID= " + newTask . getTaskId ( ) + " ( 位置ID=" + mBindPositionId + " ) " ) ;
// 3. 重新同步数据(从服务拉取最新列表,避免本地计算错误)
syncTasksFromMainService ( ) ;
// 4. 通知外部任务已更新
if ( mOnTaskUpdatedListener ! = null ) {
mOnTaskUpdatedListener . onTaskUpdated ( mBindPositionId , getCurrentPosTasks ( ) ) ;
}
showToast ( " 新增任务成功(已同步至服务) " ) ;
} catch ( Exception e ) {
LogUtils . d ( TAG , " 新增任务失败: " + e . getMessage ( ) ) ;
showToast ( " 新增失败,请重试 " ) ;
// 重新同步数据(修复可能的不一致)
syncTasksFromMainService ( ) ;
}
}
}