位置任务参数数据管理模块完成
This commit is contained in:
		| @@ -1,8 +1,8 @@ | |||||||
| #Created by .winboll/winboll_app_build.gradle | #Created by .winboll/winboll_app_build.gradle | ||||||
| #Mon Sep 29 19:30:19 GMT 2025 | #Tue Sep 30 06:43:35 GMT 2025 | ||||||
| stageCount=3 | stageCount=3 | ||||||
| libraryProject= | libraryProject= | ||||||
| baseVersion=15.0 | baseVersion=15.0 | ||||||
| publishVersion=15.0.2 | publishVersion=15.0.2 | ||||||
| buildCount=17 | buildCount=39 | ||||||
| baseBetaVersion=15.0.3 | baseBetaVersion=15.0.3 | ||||||
|   | |||||||
| @@ -32,6 +32,7 @@ import com.google.android.gms.location.LocationResult; | |||||||
| import com.google.android.gms.location.LocationServices; | import com.google.android.gms.location.LocationServices; | ||||||
| import com.google.android.gms.tasks.OnSuccessListener; | import com.google.android.gms.tasks.OnSuccessListener; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
|  | import cc.winboll.studio.positions.models.PositionTaskModel; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 实时定位活动窗口: |  * 实时定位活动窗口: | ||||||
| @@ -59,6 +60,7 @@ public class LocationActivity extends AppCompatActivity { | |||||||
|     private RecyclerView rvPositionList; // 位置列表(RecyclerView) |     private RecyclerView rvPositionList; // 位置列表(RecyclerView) | ||||||
|     private PositionAdapter positionAdapter; // 列表Adapter(含实时距离逻辑) |     private PositionAdapter positionAdapter; // 列表Adapter(含实时距离逻辑) | ||||||
|     ArrayList<PositionModel> mPositionList = new ArrayList<PositionModel>(); // 位置数据集合 |     ArrayList<PositionModel> mPositionList = new ArrayList<PositionModel>(); // 位置数据集合 | ||||||
|  |     ArrayList<PositionTaskModel> mPositionTasksList = new ArrayList<PositionTaskModel>(); // 位置数据集合 | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     protected void onCreate(Bundle savedInstanceState) { |     protected void onCreate(Bundle savedInstanceState) { | ||||||
| @@ -154,7 +156,9 @@ public class LocationActivity extends AppCompatActivity { | |||||||
|         rvPositionList.setLayoutManager(layoutManager); |         rvPositionList.setLayoutManager(layoutManager); | ||||||
|  |  | ||||||
|         // 2. 初始化Adapter(传入上下文和数据集合,兼容Java 7) |         // 2. 初始化Adapter(传入上下文和数据集合,兼容Java 7) | ||||||
|         positionAdapter = new PositionAdapter(this, mPositionList); | 		PositionModel.loadBeanList(this, this.mPositionList, PositionModel.class); | ||||||
|  | 		PositionTaskModel.loadBeanList(this, this.mPositionTasksList, PositionTaskModel.class); | ||||||
|  |         positionAdapter = new PositionAdapter(this, mPositionList, mPositionTasksList); | ||||||
|         rvPositionList.setAdapter(positionAdapter); |         rvPositionList.setAdapter(positionAdapter); | ||||||
|  |  | ||||||
|         // 3. 设置Adapter删除监听(删除列表项并同步本地数据) |         // 3. 设置Adapter删除监听(删除列表项并同步本地数据) | ||||||
| @@ -166,11 +170,25 @@ public class LocationActivity extends AppCompatActivity { | |||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
|         // 4. 设置Adapter保存监听(编辑备注后同步本地数据) |         // 4. 设置Adapter保存监听(编辑备注后同步本地数据) | ||||||
|         positionAdapter.setOnSaveClickListener(new PositionAdapter.OnSaveClickListener() { |         positionAdapter.setOnSavePositionClickListener(new PositionAdapter.OnSavePositionClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public void onSaveClick() { | 				public void onSavePositionClick() { | ||||||
| 					try { | 					try { | ||||||
| 						PositionModel.saveBeanList(LocationActivity.this, mPositionList, PositionModel.class); | 						PositionModel.saveBeanList(LocationActivity.this, mPositionList, PositionModel.class); | ||||||
|  | 						Toast.makeText(LocationActivity.this, "位置信息已保存", Toast.LENGTH_SHORT).show(); | ||||||
|  | 					} catch (Exception e) { | ||||||
|  | 						e.printStackTrace(); | ||||||
|  | 						Toast.makeText(LocationActivity.this, "数据保存失败", Toast.LENGTH_SHORT).show(); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 			 | ||||||
|  | 		positionAdapter.setOnSavePositionTaskClickListener(new PositionAdapter.OnSavePositionTaskClickListener() { | ||||||
|  | 				@Override | ||||||
|  | 				public void onSavePositionTaskClick() { | ||||||
|  | 					try { | ||||||
|  | 						PositionTaskModel.saveBeanList(LocationActivity.this, mPositionTasksList, PositionTaskModel.class); | ||||||
|  | 						Toast.makeText(LocationActivity.this, "任务信息已保存", Toast.LENGTH_SHORT).show(); | ||||||
| 					} catch (Exception e) { | 					} catch (Exception e) { | ||||||
| 						e.printStackTrace(); | 						e.printStackTrace(); | ||||||
| 						Toast.makeText(LocationActivity.this, "数据保存失败", Toast.LENGTH_SHORT).show(); | 						Toast.makeText(LocationActivity.this, "数据保存失败", Toast.LENGTH_SHORT).show(); | ||||||
| @@ -403,7 +421,7 @@ public class LocationActivity extends AppCompatActivity { | |||||||
|  |  | ||||||
|         // 2. 停止Adapter的距离刷新定时器(关键:避免Timer持有Context导致内存泄漏) |         // 2. 停止Adapter的距离刷新定时器(关键:避免Timer持有Context导致内存泄漏) | ||||||
|         if (positionAdapter != null) { |         if (positionAdapter != null) { | ||||||
|             positionAdapter.stopDistanceRefreshTimer(); |             positionAdapter.stopTimer(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 3. 最后同步一次数据(确保所有修改保存) |         // 3. 最后同步一次数据(确保所有修改保存) | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ package cc.winboll.studio.positions.adapters; | |||||||
| /** | /** | ||||||
|  * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> |  * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> | ||||||
|  * @Date 2025/09/29 20:25 |  * @Date 2025/09/29 20:25 | ||||||
|  * @Describe 位置数据适配器(支持简单视图/编辑视图切换 + 实时距离开关控制) |  * @Describe 位置数据适配器(修复定时器:仅刷新实时距离,不干扰编辑中的任务) | ||||||
|  */ |  */ | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.os.Handler; | import android.os.Handler; | ||||||
| @@ -24,120 +24,212 @@ import androidx.annotation.NonNull; | |||||||
| import androidx.recyclerview.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| import cc.winboll.studio.positions.R; | import cc.winboll.studio.positions.R; | ||||||
| import cc.winboll.studio.positions.models.PositionModel; | import cc.winboll.studio.positions.models.PositionModel; | ||||||
|  | import cc.winboll.studio.positions.models.PositionTaskModel; | ||||||
|  | import cc.winboll.studio.positions.views.PositionTaskListView; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Iterator; | ||||||
|  | import java.util.Map; | ||||||
| import java.util.Timer; | import java.util.Timer; | ||||||
| import java.util.TimerTask; | import java.util.TimerTask; | ||||||
|  |  | ||||||
| public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { | public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { | ||||||
|  |     // 常量定义(不变) | ||||||
|     public static final String TAG = "PositionAdapter"; |     public static final String TAG = "PositionAdapter"; | ||||||
|     private static final int VIEW_TYPE_SIMPLE = 1;  // 简单视图 |     private static final int VIEW_TYPE_SIMPLE = 1; | ||||||
|     private static final int VIEW_TYPE_EDIT = 2;    // 编辑视图 |     private static final int VIEW_TYPE_EDIT = 2; | ||||||
|     private static final long REFRESH_INTERVAL = 5000; // 实时刷新间隔:5秒(5000毫秒) |     private static final long REFRESH_INTERVAL = 5000; | ||||||
|  |     private static final String DEFAULT_TASK_DESC = "新位置任务"; | ||||||
|  |     private static final int DEFAULT_TASK_DISTANCE = 100; | ||||||
|  |  | ||||||
|     private ArrayList<PositionModel> mPositionList; |     // 核心成员变量(新增:用于存储可见的距离TextView,避免遍历所有ViewHolder) | ||||||
|     private Context mContext; |     private final ArrayList<PositionModel> mPositionList; | ||||||
|     private PositionModel mCurrentGpsPosition; // 存储当前GPS位置(由Activity传入) |     private final ArrayList<PositionTaskModel> mAllPositionTasks; | ||||||
|     private Timer mDistanceTimer; // 定时计算距离的定时器 |     private final Context mContext; | ||||||
|     private Handler mMainHandler; // 主线程Handler(更新UI必须在主线程) |     private PositionModel mCurrentGpsPosition; | ||||||
|  |     private final Timer mDistanceTimer; | ||||||
|  |     private final Handler mMainHandler; | ||||||
|  |     private final Map<String, ArrayList<PositionTaskModel>> mPositionTaskMap; | ||||||
|  |     // 关键新增:存储“位置ID → 距离TextView”的映射(仅包含当前可见的列表项) | ||||||
|  |     private final Map<String, TextView> mVisibleDistanceViews = new HashMap<>(); | ||||||
|  |  | ||||||
|     // 接口定义(不变) |     // 接口回调(不变) | ||||||
|     public interface OnDeleteClickListener { |     public interface OnDeleteClickListener { | ||||||
|         void onDeleteClick(int position); |         void onDeleteClick(int position); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public interface OnSaveClickListener { |     public interface OnSavePositionClickListener { | ||||||
|         void onSaveClick(); |         void onSavePositionClick(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public interface OnSavePositionTaskClickListener { | ||||||
|  |         void onSavePositionTaskClick(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private OnDeleteClickListener mOnDeleteClickListener; |     private OnDeleteClickListener mOnDeleteClickListener; | ||||||
|     private OnSaveClickListener mOnSaveClickListener; |     private OnSavePositionClickListener mOnSavePositionClickListener; | ||||||
|  |     private OnSavePositionTaskClickListener mOnSavePositionTaskClickListener; | ||||||
|  |  | ||||||
|     // 构造函数(新增:初始化主线程Handler) |     // 构造函数(不变,仅定时器初始化逻辑修改) | ||||||
|     public PositionAdapter(Context context, ArrayList<PositionModel> positionList) { |     public PositionAdapter(Context context, ArrayList<PositionModel> positionList, ArrayList<PositionTaskModel> allPositionTasks) { | ||||||
|         this.mContext = context; |         this.mContext = context; | ||||||
|         this.mPositionList = positionList; |         this.mPositionList = (positionList != null) ? positionList : new ArrayList<PositionModel>(); | ||||||
|         this.mMainHandler = new Handler(Looper.getMainLooper()); // 绑定主线程Looper |         this.mAllPositionTasks = (allPositionTasks != null) ? allPositionTasks : new ArrayList<PositionTaskModel>(); | ||||||
|  |         this.mMainHandler = new Handler(Looper.getMainLooper()); | ||||||
|  |         this.mDistanceTimer = new Timer(); | ||||||
|  |         this.mPositionTaskMap = new HashMap<String, ArrayList<PositionTaskModel>>(); | ||||||
|  |  | ||||||
|  |         // 初始化任务映射表(不变) | ||||||
|  |         for (PositionModel model : mPositionList) { | ||||||
|  |             String validPosId = model.getPositionId(); | ||||||
|  |             ArrayList<PositionTaskModel> matchedTasks = new ArrayList<PositionTaskModel>(); | ||||||
|  |             for (PositionTaskModel task : this.mAllPositionTasks) { | ||||||
|  |                 if (task != null && validPosId.equals(task.getPositionId())) { | ||||||
|  |                     matchedTasks.add(task); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             mPositionTaskMap.put(validPosId, matchedTasks); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 启动距离刷新定时器(核心修改:调用新的刷新逻辑) | ||||||
|  |         startDistanceRefreshTimer(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // ---------------------- 新增:设置当前GPS位置(由Activity调用,传入实时GPS数据) ---------------------- |     // 对外API:设置当前GPS位置(不变) | ||||||
|     /** |  | ||||||
|      * 设置当前GPS位置(Activity通过定位API获取后,调用此方法传入Adapter) |  | ||||||
|      * @param currentGpsPosition 包含当前经度、纬度的PositionModel(memo可空,isEnable无需关注) |  | ||||||
|      */ |  | ||||||
|     public void setCurrentGpsPosition(PositionModel currentGpsPosition) { |     public void setCurrentGpsPosition(PositionModel currentGpsPosition) { | ||||||
|         this.mCurrentGpsPosition = currentGpsPosition; |         this.mCurrentGpsPosition = currentGpsPosition; | ||||||
|         // 首次设置GPS位置时,启动定时器(避免重复启动) |  | ||||||
|         if (currentGpsPosition != null && mDistanceTimer == null) { |  | ||||||
|             startDistanceRefreshTimer(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // ---------------------- 新增:启动5秒定时刷新距离的定时器 ---------------------- |     // 对外API:设置监听器(不变) | ||||||
|     private void startDistanceRefreshTimer() { |  | ||||||
|         // 先停止原有定时器(防止重复创建) |  | ||||||
|         stopDistanceRefreshTimer(); |  | ||||||
|  |  | ||||||
|         mDistanceTimer = new Timer(); |  | ||||||
|         mDistanceTimer.scheduleAtFixedRate(new TimerTask() { |  | ||||||
| 				@Override |  | ||||||
| 				public void run() { |  | ||||||
| 					// 定时器在子线程执行,更新UI需切换到主线程 |  | ||||||
| 					mMainHandler.post(new Runnable() { |  | ||||||
| 							@Override |  | ||||||
| 							public void run() { |  | ||||||
| 								// 刷新所有列表项的距离显示(仅简单视图生效) |  | ||||||
| 								notifyItemRangeChanged(0, getItemCount()); |  | ||||||
| 							} |  | ||||||
| 						}); |  | ||||||
| 				} |  | ||||||
| 			}, 0, REFRESH_INTERVAL); // 0延迟启动,每REFRESH_INTERVAL(5秒)执行一次 |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // ---------------------- 新增:停止定时器(避免内存泄漏) ---------------------- |  | ||||||
|     /** |  | ||||||
|      * 停止定时器(必须在Activity销毁/Adapter不再使用时调用,如Activity的onDestroy) |  | ||||||
|      */ |  | ||||||
|     public void stopDistanceRefreshTimer() { |  | ||||||
|         if (mDistanceTimer != null) { |  | ||||||
|             mDistanceTimer.cancel(); |  | ||||||
|             mDistanceTimer.purge(); |  | ||||||
|             mDistanceTimer = null; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 设置监听(不变) |  | ||||||
|     public void setOnDeleteClickListener(OnDeleteClickListener listener) { |     public void setOnDeleteClickListener(OnDeleteClickListener listener) { | ||||||
|         this.mOnDeleteClickListener = listener; |         this.mOnDeleteClickListener = listener; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void setOnSaveClickListener(OnSaveClickListener listener) { |     public void setOnSavePositionClickListener(OnSavePositionClickListener listener) { | ||||||
|         this.mOnSaveClickListener = listener; |         this.mOnSavePositionClickListener = listener; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // 视图类型判断(不变) |     public void setOnSavePositionTaskClickListener(OnSavePositionTaskClickListener listener) { | ||||||
|  |         this.mOnSavePositionTaskClickListener = listener; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 对外API:停止定时器(不变) | ||||||
|  |     public void stopTimer() { | ||||||
|  |         if (mDistanceTimer != null) { | ||||||
|  |             mDistanceTimer.cancel(); | ||||||
|  |             mDistanceTimer.purge(); | ||||||
|  |         } | ||||||
|  |         // 清空可见距离视图缓存,避免内存泄漏 | ||||||
|  |         mVisibleDistanceViews.clear(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // ---------------------- 核心修复1:定时器逻辑(仅刷新可见的距离TextView) ---------------------- | ||||||
|  |     /** | ||||||
|  |      * 启动实时距离刷新定时器(仅更新可见的距离文本,不刷新整个列表项) | ||||||
|  |      */ | ||||||
|  |     private void startDistanceRefreshTimer() { | ||||||
|  |         mDistanceTimer.scheduleAtFixedRate(new TimerTask() { | ||||||
|  | 				@Override | ||||||
|  | 				public void run() { | ||||||
|  | 					// 切换到主线程(UI更新必须在主线程) | ||||||
|  | 					mMainHandler.post(new Runnable() { | ||||||
|  | 							@Override | ||||||
|  | 							public void run() { | ||||||
|  | 								// 遍历所有可见的距离TextView,针对性更新(不触发列表项重新绑定) | ||||||
|  | 								for (Map.Entry<String, TextView> entry : mVisibleDistanceViews.entrySet()) { | ||||||
|  | 									String positionId = entry.getKey(); // 当前可见项的位置ID | ||||||
|  | 									TextView distanceView = entry.getValue(); // 要更新的距离文本控件 | ||||||
|  |  | ||||||
|  | 									// 根据位置ID找到对应的PositionModel | ||||||
|  | 									PositionModel targetModel = null; | ||||||
|  | 									for (PositionModel model : mPositionList) { | ||||||
|  | 										if (positionId.equals(model.getPositionId())) { | ||||||
|  | 											targetModel = model; | ||||||
|  | 											break; | ||||||
|  | 										} | ||||||
|  | 									} | ||||||
|  |  | ||||||
|  | 									// 若找到对应模型,直接更新距离文本(复用原有bindRealDistance逻辑) | ||||||
|  | 									if (targetModel != null) { | ||||||
|  | 										bindRealDistance(distanceView, targetModel); | ||||||
|  | 									} | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  | 						}); | ||||||
|  | 				} | ||||||
|  | 			}, 0, REFRESH_INTERVAL); // 0延迟启动,每5秒执行一次 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // ---------------------- 核心修复2:管理可见/不可见的距离TextView(避免内存泄漏+精准刷新) ---------------------- | ||||||
|  |     /** | ||||||
|  |      * 列表项变为“可见”时:将距离TextView加入缓存(供定时器刷新) | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) { | ||||||
|  |         super.onViewAttachedToWindow(holder); | ||||||
|  |         int position = holder.getAdapterPosition(); | ||||||
|  |         if (position < 0 || position >= mPositionList.size()) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         PositionModel model = mPositionList.get(position); | ||||||
|  |         String positionId = model.getPositionId(); | ||||||
|  |         TextView distanceView = null; | ||||||
|  |  | ||||||
|  |         // 根据视图类型,找到对应的距离TextView | ||||||
|  |         if (holder instanceof SimpleViewHolder) { | ||||||
|  |             distanceView = ((SimpleViewHolder) holder).tvSimpleRealDistance; | ||||||
|  |         } else if (holder instanceof EditViewHolder) { | ||||||
|  |             // 编辑视图若也显示距离(根据需求,若不显示可删除此分支) | ||||||
|  |             distanceView = ((EditViewHolder) holder).tvEditRealDistance;  | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 若距离TextView存在,加入缓存(key=位置ID,确保唯一) | ||||||
|  |         if (distanceView != null) { | ||||||
|  |             mVisibleDistanceViews.put(positionId, distanceView); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 列表项变为“不可见”时:将距离TextView从缓存移除(避免内存泄漏+无效刷新) | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) { | ||||||
|  |         super.onViewDetachedFromWindow(holder); | ||||||
|  |         int position = holder.getAdapterPosition(); | ||||||
|  |         if (position < 0 || position >= mPositionList.size()) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         PositionModel model = mPositionList.get(position); | ||||||
|  |         String positionId = model.getPositionId(); | ||||||
|  |  | ||||||
|  |         // 从缓存移除不可见项的距离TextView | ||||||
|  |         if (mVisibleDistanceViews.containsKey(positionId)) { | ||||||
|  |             mVisibleDistanceViews.remove(positionId); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // ---------------------- RecyclerView 核心方法(仅补充编辑视图的距离TextView绑定,其他不变) ---------------------- | ||||||
|     @Override |     @Override | ||||||
|     public int getItemViewType(int position) { |     public int getItemViewType(int position) { | ||||||
|         PositionModel model = mPositionList.get(position); |         PositionModel model = mPositionList.get(position); | ||||||
|         return model.isSimpleView() ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT; |         return model.isSimpleView() ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // 创建ViewHolder(修改:EditViewHolder新增单选框组引用;SimpleViewHolder不变) |  | ||||||
|     @NonNull |     @NonNull | ||||||
|     @Override |     @Override | ||||||
|     public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { |     public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { | ||||||
|         LayoutInflater inflater = LayoutInflater.from(mContext); |         LayoutInflater inflater = LayoutInflater.from(mContext); | ||||||
|         if (viewType == VIEW_TYPE_SIMPLE) { |         if (viewType == VIEW_TYPE_SIMPLE) { | ||||||
|             // 简单视图:绑定距离显示控件(不变) |  | ||||||
|             View view = inflater.inflate(R.layout.item_position_simple, parent, false); |             View view = inflater.inflate(R.layout.item_position_simple, parent, false); | ||||||
|             return new SimpleViewHolder(view); |             return new SimpleViewHolder(view); | ||||||
|         } else { |         } else { | ||||||
|             // 编辑视图:绑定新增的单选框组(关键修改) |  | ||||||
|             View view = inflater.inflate(R.layout.item_position_edit, parent, false); |             View view = inflater.inflate(R.layout.item_position_edit, parent, false); | ||||||
|             return new EditViewHolder(view); |             return new EditViewHolder(view); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // 绑定数据(核心修改:编辑视图添加单选框状态绑定) |  | ||||||
|     @Override |     @Override | ||||||
|     public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { |     public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { | ||||||
|         PositionModel model = mPositionList.get(position); |         PositionModel model = mPositionList.get(position); | ||||||
| @@ -148,40 +240,43 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // ---------------------- 简单视图绑定(不变:实时距离显示逻辑保持原样) ---------------------- |     @Override | ||||||
|     private void bindSimpleView(SimpleViewHolder holder, final PositionModel model, final int position) { |     public int getItemCount() { | ||||||
|         // 原有:绑定经纬度、备注 |         return mPositionList.size(); | ||||||
|         holder.tvSimpleLongitude.setText(String.format("经度:%.6f", model.getLongitude())); |     } | ||||||
|         holder.tvSimpleLatitude.setText(String.format("纬度:%.6f", model.getLatitude())); |  | ||||||
|         String memo = model.getMemo().trim().isEmpty() ? "无备注" : model.getMemo(); |  | ||||||
|         holder.tvSimpleMemo.setText(String.format("备注:%s", memo)); |  | ||||||
|  |  | ||||||
|         // 实时距离显示逻辑(根据isEnableRealPositionDistance判断) |     // 回收ViewHolder时清理资源(不变,补充距离缓存移除) | ||||||
|         if (model.isEnableRealPositionDistance()) { |     @Override | ||||||
|             if (mCurrentGpsPosition != null) { |     public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { | ||||||
|                 try { |         super.onViewRecycled(holder); | ||||||
|                     // 调用PositionModel静态方法计算距离(米/千米自适应) |         // 移除缓存中已回收项的距离TextView | ||||||
|                     double distanceM = PositionModel.calculatePositionDistance( |         int position = holder.getAdapterPosition(); | ||||||
| 						mCurrentGpsPosition, model, false); |         if (position >= 0 && position < mPositionList.size()) { | ||||||
|                     String distanceText; |             String positionId = mPositionList.get(position).getPositionId(); | ||||||
|                     if (distanceM < 1000) { |             mVisibleDistanceViews.remove(positionId); | ||||||
|                         distanceText = String.format("实时距离:%.1f 米", distanceM); |  | ||||||
|                     } else { |  | ||||||
|                         double distanceKm = distanceM / 1000; |  | ||||||
|                         distanceText = String.format("实时距离:%.1f 千米", distanceKm); |  | ||||||
|                     } |  | ||||||
|                     holder.tvSimpleRealDistance.setText(distanceText); |  | ||||||
|                 } catch (IllegalArgumentException e) { |  | ||||||
|                     holder.tvSimpleRealDistance.setText("实时距离:数据无效"); |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 holder.tvSimpleRealDistance.setText("实时距离:等待GPS定位"); |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             holder.tvSimpleRealDistance.setText("实时距离未启用"); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 长按弹出编辑菜单(不变) |         // 清理任务列表资源(不变) | ||||||
|  |         if (holder instanceof SimpleViewHolder) { | ||||||
|  |             ((SimpleViewHolder) holder).ptlvSimpleTasks.clearData(); | ||||||
|  |             ((SimpleViewHolder) holder).ptlvSimpleTasks.setOnTaskUpdatedListener(null); | ||||||
|  |         } else if (holder instanceof EditViewHolder) { | ||||||
|  |             ((EditViewHolder) holder).ptlvEditTasks.clearData(); | ||||||
|  |             ((EditViewHolder) holder).ptlvEditTasks.setOnTaskUpdatedListener(null); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 绑定简单视图(不变,仅确保距离TextView正确初始化) | ||||||
|  |     private void bindSimpleView(final SimpleViewHolder holder, final PositionModel model, final int position) { | ||||||
|  |         // 1. 绑定位置基础信息(不变) | ||||||
|  |         holder.tvSimpleLongitude.setText(String.format("经度:%.6f", model.getLongitude())); | ||||||
|  |         holder.tvSimpleLatitude.setText(String.format("纬度:%.6f", model.getLatitude())); | ||||||
|  |         holder.tvSimpleMemo.setText(String.format("备注:%s", model.getMemo())); | ||||||
|  |  | ||||||
|  |         // 2. 绑定实时距离(首次初始化时调用,后续由定时器刷新) | ||||||
|  |         bindRealDistance(holder.tvSimpleRealDistance, model); | ||||||
|  |  | ||||||
|  |         // 3. 长按切换编辑视图(不变) | ||||||
|         holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { |         holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public boolean onLongClick(View v) { | 				public boolean onLongClick(View v) { | ||||||
| @@ -194,47 +289,56 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
| 							public boolean onMenuItemClick(MenuItem item) { | 							public boolean onMenuItemClick(MenuItem item) { | ||||||
| 								if (item.getItemId() == R.id.menu_edit) { | 								if (item.getItemId() == R.id.menu_edit) { | ||||||
| 									model.setIsSimpleView(false); | 									model.setIsSimpleView(false); | ||||||
| 									notifyItemChanged(position); | 									notifyItemChanged(position); // 仅切换视图时刷新,不影响定时器 | ||||||
| 									return true; | 									return true; | ||||||
| 								} | 								} | ||||||
| 								return false; | 								return false; | ||||||
| 							} | 							} | ||||||
| 						}); | 						}); | ||||||
|  |  | ||||||
| 					popupMenu.show(); | 					popupMenu.show(); | ||||||
| 					return true; | 					return true; | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
|  |         // 4. 绑定任务列表(不变,确保编辑不被干扰) | ||||||
|  |         String currentPosId = model.getPositionId(); | ||||||
|  |         ArrayList<PositionTaskModel> matchedTasks = getSafeTasks(currentPosId); | ||||||
|  |         holder.ptlvSimpleTasks.clearData(); | ||||||
|  |         holder.ptlvSimpleTasks.setViewStatus(PositionTaskListView.VIEW_MODE_SIMPLE); | ||||||
|  |         if (holder.ptlvSimpleTasks.getAllTasks().isEmpty()) { | ||||||
|  |             holder.ptlvSimpleTasks.init(matchedTasks, currentPosId); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // ---------------------- 核心修改:编辑视图绑定(新增单选框状态同步+保存) ---------------------- |     // 绑定编辑视图(补充距离TextView绑定,其他不变) | ||||||
|     private void bindEditView(final EditViewHolder holder, final PositionModel model, final int position) { |     private void bindEditView(final EditViewHolder holder, final PositionModel model, final int position) { | ||||||
|         // 1. 原有:绑定经纬度、备注(不变) |         // 1. 绑定位置基础信息(新增:编辑视图的距离TextView初始化) | ||||||
|         holder.tvEditLongitude.setText(String.format("经度:%.6f", model.getLongitude())); |         holder.tvEditLongitude.setText(String.format("经度:%.6f", model.getLongitude())); | ||||||
|         holder.tvEditLatitude.setText(String.format("纬度:%.6f", model.getLatitude())); |         holder.tvEditLatitude.setText(String.format("纬度:%.6f", model.getLatitude())); | ||||||
|         holder.etEditMemo.setText(model.getMemo()); |         holder.etEditMemo.setText(model.getMemo()); | ||||||
|         holder.etEditMemo.setSelection(model.getMemo().length()); |         holder.etEditMemo.setSelection(holder.etEditMemo.getText().length()); | ||||||
|  |         // 新增:首次绑定编辑视图时,初始化距离文本(后续由定时器刷新) | ||||||
|  |         bindRealDistance(holder.tvEditRealDistance, model); | ||||||
|  |  | ||||||
|         // 2. 新增:绑定实时距离开关状态(与model.isEnableRealPositionDistance同步) |         // 2. 绑定实时距离开关(不变) | ||||||
|         if (model.isEnableRealPositionDistance()) { |         if (model.isEnableRealPositionDistance()) { | ||||||
|             // 启用实时距离→选中“启用”单选框 |  | ||||||
|             holder.rgRealDistanceSwitch.check(R.id.rb_enable); |             holder.rgRealDistanceSwitch.check(R.id.rb_enable); | ||||||
|         } else { |         } else { | ||||||
|             // 禁用实时距离→选中“禁用”单选框(默认状态) |  | ||||||
|             holder.rgRealDistanceSwitch.check(R.id.rb_disable); |             holder.rgRealDistanceSwitch.check(R.id.rb_disable); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 3. 按钮逻辑(不变+新增单选框保存) |         // 3. 绑定删除/取消/确定按钮(不变,确保任务编辑逻辑正常) | ||||||
|         // ① 删除按钮(最左侧,逻辑不变) |  | ||||||
|         holder.btnEditDelete.setOnClickListener(new View.OnClickListener() { |         holder.btnEditDelete.setOnClickListener(new View.OnClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public void onClick(View v) { | 				public void onClick(View v) { | ||||||
| 					if (mOnDeleteClickListener != null) { | 					if (mOnDeleteClickListener != null) { | ||||||
| 						mOnDeleteClickListener.onDeleteClick(position); | 						mOnDeleteClickListener.onDeleteClick(position); | ||||||
|  | 						mPositionTaskMap.remove(model.getPositionId()); | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
|         // ② 取消按钮(中间,逻辑不变:不保存修改,切回简单视图) |  | ||||||
|         holder.btnEditCancel.setOnClickListener(new View.OnClickListener() { |         holder.btnEditCancel.setOnClickListener(new View.OnClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public void onClick(View v) { | 				public void onClick(View v) { | ||||||
| @@ -244,132 +348,280 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
|         // ③ 确定按钮(最右侧,核心修改:保存备注+保存单选框状态) |  | ||||||
|         holder.btnEditConfirm.setOnClickListener(new View.OnClickListener() { |         holder.btnEditConfirm.setOnClickListener(new View.OnClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public void onClick(View v) { | 				public void onClick(View v) { | ||||||
| 					// 原有:保存备注内容 |  | ||||||
| 					String newMemo = holder.etEditMemo.getText().toString().trim(); | 					String newMemo = holder.etEditMemo.getText().toString().trim(); | ||||||
| 					model.setMemo(newMemo.isEmpty() ? "无备注" : newMemo); | 					model.setMemo(newMemo); | ||||||
|  | 					boolean isEnableDistance = holder.rgRealDistanceSwitch.getCheckedRadioButtonId() == R.id.rb_enable; | ||||||
|  | 					model.setIsEnableRealPositionDistance(isEnableDistance); | ||||||
|  |  | ||||||
| 					// 新增:保存实时距离开关状态(根据单选框选中项更新model属性) | 					String currentPosId = model.getPositionId(); | ||||||
| 					int checkedRadioId = holder.rgRealDistanceSwitch.getCheckedRadioButtonId(); | 					ArrayList<PositionTaskModel> allTasks = holder.ptlvEditTasks.getAllTasks(); | ||||||
| 					if (checkedRadioId == R.id.rb_enable) { | 					ArrayList<PositionTaskModel> boundTasks = new ArrayList<PositionTaskModel>(); | ||||||
| 						// 选中“启用”→设置isEnableRealPositionDistance为true | 					for (PositionTaskModel task : allTasks) { | ||||||
| 						model.setIsEnableRealPositionDistance(true); | 						if (task != null) { | ||||||
| 					} else { | 							task.setPositionId(currentPosId); | ||||||
| 						// 选中“禁用”(含默认)→设置isEnableRealPositionDistance为false | 							boundTasks.add(task); | ||||||
| 						model.setIsEnableRealPositionDistance(false); | 						} | ||||||
| 					} | 					} | ||||||
|  | 					mPositionTaskMap.put(currentPosId, boundTasks); | ||||||
|  |  | ||||||
| 					// 原有:切回简单视图+刷新列表+通知保存 |  | ||||||
| 					model.setIsSimpleView(true); | 					model.setIsSimpleView(true); | ||||||
| 					notifyItemChanged(position); // 刷新当前项,实时更新距离显示状态 | 					notifyItemChanged(position); | ||||||
|  | 					if (mOnSavePositionClickListener != null) { | ||||||
| 					if (mOnSaveClickListener != null) { | 						mOnSavePositionClickListener.onSavePositionClick(); | ||||||
| 						mOnSaveClickListener.onSaveClick(); // 通知Activity保存到本地 |  | ||||||
| 					} | 					} | ||||||
| 					Toast.makeText(mContext, "备注及实时距离设置已保存", Toast.LENGTH_SHORT).show(); | 					Toast.makeText(mContext, "位置信息已保存", Toast.LENGTH_SHORT).show(); | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // ---------------------- ViewHolder 定义(核心修改:EditViewHolder新增单选框组引用) ---------------------- |         // 4. 绑定任务列表(不变,确保编辑中的任务不被刷新覆盖) | ||||||
|     /** |         final String currentPosId = model.getPositionId(); | ||||||
|      * 简单视图Holder(不变:含实时距离显示控件) |         ArrayList<PositionTaskModel> matchedTasks = getSafeTasks(currentPosId); | ||||||
|      */ |         holder.ptlvEditTasks.clearData(); | ||||||
|     public static class SimpleViewHolder extends RecyclerView.ViewHolder { |  | ||||||
|         TextView tvSimpleLongitude; |  | ||||||
|         TextView tvSimpleLatitude; |  | ||||||
|         TextView tvSimpleMemo; |  | ||||||
|         TextView tvSimpleRealDistance; // 实时距离显示控件 |  | ||||||
|  |  | ||||||
|         public SimpleViewHolder(@NonNull View itemView) { |         holder.ptlvEditTasks.setOnTaskUpdatedListener(new PositionTaskListView.OnTaskUpdatedListener() { | ||||||
|             super(itemView); | 				@Override | ||||||
|             // 绑定原有控件(不变) | 				public void onTaskUpdated(String posId, ArrayList<PositionTaskModel> updatedTasks) { | ||||||
|             tvSimpleLongitude = (TextView) itemView.findViewById(R.id.tv_simple_longitude); | 					ArrayList<PositionTaskModel> boundTasks = new ArrayList<PositionTaskModel>(); | ||||||
|             tvSimpleLatitude = (TextView) itemView.findViewById(R.id.tv_simple_latitude); | 					for (PositionTaskModel task : updatedTasks) { | ||||||
|             tvSimpleMemo = (TextView) itemView.findViewById(R.id.tv_simple_memo); | 						if (task != null) { | ||||||
|             // 绑定实时距离显示控件(不变) | 							task.setPositionId(currentPosId); | ||||||
|             tvSimpleRealDistance = (TextView) itemView.findViewById(R.id.tv_simple_real_distance); | 							boundTasks.add(task); | ||||||
|         } | 						} | ||||||
|     } | 					} | ||||||
|  |  | ||||||
|     /** | 					mPositionTaskMap.put(currentPosId, boundTasks); | ||||||
|      * 编辑视图Holder(核心修改:新增实时距离开关单选框组及子项引用) | 					Iterator<PositionTaskModel> taskIterator = mAllPositionTasks.iterator(); | ||||||
|      */ | 					while (taskIterator.hasNext()) { | ||||||
|     public static class EditViewHolder extends RecyclerView.ViewHolder { | 						PositionTaskModel task = taskIterator.next(); | ||||||
|         TextView tvEditLongitude; | 						if (task != null && currentPosId.equals(task.getPositionId())) { | ||||||
|         TextView tvEditLatitude; | 							taskIterator.remove(); | ||||||
|         EditText etEditMemo; | 						} | ||||||
|         Button btnEditDelete; | 					} | ||||||
|         Button btnEditCancel; | 					mAllPositionTasks.addAll(boundTasks); | ||||||
|         Button btnEditConfirm; |  | ||||||
|         // 新增:实时距离开关单选框组及子项(与item_position_edit.xml对应) |  | ||||||
|         RadioGroup rgRealDistanceSwitch; // 单选框组(控制二选一) |  | ||||||
|         RadioButton rbDisable;          // “禁用”单选框(对应isEnable=false) |  | ||||||
|         RadioButton rbEnable;           // “启用”单选框(对应isEnable=true) |  | ||||||
|  |  | ||||||
|         public EditViewHolder(@NonNull View itemView) { | 					if (mOnSavePositionTaskClickListener != null) { | ||||||
|             super(itemView); |                         mOnSavePositionTaskClickListener.onSavePositionTaskClick(); | ||||||
|             // 绑定原有控件(不变,显式强转适配Java 7) |                     } | ||||||
|             tvEditLongitude = (TextView) itemView.findViewById(R.id.tv_edit_longitude); |                     Toast.makeText(mContext, "任务信息已保存", Toast.LENGTH_SHORT).show(); | ||||||
|             tvEditLatitude = (TextView) itemView.findViewById(R.id.tv_edit_latitude); |                 } | ||||||
|             etEditMemo = (EditText) itemView.findViewById(R.id.et_edit_memo); |             }); | ||||||
|             btnEditDelete = (Button) itemView.findViewById(R.id.btn_edit_delete); |  | ||||||
|             btnEditCancel = (Button) itemView.findViewById(R.id.btn_edit_cancel); |  | ||||||
|             btnEditConfirm = (Button) itemView.findViewById(R.id.btn_edit_confirm); |  | ||||||
|             // 新增:绑定单选框组及子项(显式强转适配Java 7,与布局ID严格对应) |  | ||||||
|             rgRealDistanceSwitch = (RadioGroup) itemView.findViewById(R.id.rg_real_distance_switch); |  | ||||||
|             rbDisable = (RadioButton) itemView.findViewById(R.id.rb_disable); |  | ||||||
|             rbEnable = (RadioButton) itemView.findViewById(R.id.rb_enable); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 数据存取方法(不变) | 		if (holder.ptlvEditTasks.getAllTasks().isEmpty()) { | ||||||
|     public void addPosition(PositionModel model) { | 			holder.ptlvEditTasks.init(matchedTasks, currentPosId); | ||||||
|         if (mPositionList != null && model != null) { | 		} | ||||||
|             model.setIsSimpleView(true); | 		holder.ptlvEditTasks.setViewStatus(PositionTaskListView.VIEW_MODE_EDIT); | ||||||
|             mPositionList.add(model); |  | ||||||
|             notifyItemInserted(mPositionList.size() - 1); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void removePosition(int position) { | 		// 5. 绑定新增任务按钮(不变) | ||||||
|         if (mPositionList != null && position >= 0 && position < mPositionList.size()) { | 		holder.btnAddTask.setOnClickListener(new View.OnClickListener() { | ||||||
|             mPositionList.remove(position); |                 @Override | ||||||
|             notifyItemRemoved(position); |                 public void onClick(View v) { | ||||||
|             notifyItemRangeChanged(position, mPositionList.size()); |                     PositionTaskModel newTask = new PositionTaskModel( | ||||||
|         } | 						PositionTaskModel.genTaskId(), | ||||||
|     } | 						currentPosId, | ||||||
|  | 						DEFAULT_TASK_DESC, | ||||||
|  | 						true, | ||||||
|  | 						DEFAULT_TASK_DISTANCE, | ||||||
|  | 						true | ||||||
|  |                     ); | ||||||
|  |  | ||||||
|     public void updateAllPositions(ArrayList<PositionModel> newList) { |                     ArrayList<PositionTaskModel> currentTasks = getSafeTasks(currentPosId); | ||||||
|         if (newList != null) { |                     currentTasks.add(newTask); | ||||||
|             mPositionList.clear(); |                     mPositionTaskMap.put(currentPosId, new ArrayList<PositionTaskModel>(currentTasks)); | ||||||
|             for (PositionModel model : newList) { |                     mAllPositionTasks.add(newTask); | ||||||
|                 model.setIsSimpleView(true); |  | ||||||
|             } |  | ||||||
|             mPositionList.addAll(newList); |  | ||||||
|             notifyDataSetChanged(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public ArrayList<PositionModel> getPositionList() { |                     holder.ptlvEditTasks.clearData(); | ||||||
|         return mPositionList; |                     holder.ptlvEditTasks.init(currentTasks, currentPosId); | ||||||
|     } |                     holder.ptlvEditTasks.setViewStatus(PositionTaskListView.VIEW_MODE_EDIT); | ||||||
|  |                     Toast.makeText(mContext, "已新增1个任务(绑定当前位置)", Toast.LENGTH_SHORT).show(); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|     public void switchAllToSimpleView() { | 	// 工具方法:绑定实时距离(逻辑不变,仅负责文本更新,不影响其他控件) | ||||||
|         if (mPositionList != null) { | 	private void bindRealDistance(TextView distanceView, PositionModel model) { | ||||||
|             for (PositionModel model : mPositionList) { | 		if (!model.isEnableRealPositionDistance()) { | ||||||
|                 model.setIsSimpleView(true); | 			distanceView.setText("实时距离:未启用"); | ||||||
|             } | 			distanceView.setTextColor(mContext.getResources().getColor(R.color.colorGrayText)); | ||||||
|             notifyDataSetChanged(); | 			return; | ||||||
|         } | 		} | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override | 		if (mCurrentGpsPosition == null) { | ||||||
|     public int getItemCount() { | 			distanceView.setText("实时距离:等待GPS定位"); | ||||||
|         return mPositionList == null ? 0 : mPositionList.size(); | 			distanceView.setTextColor(mContext.getResources().getColor(R.color.colorGrayText)); | ||||||
|     } | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		try { | ||||||
|  | 			double distanceM = PositionModel.calculatePositionDistance(mCurrentGpsPosition, model, false); | ||||||
|  | 			String distanceText = (distanceM < 1000) ? | ||||||
|  | 				String.format("实时距离:%.1f 米", distanceM) : | ||||||
|  | 				String.format("实时距离:%.1f 千米", distanceM / 1000); | ||||||
|  | 			distanceView.setText(distanceText); | ||||||
|  | 			distanceView.setTextColor(mContext.getResources().getColor(R.color.colorEnableGreen)); | ||||||
|  | 		} catch (IllegalArgumentException e) { | ||||||
|  | 			distanceView.setText("实时距离:数据无效"); | ||||||
|  | 			distanceView.setTextColor(mContext.getResources().getColor(R.color.colorRed)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// 工具方法:安全获取任务列表(不变) | ||||||
|  | 	private ArrayList<PositionTaskModel> getSafeTasks(String positionId) { | ||||||
|  | 		if (!mPositionTaskMap.containsKey(positionId)) { | ||||||
|  | 			mPositionTaskMap.put(positionId, new ArrayList<PositionTaskModel>()); | ||||||
|  | 		} | ||||||
|  | 		return mPositionTaskMap.get(positionId); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// 对外API:新增位置(不变) | ||||||
|  | 	public void addPosition(PositionModel model) { | ||||||
|  | 		if (model == null) return; | ||||||
|  | 		String validPosId = model.getPositionId(); | ||||||
|  | 		mPositionList.add(model); | ||||||
|  | 		if (!mPositionTaskMap.containsKey(validPosId)) { | ||||||
|  | 			mPositionTaskMap.put(validPosId, new ArrayList<PositionTaskModel>()); | ||||||
|  | 		} | ||||||
|  | 		notifyItemInserted(mPositionList.size() - 1); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// 对外API:删除位置(不变,补充距离缓存清理) | ||||||
|  | 	public void removePosition(int position) { | ||||||
|  | 		if (position < 0 || position >= mPositionList.size()) return; | ||||||
|  | 		PositionModel removedModel = mPositionList.get(position); | ||||||
|  | 		String removedPosId = removedModel.getPositionId(); | ||||||
|  |  | ||||||
|  | 		// 1. 清理距离缓存(新增:移除已删除位置的距离TextView) | ||||||
|  | 		mVisibleDistanceViews.remove(removedPosId); | ||||||
|  |  | ||||||
|  | 		// 2. 清理全局任务列表和映射表(不变) | ||||||
|  | 		Iterator<PositionTaskModel> taskIterator = mAllPositionTasks.iterator(); | ||||||
|  | 		while (taskIterator.hasNext()) { | ||||||
|  | 			PositionTaskModel task = taskIterator.next(); | ||||||
|  | 			if (task != null && removedPosId.equals(task.getPositionId())) { | ||||||
|  | 				taskIterator.remove(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		mPositionTaskMap.remove(removedPosId); | ||||||
|  | 		mPositionList.remove(position); | ||||||
|  |  | ||||||
|  | 		notifyItemRemoved(position); | ||||||
|  | 		notifyItemRangeChanged(position, mPositionList.size()); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// 对外API:更新所有位置(不变,补充距离缓存同步) | ||||||
|  | 	public void updateAllPositions(ArrayList<PositionModel> newPositionList) { | ||||||
|  | 		if (newPositionList == null) return; | ||||||
|  | 		mPositionList.clear(); | ||||||
|  | 		mPositionList.addAll(newPositionList); | ||||||
|  |  | ||||||
|  | 		// 1. 清理距离缓存中无效的位置ID(新增:仅保留新列表中的位置) | ||||||
|  | 		Iterator<String> distanceViewIter = mVisibleDistanceViews.keySet().iterator(); | ||||||
|  | 		while (distanceViewIter.hasNext()) { | ||||||
|  | 			String posId = distanceViewIter.next(); | ||||||
|  | 			boolean isPosExist = false; | ||||||
|  | 			for (PositionModel model : newPositionList) { | ||||||
|  | 				if (posId.equals(model.getPositionId())) { | ||||||
|  | 					isPosExist = true; | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if (!isPosExist) { | ||||||
|  | 				distanceViewIter.remove(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// 2. 清理任务映射表(不变) | ||||||
|  | 		Iterator<String> taskMapKeyIter = mPositionTaskMap.keySet().iterator(); | ||||||
|  | 		while (taskMapKeyIter.hasNext()) { | ||||||
|  | 			String posId = taskMapKeyIter.next(); | ||||||
|  | 			boolean isPosExist = false; | ||||||
|  | 			for (PositionModel model : newPositionList) { | ||||||
|  | 				if (posId.equals(model.getPositionId())) { | ||||||
|  | 					isPosExist = true; | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if (!isPosExist) { | ||||||
|  | 				taskMapKeyIter.remove(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// 3. 为新位置初始化任务列表(不变) | ||||||
|  | 		for (PositionModel model : newPositionList) { | ||||||
|  | 			String posId = model.getPositionId(); | ||||||
|  | 			if (!mPositionTaskMap.containsKey(posId)) { | ||||||
|  | 				mPositionTaskMap.put(posId, new ArrayList<PositionTaskModel>()); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		notifyDataSetChanged(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// 对外API:批量切换简单视图(不变) | ||||||
|  | 	public void switchAllToSimpleView() { | ||||||
|  | 		for (PositionModel model : mPositionList) { | ||||||
|  | 			model.setIsSimpleView(true); | ||||||
|  | 		} | ||||||
|  | 		notifyDataSetChanged(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// 对外API:获取位置列表(不变) | ||||||
|  | 	public ArrayList<PositionModel> getPositionList() { | ||||||
|  | 		return new ArrayList<PositionModel>(mPositionList); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// ---------------------- ViewHolder 定义(核心补充:编辑视图的距离TextView) ---------------------- | ||||||
|  | 	// 简单视图Holder(不变) | ||||||
|  | 	public static class SimpleViewHolder extends RecyclerView.ViewHolder { | ||||||
|  | 		TextView tvSimpleLongitude; | ||||||
|  | 		TextView tvSimpleLatitude; | ||||||
|  | 		TextView tvSimpleMemo; | ||||||
|  | 		TextView tvSimpleRealDistance; // 简单视图-距离文本 | ||||||
|  | 		PositionTaskListView ptlvSimpleTasks; | ||||||
|  |  | ||||||
|  | 		public SimpleViewHolder(@NonNull View itemView) { | ||||||
|  | 			super(itemView); | ||||||
|  | 			tvSimpleLongitude = (TextView) itemView.findViewById(R.id.tv_simple_longitude); | ||||||
|  | 			tvSimpleLatitude = (TextView) itemView.findViewById(R.id.tv_simple_latitude); | ||||||
|  | 			tvSimpleMemo = (TextView) itemView.findViewById(R.id.tv_simple_memo); | ||||||
|  | 			tvSimpleRealDistance = (TextView) itemView.findViewById(R.id.tv_simple_real_distance); | ||||||
|  | 			ptlvSimpleTasks = (PositionTaskListView) itemView.findViewById(R.id.ptlv_simple_tasks); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// 编辑视图Holder(核心补充:tvEditRealDistance 距离文本控件) | ||||||
|  | 	public static class EditViewHolder extends RecyclerView.ViewHolder { | ||||||
|  | 		TextView tvEditLongitude; | ||||||
|  | 		TextView tvEditLatitude; | ||||||
|  | 		EditText etEditMemo; | ||||||
|  | 		RadioGroup rgRealDistanceSwitch; | ||||||
|  | 		RadioButton rbDisable; | ||||||
|  | 		RadioButton rbEnable; | ||||||
|  | 		Button btnEditDelete; | ||||||
|  | 		Button btnEditCancel; | ||||||
|  | 		Button btnEditConfirm; | ||||||
|  | 		PositionTaskListView ptlvEditTasks; | ||||||
|  | 		Button btnAddTask; | ||||||
|  | 		TextView tvEditRealDistance; // 新增:编辑视图-距离文本(与布局文件对应) | ||||||
|  |  | ||||||
|  | 		public EditViewHolder(@NonNull View itemView) { | ||||||
|  | 			super(itemView); | ||||||
|  | 			tvEditLongitude = (TextView) itemView.findViewById(R.id.tv_edit_longitude); | ||||||
|  | 			tvEditLatitude = (TextView) itemView.findViewById(R.id.tv_edit_latitude); | ||||||
|  | 			etEditMemo = (EditText) itemView.findViewById(R.id.et_edit_memo); | ||||||
|  | 			rgRealDistanceSwitch = (RadioGroup) itemView.findViewById(R.id.rg_real_distance_switch); | ||||||
|  | 			rbDisable = (RadioButton) itemView.findViewById(R.id.rb_disable); | ||||||
|  | 			rbEnable = (RadioButton) itemView.findViewById(R.id.rb_enable); | ||||||
|  | 			btnEditDelete = (Button) itemView.findViewById(R.id.btn_edit_delete); | ||||||
|  | 			btnEditCancel = (Button) itemView.findViewById(R.id.btn_edit_cancel); | ||||||
|  | 			btnEditConfirm = (Button) itemView.findViewById(R.id.btn_edit_confirm); | ||||||
|  | 			ptlvEditTasks = (PositionTaskListView) itemView.findViewById(R.id.ptlv_edit_tasks); | ||||||
|  | 			btnAddTask = (Button) itemView.findViewById(R.id.btn_add_task); | ||||||
|  | 			tvEditRealDistance = (TextView) itemView.findViewById(R.id.tv_edit_real_distance); // 绑定编辑视图的距离控件 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,39 +12,42 @@ import java.io.IOException; | |||||||
| import java.util.UUID; | import java.util.UUID; | ||||||
|  |  | ||||||
| public class PositionModel extends BaseBean { | public class PositionModel extends BaseBean { | ||||||
|      |  | ||||||
|     public static final String TAG = "PositionModel"; |     public static final String TAG = "PositionModel"; | ||||||
| 	// 位置标识符 | 	// 位置唯一标识符(与任务的positionId绑定) | ||||||
| 	String positionId; | 	String positionId; | ||||||
| 	// 经度 | 	// 经度(范围:-180~180) | ||||||
| 	double longitude; | 	double longitude; | ||||||
| 	// 纬度 | 	// 纬度(范围:-90~90) | ||||||
| 	double latitude; | 	double latitude; | ||||||
| 	// 位置信息备注 | 	// 位置备注(空值时显示“无备注”) | ||||||
| 	String memo; | 	String memo; | ||||||
| 	// 是否启用实时距离计算 | 	// 是否启用实时距离计算 | ||||||
| 	boolean isEnableRealPositionDistance; | 	boolean isEnableRealPositionDistance; | ||||||
| 	// 是否是简单视图 | 	// 是否显示简单视图(true=简单视图,false=编辑视图) | ||||||
| 	boolean isSimpleView = true; | 	boolean isSimpleView = true; | ||||||
|  |  | ||||||
|  | 	// 带参构造(强制初始化位置ID和经纬度) | ||||||
| 	public PositionModel(String positionId, double longitude, double latitude, String memo, boolean isEnableRealPositionDistance) { | 	public PositionModel(String positionId, double longitude, double latitude, String memo, boolean isEnableRealPositionDistance) { | ||||||
| 		this.positionId = positionId; | 		this.positionId = (positionId == null || positionId.trim().isEmpty()) ? genPositionId() : positionId; | ||||||
| 		this.longitude = longitude; | 		this.longitude = Math.max(-180, Math.min(180, longitude)); // 经度范围限制 | ||||||
| 		this.latitude = latitude; | 		this.latitude = Math.max(-90, Math.min(90, latitude)); // 纬度范围限制 | ||||||
| 		this.memo = memo; | 		this.memo = (memo == null || memo.trim().isEmpty()) ? "无备注" : memo; | ||||||
| 		this.isEnableRealPositionDistance = isEnableRealPositionDistance; | 		this.isEnableRealPositionDistance = isEnableRealPositionDistance; | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
|  | 	// 无参构造(默认值初始化,避免空指针) | ||||||
| 	public PositionModel() { | 	public PositionModel() { | ||||||
| 		this.positionId = ""; | 		this.positionId = genPositionId(); | ||||||
| 		this.longitude = 0.0f; | 		this.longitude = 0.0; | ||||||
| 		this.latitude = 0.0f; | 		this.latitude = 0.0; | ||||||
| 		this.memo = ""; | 		this.memo = "无备注"; | ||||||
| 		this.isEnableRealPositionDistance = false; | 		this.isEnableRealPositionDistance = false; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// ---------------------- Getter/Setter(确保字段有效性) ---------------------- | ||||||
| 	public void setPositionId(String positionId) { | 	public void setPositionId(String positionId) { | ||||||
| 		this.positionId = positionId; | 		this.positionId = (positionId == null || positionId.trim().isEmpty()) ? genPositionId() : positionId; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public String getPositionId() { | 	public String getPositionId() { | ||||||
| @@ -68,7 +71,7 @@ public class PositionModel extends BaseBean { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public void setMemo(String memo) { | 	public void setMemo(String memo) { | ||||||
| 		this.memo = memo; | 		this.memo = (memo == null || memo.trim().isEmpty()) ? "无备注" : memo; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public String getMemo() { | 	public String getMemo() { | ||||||
| @@ -76,7 +79,7 @@ public class PositionModel extends BaseBean { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public void setLongitude(double longitude) { | 	public void setLongitude(double longitude) { | ||||||
| 		this.longitude = longitude; | 		this.longitude = Math.max(-180, Math.min(180, longitude)); // 限制经度范围 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public double getLongitude() { | 	public double getLongitude() { | ||||||
| @@ -84,79 +87,84 @@ public class PositionModel extends BaseBean { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public void setLatitude(double latitude) { | 	public void setLatitude(double latitude) { | ||||||
| 		this.latitude = latitude; | 		this.latitude = Math.max(-90, Math.min(90, latitude)); // 限制纬度范围 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public double getLatitude() { | 	public double getLatitude() { | ||||||
| 		return latitude; | 		return latitude; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|     @Override | 	// ---------------------- 父类方法重写 ---------------------- | ||||||
|     public String getName() { | 	@Override | ||||||
|         return PositionModel.class.getName(); | 	public String getName() { | ||||||
|     } | 		return PositionModel.class.getName(); | ||||||
| 	 | 	} | ||||||
|  |  | ||||||
|  | 	// 生成唯一位置ID(与任务ID格式一致,确保关联匹配) | ||||||
| 	public static String genPositionId() { | 	public static String genPositionId() { | ||||||
|         // 生成唯一UUID(版本4,随机型) |  | ||||||
|         UUID uniqueUuid = UUID.randomUUID(); |         UUID uniqueUuid = UUID.randomUUID(); | ||||||
|         // 转成字符串(标准格式,含横杠,共36位) |         return uniqueUuid.toString(); // 36位标准UUID(含横杠,确保与任务ID格式统一) | ||||||
|         String uuidStr = uniqueUuid.toString();  |  | ||||||
|         return uuidStr; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override | 	// JSON序列化(保存位置数据) | ||||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | 	@Override | ||||||
|         super.writeThisToJsonWriter(jsonWriter); | 	public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||||
|         jsonWriter.name("positionId").value(getPositionId()); | 		super.writeThisToJsonWriter(jsonWriter); | ||||||
|         jsonWriter.name("longitude").value(getLongitude()); | 		jsonWriter.name("positionId").value(getPositionId()); | ||||||
|         jsonWriter.name("latitude").value(getLatitude()); | 		jsonWriter.name("longitude").value(getLongitude()); | ||||||
|         jsonWriter.name("memo").value(getMemo()); | 		jsonWriter.name("latitude").value(getLatitude()); | ||||||
|         jsonWriter.name("isEnableRealPositionDistance").value(isEnableRealPositionDistance()); | 		jsonWriter.name("memo").value(getMemo()); | ||||||
|     } | 		jsonWriter.name("isEnableRealPositionDistance").value(isEnableRealPositionDistance()); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|     @Override | 	// JSON反序列化(加载位置数据,校验字段) | ||||||
|     public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | 	@Override | ||||||
|         if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { | 	public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | ||||||
|             if (name.equals("positionId")) { | 		if (super.initObjectsFromJsonReader(jsonReader, name)) {  | ||||||
|                 setPositionId(jsonReader.nextString()); | 			return true;  | ||||||
|             } else if (name.equals("longitude")) { | 		} else { | ||||||
|                 setLongitude(jsonReader.nextDouble()); | 			if (name.equals("positionId")) { | ||||||
|             } else if (name.equals("latitude")) { | 				setPositionId(jsonReader.nextString()); | ||||||
|                 setLatitude(jsonReader.nextDouble()); | 			} else if (name.equals("longitude")) { | ||||||
|             } else if (name.equals("memo")) { | 				setLongitude(jsonReader.nextDouble()); | ||||||
|                 setMemo(jsonReader.nextString()); | 			} else if (name.equals("latitude")) { | ||||||
|             } else if (name.equals("isEnableRealPositionDistance")) { | 				setLatitude(jsonReader.nextDouble()); | ||||||
|                 setIsEnableRealPositionDistance(jsonReader.nextBoolean()); | 			} else if (name.equals("memo")) { | ||||||
|             } else { | 				setMemo(jsonReader.nextString()); | ||||||
|                 return false; | 			} else if (name.equals("isEnableRealPositionDistance")) { | ||||||
|             } | 				setIsEnableRealPositionDistance(jsonReader.nextBoolean()); | ||||||
|         } | 			} else { | ||||||
|         return true; | 				return false; | ||||||
|     } | 			} | ||||||
|  | 		} | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|     @Override | 	// 从JSON读取位置数据 | ||||||
|     public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { | 	@Override | ||||||
|         jsonReader.beginObject(); | 	public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { | ||||||
|         while (jsonReader.hasNext()) { | 		jsonReader.beginObject(); | ||||||
|             String name = jsonReader.nextName(); | 		while (jsonReader.hasNext()) { | ||||||
|             if (!initObjectsFromJsonReader(jsonReader, name)) { | 			String name = jsonReader.nextName(); | ||||||
|                 jsonReader.skipValue(); | 			if (!initObjectsFromJsonReader(jsonReader, name)) { | ||||||
|             } | 				jsonReader.skipValue(); // 跳过未知字段 | ||||||
|         } | 			} | ||||||
|         // 结束 JSON 对象 | 		} | ||||||
|         jsonReader.endObject(); | 		jsonReader.endObject(); | ||||||
|         return this; | 		return this; | ||||||
|     } | 	} | ||||||
|      |  | ||||||
|  | 	// ---------------------- 核心工具方法:计算两点距离(Haversine公式,确保精度) ---------------------- | ||||||
| 	/** | 	/** | ||||||
| 	 * 计算两个位置之间的直线距离(地球表面最短距离,基于Haversine公式) | 	 * 计算两个位置之间的直线距离(地球表面最短距离) | ||||||
| 	 * @param position1 第一个位置(PositionModel对象,含longitude/latitude) | 	 * @param position1 第一个位置(非null) | ||||||
| 	 * @param position2 第二个位置(PositionModel对象,含longitude/latitude) | 	 * @param position2 第二个位置(非null) | ||||||
| 	 * @param isKilometer 是否返回千米单位:true→千米(km),false→米(m) | 	 * @param isKilometer 是否返回千米单位:true→千米,false→米 | ||||||
| 	 * @return 两个位置间的距离(保留2位小数,单位由isKilometer决定) | 	 * @return 距离(保留1位小数,符合显示需求) | ||||||
|  | 	 * @throws IllegalArgumentException 位置为null或经纬度无效时抛出 | ||||||
| 	 */ | 	 */ | ||||||
| 	public static double calculatePositionDistance(PositionModel position1, PositionModel position2, boolean isKilometer) { | 	public static double calculatePositionDistance(PositionModel position1, PositionModel position2, boolean isKilometer) { | ||||||
| 		// 1. 校验参数(避免空指针/无效经纬度,防止计算异常) | 		// 1. 校验参数有效性(避免计算异常) | ||||||
| 		if (position1 == null || position2 == null) { | 		if (position1 == null || position2 == null) { | ||||||
| 			throw new IllegalArgumentException("位置对象不能为null"); | 			throw new IllegalArgumentException("位置对象不能为null"); | ||||||
| 		} | 		} | ||||||
| @@ -164,41 +172,38 @@ public class PositionModel extends BaseBean { | |||||||
| 		double lat1 = position1.getLatitude(); | 		double lat1 = position1.getLatitude(); | ||||||
| 		double lon2 = position2.getLongitude(); | 		double lon2 = position2.getLongitude(); | ||||||
| 		double lat2 = position2.getLatitude(); | 		double lat2 = position2.getLatitude(); | ||||||
| 		// 经纬度范围校验(纬度:-90~90,经度:-180~180,超出则视为无效值) | 		// 经纬度范围二次校验(确保有效) | ||||||
| 		if (lat1 < -90 || lat1 > 90 || lat2 < -90 || lat2 > 90  | 		if (lat1 < -90 || lat1 > 90 || lat2 < -90 || lat2 > 90  | ||||||
| 			|| lon1 < -180 || lon1 > 180 || lon2 < -180 || lon2 > 180) { | 			|| lon1 < -180 || lon1 > 180 || lon2 < -180 || lon2 > 180) { | ||||||
| 			throw new IllegalArgumentException("经纬度值无效(纬度:-90~90,经度:-180~180)"); | 			throw new IllegalArgumentException("经纬度值无效(纬度:-90~90,经度:-180~180)"); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// 2. Haversine公式核心计算(将角度转为弧度,地球半径取平均半径6371km) | 		// 2. Haversine公式计算(地球半径取6371km,行业标准) | ||||||
| 		final double EARTH_RADIUS_KM = 6371; // 地球平均半径(单位:千米) | 		final double EARTH_RADIUS_KM = 6371; | ||||||
| 		// 经纬度转为弧度(Math.toRadians:角度→弧度,公式计算需弧度值) | 		double radLat1 = Math.toRadians(lat1); // 角度转弧度 | ||||||
| 		double radLat1 = Math.toRadians(lat1); |  | ||||||
| 		double radLat2 = Math.toRadians(lat2); | 		double radLat2 = Math.toRadians(lat2); | ||||||
| 		double radLon1 = Math.toRadians(lon1); | 		double radLon1 = Math.toRadians(lon1); | ||||||
| 		double radLon2 = Math.toRadians(lon2); | 		double radLon2 = Math.toRadians(lon2); | ||||||
|  |  | ||||||
| 		// 计算纬度差、经度差 | 		double deltaLat = radLat2 - radLat1; // 纬度差 | ||||||
| 		double deltaLat = radLat2 - radLat1; | 		double deltaLon = radLon2 - radLon1; // 经度差 | ||||||
| 		double deltaLon = radLon2 - radLon1; |  | ||||||
|  |  | ||||||
| 		// Haversine公式:a = sin²(Δφ/2) + cos φ1 ⋅ cos φ2 ⋅ sin²(Δλ/2) | 		// 核心公式 | ||||||
| 		double a = Math.pow(Math.sin(deltaLat / 2), 2)  | 		double a = Math.pow(Math.sin(deltaLat / 2), 2)  | ||||||
| 			+ Math.cos(radLat1) * Math.cos(radLat2)  | 			+ Math.cos(radLat1) * Math.cos(radLat2)  | ||||||
| 			* Math.pow(Math.sin(deltaLon / 2), 2); | 			* Math.pow(Math.sin(deltaLon / 2), 2); | ||||||
| 		// c = 2 ⋅ atan2(√a, √(1−a)) (计算圆心角) |  | ||||||
| 		double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | 		double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | ||||||
| 		// 距离 = 地球半径 × 圆心角(单位:千米) | 		double distanceKm = EARTH_RADIUS_KM * c; // 距离(千米) | ||||||
| 		double distanceKm = EARTH_RADIUS_KM * c; |  | ||||||
|  |  | ||||||
| 		// 3. 单位转换+保留2位小数(避免精度冗余,符合日常使用习惯) | 		// 3. 单位转换+精度处理(保留1位小数,符合显示需求) | ||||||
| 		double distance; | 		double distance; | ||||||
| 		if (isKilometer) { | 		if (isKilometer) { | ||||||
| 			distance = Math.round(distanceKm * 100.0) / 100.0; // 千米,保留2位小数 | 			distance = Math.round(distanceKm * 10.0) / 10.0; // 千米→1位小数 | ||||||
| 		} else { | 		} else { | ||||||
| 			distance = Math.round(distanceKm * 1000 * 100.0) / 100.0; // 米,保留2位小数(1km=1000m) | 			distance = Math.round(distanceKm * 1000 * 10.0) / 10.0; // 米→1位小数 | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		return distance; | 		return distance; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,43 +14,46 @@ import java.util.UUID; | |||||||
| public class PositionTaskModel extends BaseBean { | public class PositionTaskModel extends BaseBean { | ||||||
|      |      | ||||||
|     public static final String TAG = "PositionTaskModel"; |     public static final String TAG = "PositionTaskModel"; | ||||||
|     // 任务标识符 |     // 任务标识符(唯一) | ||||||
| 	String taskId; | 	String taskId; | ||||||
| 	// 位置标识符 | 	// 绑定的位置标识符(与PositionModel的positionId一一对应) | ||||||
| 	String positionId; | 	String positionId; | ||||||
| 	// 任务描述 | 	// 任务描述 | ||||||
| 	String taskDescription; | 	String taskDescription; | ||||||
| 	// 任务距离条件是否大于 | 	// 任务距离条件:是否大于设定距离 | ||||||
| 	boolean isGreaterThan; | 	boolean isGreaterThan; | ||||||
| 	// 任务距离条件是否小于 | 	// 任务距离条件:是否小于设定距离(与isGreaterThan互斥) | ||||||
| 	boolean isLessThan; | 	boolean isLessThan; | ||||||
| 	// 任务条件商议距离 | 	// 任务条件距离(单位:米) | ||||||
| 	int discussDistance; | 	int discussDistance; | ||||||
| 	// 是否启用任务 | 	// 是否启用任务 | ||||||
| 	boolean isEnable; | 	boolean isEnable; | ||||||
|  |  | ||||||
|  | 	// 带参构造(强制传入positionId,确保任务与位置绑定) | ||||||
| 	public PositionTaskModel(String taskId, String positionId, String taskDescription, boolean isGreaterThan, int discussDistance, boolean isEnable) { | 	public PositionTaskModel(String taskId, String positionId, String taskDescription, boolean isGreaterThan, int discussDistance, boolean isEnable) { | ||||||
| 		this.taskId = taskId; | 		this.taskId = (taskId == null || taskId.trim().isEmpty()) ? genTaskId() : taskId; // 空ID自动生成 | ||||||
| 		this.positionId = positionId; | 		this.positionId = positionId; // 强制绑定位置ID | ||||||
| 		this.taskDescription = taskDescription; | 		this.taskDescription = (taskDescription == null || taskDescription.trim().isEmpty()) ? "新任务" : taskDescription; | ||||||
| 		this.isGreaterThan = isGreaterThan; | 		this.isGreaterThan = isGreaterThan; | ||||||
| 		this.isLessThan = !this.isGreaterThan; | 		this.isLessThan = !isGreaterThan; // 确保互斥 | ||||||
| 		this.discussDistance = discussDistance; | 		this.discussDistance = Math.max(discussDistance, 1); // 距离最小1米,避免无效值 | ||||||
| 		this.isEnable = isEnable; | 		this.isEnable = isEnable; | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
|  | 	// 无参构造(初始化默认值,positionId需后续设置) | ||||||
| 	public PositionTaskModel() { | 	public PositionTaskModel() { | ||||||
| 		this.taskId = ""; | 		this.taskId = genTaskId(); | ||||||
| 		this.positionId = ""; | 		this.positionId = ""; | ||||||
| 		this.taskDescription = ""; | 		this.taskDescription = "新任务"; | ||||||
| 		this.isGreaterThan = true; | 		this.isGreaterThan = true; | ||||||
| 		this.isLessThan = !this.isGreaterThan ; | 		this.isLessThan = false; // 初始互斥 | ||||||
| 		this.discussDistance = 0; | 		this.discussDistance = 100; // 默认100米 | ||||||
| 		this.isEnable = false; | 		this.isEnable = true; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// ---------------------- Getter/Setter(确保positionId不为空,距离有效) ---------------------- | ||||||
| 	public void setTaskId(String taskId) { | 	public void setTaskId(String taskId) { | ||||||
| 		this.taskId = taskId; | 		this.taskId = (taskId == null || taskId.trim().isEmpty()) ? genTaskId() : taskId; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public String getTaskId() { | 	public String getTaskId() { | ||||||
| @@ -58,7 +61,7 @@ public class PositionTaskModel extends BaseBean { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public void setPositionId(String positionId) { | 	public void setPositionId(String positionId) { | ||||||
| 		this.positionId = positionId; | 		this.positionId = (positionId == null || positionId.trim().isEmpty()) ? "" : positionId; // 空值防护 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public String getPositionId() { | 	public String getPositionId() { | ||||||
| @@ -66,25 +69,27 @@ public class PositionTaskModel extends BaseBean { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public void setTaskDescription(String taskDescription) { | 	public void setTaskDescription(String taskDescription) { | ||||||
| 		this.taskDescription = taskDescription; | 		this.taskDescription = (taskDescription == null || taskDescription.trim().isEmpty()) ? "新任务" : taskDescription; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public String getTaskDescription() { | 	public String getTaskDescription() { | ||||||
| 		return taskDescription; | 		return taskDescription; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// 修复:确保isGreaterThan和isLessThan互斥 | ||||||
| 	public void setIsGreaterThan(boolean isGreaterThan) { | 	public void setIsGreaterThan(boolean isGreaterThan) { | ||||||
| 		this.isGreaterThan = isGreaterThan; | 		this.isGreaterThan = isGreaterThan; | ||||||
| 		this.isLessThan = this.isGreaterThan; | 		this.isLessThan = !isGreaterThan; // 关键:小于 = 非大于 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public boolean isGreaterThan() { | 	public boolean isGreaterThan() { | ||||||
| 		return isGreaterThan; | 		return isGreaterThan; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// 修复:确保isLessThan和isGreaterThan互斥 | ||||||
| 	public void setIsLessThan(boolean isLessThan) { | 	public void setIsLessThan(boolean isLessThan) { | ||||||
| 		this.isLessThan = isLessThan; | 		this.isLessThan = isLessThan; | ||||||
| 		this.isGreaterThan = !this.isLessThan; | 		this.isGreaterThan = !isLessThan; // 关键:大于 = 非小于 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public boolean isLessThan() { | 	public boolean isLessThan() { | ||||||
| @@ -92,7 +97,7 @@ public class PositionTaskModel extends BaseBean { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public void setDiscussDistance(int discussDistance) { | 	public void setDiscussDistance(int discussDistance) { | ||||||
| 		this.discussDistance = discussDistance; | 		this.discussDistance = Math.max(discussDistance, 1); // 距离最小1米,避免0或负数 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public int getDiscussDistance() { | 	public int getDiscussDistance() { | ||||||
| @@ -107,67 +112,71 @@ public class PositionTaskModel extends BaseBean { | |||||||
| 		return isEnable; | 		return isEnable; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|     @Override | 	// ---------------------- 父类方法重写 ---------------------- | ||||||
|     public String getName() { | 	@Override | ||||||
|         return PositionTaskModel.class.getName(); | 	public String getName() { | ||||||
|     } | 		return PositionTaskModel.class.getName(); | ||||||
|  | 	} | ||||||
| 	 | 	 | ||||||
|  | 	// 生成唯一任务ID(与PositionModel保持一致格式) | ||||||
| 	public static String genTaskId() { | 	public static String genTaskId() { | ||||||
|         // 生成唯一UUID(版本4,随机型) |  | ||||||
|         UUID uniqueUuid = UUID.randomUUID(); |         UUID uniqueUuid = UUID.randomUUID(); | ||||||
|         // 转成字符串(标准格式,含横杠,共36位) |         return uniqueUuid.toString(); // 36位标准UUID(含横杠,确保唯一) | ||||||
|         String uuidStr = uniqueUuid.toString();  |  | ||||||
|         return uuidStr; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override | 	// JSON序列化(保存任务数据,包含所有字段) | ||||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | 	@Override | ||||||
|         super.writeThisToJsonWriter(jsonWriter); | 	public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||||
|         jsonWriter.name("taskId").value(getTaskId()); | 		super.writeThisToJsonWriter(jsonWriter); | ||||||
|         jsonWriter.name("positionId").value(getPositionId()); | 		jsonWriter.name("taskId").value(getTaskId()); | ||||||
|         jsonWriter.name("taskDescription").value(getTaskDescription()); | 		jsonWriter.name("positionId").value(getPositionId()); | ||||||
|         jsonWriter.name("isGreaterThan").value(isGreaterThan()); | 		jsonWriter.name("taskDescription").value(getTaskDescription()); | ||||||
|         jsonWriter.name("isLessThan").value(isLessThan()); | 		jsonWriter.name("isGreaterThan").value(isGreaterThan()); | ||||||
|         jsonWriter.name("discussDistance").value(getDiscussDistance()); | 		jsonWriter.name("isLessThan").value(isLessThan()); | ||||||
|         jsonWriter.name("isEnable").value(isEnable()); | 		jsonWriter.name("discussDistance").value(getDiscussDistance()); | ||||||
|     } | 		jsonWriter.name("isEnable").value(isEnable()); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|     @Override | 	// JSON反序列化(加载任务数据,校验字段有效性) | ||||||
|     public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | 	@Override | ||||||
|         if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { | 	public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | ||||||
|             if (name.equals("taskId")) { | 		if (super.initObjectsFromJsonReader(jsonReader, name)) {  | ||||||
|                 setTaskId(jsonReader.nextString()); | 			return true;  | ||||||
|             } else if (name.equals("positionId")) { | 		} else { | ||||||
|                 setPositionId(jsonReader.nextString()); | 			if (name.equals("taskId")) { | ||||||
|             } else if (name.equals("taskDescription")) { | 				setTaskId(jsonReader.nextString()); | ||||||
|                 setTaskDescription(jsonReader.nextString()); | 			} else if (name.equals("positionId")) { | ||||||
|             } else if (name.equals("isGreaterThan")) { | 				setPositionId(jsonReader.nextString()); | ||||||
|                 setIsGreaterThan(jsonReader.nextBoolean()); | 			} else if (name.equals("taskDescription")) { | ||||||
|             } else if (name.equals("isLessThan")) { | 				setTaskDescription(jsonReader.nextString()); | ||||||
|                 setIsLessThan(jsonReader.nextBoolean()); | 			} else if (name.equals("isGreaterThan")) { | ||||||
|             } else if (name.equals("discussDistance")) { | 				setIsGreaterThan(jsonReader.nextBoolean()); | ||||||
|                 setDiscussDistance(jsonReader.nextInt()); | 			} else if (name.equals("isLessThan")) { | ||||||
|             } else if (name.equals("isEnable")) { | 				setIsLessThan(jsonReader.nextBoolean()); | ||||||
|                 setIsEnable(jsonReader.nextBoolean()); | 			} else if (name.equals("discussDistance")) { | ||||||
|             } else { | 				setDiscussDistance(jsonReader.nextInt()); | ||||||
|                 return false; | 			} else if (name.equals("isEnable")) { | ||||||
|             } | 				setIsEnable(jsonReader.nextBoolean()); | ||||||
|         } | 			} else { | ||||||
|         return true; | 				return false; | ||||||
|     } | 			} | ||||||
|  | 		} | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|     @Override | 	// 从JSON读取任务数据(确保反序列化完整) | ||||||
|     public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { | 	@Override | ||||||
|         jsonReader.beginObject(); | 	public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { | ||||||
|         while (jsonReader.hasNext()) { | 		jsonReader.beginObject(); | ||||||
|             String name = jsonReader.nextName(); | 		while (jsonReader.hasNext()) { | ||||||
|             if (!initObjectsFromJsonReader(jsonReader, name)) { | 			String name = jsonReader.nextName(); | ||||||
|                 jsonReader.skipValue(); | 			if (!initObjectsFromJsonReader(jsonReader, name)) { | ||||||
|             } | 				jsonReader.skipValue(); // 跳过未知字段,避免崩溃 | ||||||
|         } | 			} | ||||||
|         // 结束 JSON 对象 | 		} | ||||||
|         jsonReader.endObject(); | 		jsonReader.endObject(); | ||||||
|         return this; | 		return this; | ||||||
|     } | 	} | ||||||
|      |      | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,424 @@ | |||||||
|  | package cc.winboll.studio.positions.views; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> | ||||||
|  |  * @Date 2025/09/30 08:09 | ||||||
|  |  * @Describe 位置任务列表视图(支持简单/编辑模式,确保任务修改实时生效) | ||||||
|  |  */ | ||||||
|  | import android.content.Context; | ||||||
|  | import android.util.AttributeSet; | ||||||
|  | import android.view.LayoutInflater; | ||||||
|  | import android.view.View; | ||||||
|  | import android.view.ViewGroup; | ||||||
|  | import android.widget.Button; | ||||||
|  | import android.widget.CompoundButton; | ||||||
|  | import android.widget.EditText; | ||||||
|  | import android.widget.LinearLayout; | ||||||
|  | import android.widget.RadioButton; | ||||||
|  | import android.widget.RadioGroup; | ||||||
|  | import android.widget.TextView; | ||||||
|  | import android.widget.Toast; | ||||||
|  | import androidx.annotation.NonNull; | ||||||
|  | import androidx.annotation.Nullable; | ||||||
|  | import androidx.recyclerview.widget.LinearLayoutManager; | ||||||
|  | import androidx.recyclerview.widget.RecyclerView; | ||||||
|  | import cc.winboll.studio.positions.R; | ||||||
|  | import cc.winboll.studio.positions.models.PositionTaskModel; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | public class PositionTaskListView extends LinearLayout { | ||||||
|  |     // 视图模式常量 | ||||||
|  |     public static final int VIEW_MODE_SIMPLE = 1; | ||||||
|  |     public static final int VIEW_MODE_EDIT = 2; | ||||||
|  |  | ||||||
|  |     // 核心成员变量 | ||||||
|  |     private String mBindPositionId; | ||||||
|  |     private ArrayList<PositionTaskModel> mTaskList; | ||||||
|  |     private int mCurrentViewMode; | ||||||
|  |     private TaskListAdapter mTaskAdapter; | ||||||
|  |     private RecyclerView mRvTasks; | ||||||
|  |  | ||||||
|  |     // 任务修改回调接口 | ||||||
|  |     public interface OnTaskUpdatedListener { | ||||||
|  |         void onTaskUpdated(String positionId, ArrayList<PositionTaskModel> updatedTasks); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private OnTaskUpdatedListener mOnTaskUpdatedListener; | ||||||
|  |  | ||||||
|  |     // ---------------------- 构造函数 ---------------------- | ||||||
|  |     public PositionTaskListView(Context context) { | ||||||
|  |         super(context); | ||||||
|  |         initView(context); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public PositionTaskListView(Context context, @Nullable AttributeSet attrs) { | ||||||
|  |         super(context, attrs); | ||||||
|  |         initView(context); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public PositionTaskListView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | ||||||
|  |         super(context, attrs, defStyleAttr); | ||||||
|  |         initView(context); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 初始化视图(绑定控件+设置布局) | ||||||
|  |     private void initView(Context context) { | ||||||
|  |         setOrientation(VERTICAL); | ||||||
|  |         // 加载根布局(view_position_task_list.xml) | ||||||
|  |         LayoutInflater.from(context).inflate(R.layout.view_position_task_list, this, true); | ||||||
|  |  | ||||||
|  |         // 绑定RecyclerView并设置布局管理器 | ||||||
|  |         mRvTasks = (RecyclerView) findViewById(R.id.rv_position_tasks); | ||||||
|  |         mRvTasks.setLayoutManager(new LinearLayoutManager(context)); | ||||||
|  |  | ||||||
|  |         // 初始化任务列表+适配器(强引用关联,确保数据同步) | ||||||
|  |         mTaskList = new ArrayList<PositionTaskModel>(); | ||||||
|  |         mTaskAdapter = new TaskListAdapter(mTaskList); | ||||||
|  |         mRvTasks.setAdapter(mTaskAdapter); | ||||||
|  |  | ||||||
|  |         // 默认简单模式 | ||||||
|  |         mCurrentViewMode = VIEW_MODE_SIMPLE; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // ---------------------- 对外API ---------------------- | ||||||
|  |     /** | ||||||
|  |      * 初始化任务列表(仅首次空列表时加载,避免覆盖修改后数据) | ||||||
|  |      */ | ||||||
|  |     public void init(ArrayList<PositionTaskModel> taskList, String positionId) { | ||||||
|  |         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(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 设置视图模式(简单/编辑) | ||||||
|  |      */ | ||||||
|  |     public void setViewStatus(int viewMode) { | ||||||
|  |         if (viewMode != VIEW_MODE_SIMPLE && viewMode != VIEW_MODE_EDIT) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         mCurrentViewMode = viewMode; | ||||||
|  |         mTaskAdapter.notifyDataSetChanged(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 设置任务修改回调 | ||||||
|  |      */ | ||||||
|  |     public void setOnTaskUpdatedListener(OnTaskUpdatedListener listener) { | ||||||
|  |         this.mOnTaskUpdatedListener = listener; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取当前任务列表(返回副本,避免外部修改污染) | ||||||
|  |      */ | ||||||
|  |     public ArrayList<PositionTaskModel> getAllTasks() { | ||||||
|  |         return new ArrayList<PositionTaskModel>(mTaskList); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 清空任务数据(避免RecyclerView复用残留) | ||||||
|  |      */ | ||||||
|  |     public void clearData() { | ||||||
|  |         mTaskList.clear(); | ||||||
|  |         if (mTaskAdapter != null && mTaskAdapter.mData != null) { | ||||||
|  |             mTaskAdapter.mData.clear(); | ||||||
|  |         } | ||||||
|  |         mTaskAdapter.notifyDataSetChanged(); | ||||||
|  |         mBindPositionId = null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 主动触发任务同步(供外部强制同步数据) | ||||||
|  |      */ | ||||||
|  |     public void triggerTaskSync() { | ||||||
|  |         if (mOnTaskUpdatedListener != null && mBindPositionId != null) { | ||||||
|  |             mOnTaskUpdatedListener.onTaskUpdated(mBindPositionId, new ArrayList<PositionTaskModel>(mTaskList)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // ---------------------- 内部工具方法 ---------------------- | ||||||
|  |     /** | ||||||
|  |      * 校验任务是否与当前位置匹配 | ||||||
|  |      */ | ||||||
|  |     private boolean isTaskMatchedWithPosition(PositionTaskModel task) { | ||||||
|  |         if (task == null || mBindPositionId == null || mBindPositionId.trim().isEmpty()) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         return mBindPositionId.equals(task.getPositionId()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // ---------------------- 内部Adapter:任务列表适配器(核心修改:确保修改生效) ---------------------- | ||||||
|  |     private class TaskListAdapter extends RecyclerView.Adapter<TaskListAdapter.TaskViewHolder> { | ||||||
|  |         // 适配器数据源(与外部mTaskList强引用,确保数据实时同步) | ||||||
|  |         private final List<PositionTaskModel> mData; | ||||||
|  |  | ||||||
|  |         public TaskListAdapter(List<PositionTaskModel> data) { | ||||||
|  |             this.mData = data; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 获取列表项数量(空列表显示1个“空提示”项) | ||||||
|  |         @Override | ||||||
|  |         public int getItemCount() { | ||||||
|  |             return mData.isEmpty() ? 1 : mData.size(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 区分视图类型(0=空提示,1=任务项) | ||||||
|  |         @Override | ||||||
|  |         public int getItemViewType(int position) { | ||||||
|  |             return mData.isEmpty() ? 0 : 1; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 创建ViewHolder | ||||||
|  |         @NonNull | ||||||
|  |         @Override | ||||||
|  |         public TaskViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { | ||||||
|  |             Context context = parent.getContext(); | ||||||
|  |             LayoutInflater inflater = LayoutInflater.from(context); | ||||||
|  |             if (viewType == 0) { | ||||||
|  |                 // 空提示布局(item_task_empty.xml) | ||||||
|  |                 View emptyView = inflater.inflate(R.layout.item_task_empty, parent, false); | ||||||
|  |                 return new EmptyViewHolder(emptyView); | ||||||
|  |             } else { | ||||||
|  |                 // 任务项布局(item_task_content.xml) | ||||||
|  |                 View taskView = inflater.inflate(R.layout.item_task_content, parent, false); | ||||||
|  |                 return new TaskContentViewHolder(taskView); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 绑定数据(核心:修复开关监听重复绑定+实时刷新) | ||||||
|  |         @Override | ||||||
|  |         public void onBindViewHolder(@NonNull TaskViewHolder holder, int position) { | ||||||
|  |             if (holder instanceof EmptyViewHolder) { | ||||||
|  |                 // 空提示项无需绑定数据 | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 校验位置有效性(避免越界) | ||||||
|  |             if (position >= mData.size()) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             final PositionTaskModel task = mData.get(position); | ||||||
|  |             if (task == null) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 绑定任务数据 | ||||||
|  |             final TaskContentViewHolder contentHolder = (TaskContentViewHolder) holder; | ||||||
|  |             bindTaskData(contentHolder, task, position); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * 绑定任务具体数据(核心修复:开关监听去重+修改后实时刷新) | ||||||
|  |          */ | ||||||
|  |         private void bindTaskData(final TaskContentViewHolder holder, final PositionTaskModel task, final int position) { | ||||||
|  |             // 1. 绑定基础信息(描述、距离条件、启用状态) | ||||||
|  |             String taskDesc = (task.getTaskDescription() == null) ? "未设置描述" : task.getTaskDescription(); | ||||||
|  |             holder.tvTaskDesc.setText(String.format("任务:%s", taskDesc)); | ||||||
|  |  | ||||||
|  |             String distanceCondition = task.isGreaterThan() ? "大于" : "小于"; | ||||||
|  |             holder.tvTaskDistance.setText(String.format("条件:%s %d 米", distanceCondition, task.getDiscussDistance())); | ||||||
|  |  | ||||||
|  |             holder.cbTaskEnable.setChecked(task.isEnable()); | ||||||
|  |             holder.cbTaskEnable.setEnabled(mCurrentViewMode == VIEW_MODE_EDIT); // 仅编辑模式可点击 | ||||||
|  |  | ||||||
|  |             // 2. 编辑模式处理(核心:修复开关监听重复绑定+修改同步) | ||||||
|  |             if (mCurrentViewMode == VIEW_MODE_EDIT) { | ||||||
|  |                 holder.btnEditTask.setVisibility(View.VISIBLE); | ||||||
|  |                 holder.btnDeleteTask.setVisibility(View.VISIBLE); | ||||||
|  |  | ||||||
|  |                 // (1)删除按钮逻辑 | ||||||
|  |                 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)); | ||||||
|  | 							} | ||||||
|  | 							Toast.makeText(getContext(), "任务已删除", Toast.LENGTH_SHORT).show(); | ||||||
|  | 						} | ||||||
|  | 					}); | ||||||
|  |  | ||||||
|  |                 // (2)修改按钮逻辑(弹窗编辑) | ||||||
|  |                 holder.btnEditTask.setOnClickListener(new View.OnClickListener() { | ||||||
|  | 						@Override | ||||||
|  | 						public void onClick(View v) { | ||||||
|  | 							showTaskEditDialog(task, position); | ||||||
|  | 						} | ||||||
|  | 					}); | ||||||
|  |  | ||||||
|  |                 // (核心修复1:绑定新监听前先移除旧监听,避免重复触发) | ||||||
|  |                 holder.cbTaskEnable.setOnCheckedChangeListener(null); | ||||||
|  |                 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)); | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					}); | ||||||
|  |             } else { | ||||||
|  |                 // 简单模式:隐藏编辑按钮+移除监听 | ||||||
|  |                 holder.btnEditTask.setVisibility(View.GONE); | ||||||
|  |                 holder.btnDeleteTask.setVisibility(View.GONE); | ||||||
|  |                 holder.cbTaskEnable.setOnCheckedChangeListener(null); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * 任务编辑弹窗(核心修复2:修改后实时刷新+同步数据) | ||||||
|  |          */ | ||||||
|  |         private void showTaskEditDialog(final PositionTaskModel task, final int position) { | ||||||
|  |             final Context context = getContext(); | ||||||
|  |             // 加载弹窗布局(dialog_edit_task.xml) | ||||||
|  |             View dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_edit_task, null); | ||||||
|  |  | ||||||
|  |             // 绑定弹窗控件 | ||||||
|  |             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()); // 光标定位到末尾 | ||||||
|  |  | ||||||
|  |             if (task.isGreaterThan()) { | ||||||
|  |                 rgDistanceCondition.check(R.id.rb_greater_than); | ||||||
|  |             } else { | ||||||
|  |                 rgDistanceCondition.check(R.id.rb_less_than); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             etEditDistance.setText(String.valueOf(task.getDiscussDistance())); | ||||||
|  |  | ||||||
|  |             // 创建并显示弹窗 | ||||||
|  |             final android.app.AlertDialog dialog = new android.app.AlertDialog.Builder(context) | ||||||
|  | 				.setView(dialogView) | ||||||
|  | 				.create(); | ||||||
|  |             dialog.show(); | ||||||
|  |  | ||||||
|  |             // 取消按钮:关闭弹窗(不保存) | ||||||
|  |             btnCancel.setOnClickListener(new View.OnClickListener() { | ||||||
|  | 					@Override | ||||||
|  | 					public void onClick(View v) { | ||||||
|  | 						dialog.dismiss(); | ||||||
|  | 					} | ||||||
|  | 				}); | ||||||
|  |  | ||||||
|  |             // 保存按钮:校验+更新+同步(核心修复:实时刷新列表) | ||||||
|  |             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(); | ||||||
|  | 							return; | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						int newDistance; | ||||||
|  | 						try { | ||||||
|  | 							newDistance = Integer.parseInt(distanceStr); | ||||||
|  | 							if (newDistance < 1) { // 距离最小为1米,避免无效值 | ||||||
|  | 								Toast.makeText(context, "距离不能小于1米", Toast.LENGTH_SHORT).show(); | ||||||
|  | 								return; | ||||||
|  | 							} | ||||||
|  | 						} catch (NumberFormatException e) { | ||||||
|  | 							Toast.makeText(context, "距离请输入数字", Toast.LENGTH_SHORT).show(); | ||||||
|  | 							return; | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						// 2. 更新任务数据(直接修改数据源中的任务对象,强引用同步) | ||||||
|  | 						task.setTaskDescription(newDesc); | ||||||
|  | 						task.setDiscussDistance(newDistance); | ||||||
|  | 						// 更新距离条件(大于/小于) | ||||||
|  | 						boolean isGreater = rgDistanceCondition.getCheckedRadioButtonId() == R.id.rb_greater_than; | ||||||
|  | 						task.setIsGreaterThan(isGreater); | ||||||
|  | 						// 强制绑定当前位置ID,避免任务串位 | ||||||
|  | 						task.setPositionId(mBindPositionId); | ||||||
|  |  | ||||||
|  | 						// 3. 核心修复:实时刷新当前任务项,确保修改后立即显示 | ||||||
|  | 						notifyItemChanged(position); | ||||||
|  |  | ||||||
|  | 						// 4. 同步通知外部Adapter更新全局数据 | ||||||
|  | 						if (mOnTaskUpdatedListener != null && mBindPositionId != null) { | ||||||
|  | 							mOnTaskUpdatedListener.onTaskUpdated(mBindPositionId, new ArrayList<PositionTaskModel>(mData)); | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						// 5. 关闭弹窗并提示 | ||||||
|  | 						dialog.dismiss(); | ||||||
|  | 						Toast.makeText(context, "任务已更新", Toast.LENGTH_SHORT).show(); | ||||||
|  | 					} | ||||||
|  | 				}); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // ---------------------- ViewHolder 定义(内部类,绑定布局控件) ---------------------- | ||||||
|  |         /** | ||||||
|  |          * 基础ViewHolder(抽象类,统一父类型) | ||||||
|  |          */ | ||||||
|  |         public abstract class TaskViewHolder extends RecyclerView.ViewHolder { | ||||||
|  |             public TaskViewHolder(@NonNull View itemView) { | ||||||
|  |                 super(itemView); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * 空提示ViewHolder(绑定“无任务”提示布局 item_task_empty.xml) | ||||||
|  |          */ | ||||||
|  |         public class EmptyViewHolder extends TaskViewHolder { | ||||||
|  |             public EmptyViewHolder(@NonNull View itemView) { | ||||||
|  |                 super(itemView); | ||||||
|  |                 // 根据当前视图模式修改提示文本 | ||||||
|  |                 TextView tvEmptyTip = (TextView) itemView.findViewById(R.id.tv_task_empty_tip); | ||||||
|  |                 if (mCurrentViewMode == VIEW_MODE_EDIT) { | ||||||
|  |                     tvEmptyTip.setText("暂无任务,点击\"添加新任务\"创建"); | ||||||
|  |                 } else { | ||||||
|  |                     tvEmptyTip.setText("暂无启用的任务"); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * 任务内容ViewHolder(绑定任务项布局 item_task_content.xml) | ||||||
|  |          */ | ||||||
|  |         public class TaskContentViewHolder extends TaskViewHolder { | ||||||
|  |             TextView tvTaskDesc;       // 任务描述 | ||||||
|  |             TextView tvTaskDistance;   // 距离条件(如“大于 100 米”) | ||||||
|  |             CompoundButton cbTaskEnable; // 任务启用/禁用开关 | ||||||
|  |             Button btnEditTask;        // 编辑任务按钮 | ||||||
|  |             Button btnDeleteTask;      // 删除任务按钮 | ||||||
|  |  | ||||||
|  |             public TaskContentViewHolder(@NonNull View itemView) { | ||||||
|  |                 super(itemView); | ||||||
|  |                 // 绑定布局控件(与 item_task_content.xml 中的ID严格对应) | ||||||
|  |                 tvTaskDesc = (TextView) itemView.findViewById(R.id.tv_task_desc); | ||||||
|  |                 tvTaskDistance = (TextView) itemView.findViewById(R.id.tv_task_distance); | ||||||
|  |                 cbTaskEnable = (CompoundButton) itemView.findViewById(R.id.cb_task_enable); | ||||||
|  |                 btnEditTask = (Button) itemView.findViewById(R.id.btn_edit_task); | ||||||
|  |                 btnDeleteTask = (Button) itemView.findViewById(R.id.btn_delete_task); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								positions/src/main/res/drawable/bg_task_item.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								positions/src/main/res/drawable/bg_task_item.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <shape xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:shape="rectangle"> | ||||||
|  |  | ||||||
|  |     <solid android:color="@color/white" /> | ||||||
|  |  | ||||||
|  |     <corners android:radius="4dp" /> | ||||||
|  |  | ||||||
|  |     <stroke | ||||||
|  |         android:width="1dp" | ||||||
|  |         android:color="@color/gray" /> | ||||||
|  |  | ||||||
|  |     <padding | ||||||
|  |         android:left="8dp" | ||||||
|  |         android:top="8dp" | ||||||
|  |         android:right="8dp" | ||||||
|  |         android:bottom="8dp" /> | ||||||
|  |  | ||||||
|  | </shape> | ||||||
|  |  | ||||||
							
								
								
									
										26
									
								
								positions/src/main/res/drawable/item_bg_edit.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								positions/src/main/res/drawable/item_bg_edit.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <!-- res/drawable/item_bg_edit.xml --> | ||||||
|  | <selector xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |     <!-- 状态1:按压时(编辑项被点击,反馈明确) --> | ||||||
|  |     <item android:state_pressed="true"> | ||||||
|  |         <shape android:shape="rectangle"> | ||||||
|  |             <solid android:color="#E0E6ED" /> <!-- 按压底色:比默认深一点的灰蓝色,反馈清晰 --> | ||||||
|  |             <corners android:radius="8dp" />  <!-- 保持圆角统一 --> | ||||||
|  |             <stroke | ||||||
|  |                 android:width="1.5dp" | ||||||
|  |                 android:color="#A0B4C8" />    <!-- 按压时边框加深,增强视觉焦点 --> | ||||||
|  |         </shape> | ||||||
|  |     </item> | ||||||
|  |  | ||||||
|  |     <!-- 状态2:默认状态(未按压,区分简单视图) --> | ||||||
|  |     <item> | ||||||
|  |         <shape android:shape="rectangle"> | ||||||
|  |             <solid android:color="#EEEEEE" /> <!-- 默认底色:比简单视图深,明确编辑模式 --> | ||||||
|  |             <corners android:radius="8dp" />  <!-- 与简单视图一致的圆角,UI不割裂 --> | ||||||
|  |             <stroke | ||||||
|  |                 android:width="1.5dp" | ||||||
|  |                 android:color="#D0D8E0" />    <!-- 淡蓝色边框,暗示“可编辑” --> | ||||||
|  |         </shape> | ||||||
|  |     </item> | ||||||
|  | </selector> | ||||||
|  |  | ||||||
							
								
								
									
										26
									
								
								positions/src/main/res/drawable/item_bg_simple.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								positions/src/main/res/drawable/item_bg_simple.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <!-- res/drawable/item_bg_simple.xml --> | ||||||
|  | <selector xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |     <!-- 状态1:按压时的背景(深一点的灰色,明确反馈点击动作) --> | ||||||
|  |     <item android:state_pressed="true"> | ||||||
|  |         <shape android:shape="rectangle"> | ||||||
|  |             <solid android:color="#E8E8E8" /> <!-- 按压底色(比默认深一点) --> | ||||||
|  |             <corners android:radius="8dp" />  <!-- 保持和默认状态一致的圆角 --> | ||||||
|  |             <stroke | ||||||
|  |                 android:width="1dp" | ||||||
|  |                 android:color="#E0E0E0" /> | ||||||
|  |         </shape> | ||||||
|  |     </item> | ||||||
|  |  | ||||||
|  |     <!-- 状态2:默认状态(未按压时的背景,和基础版一致) --> | ||||||
|  |     <item> | ||||||
|  |         <shape android:shape="rectangle"> | ||||||
|  |             <solid android:color="#F5F5F5" /> <!-- 默认浅灰底色 --> | ||||||
|  |             <corners android:radius="8dp" />  <!-- 8dp圆角 --> | ||||||
|  |             <stroke | ||||||
|  |                 android:width="1dp" | ||||||
|  |                 android:color="#E0E0E0" />    <!-- 细灰边框 --> | ||||||
|  |         </shape> | ||||||
|  |     </item> | ||||||
|  | </selector> | ||||||
|  |  | ||||||
							
								
								
									
										82
									
								
								positions/src/main/res/layout/dialog_edit_task.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								positions/src/main/res/layout/dialog_edit_task.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:orientation="vertical" | ||||||
|  |     android:padding="16dp" | ||||||
|  |     android:background="@color/white"> | ||||||
|  |  | ||||||
|  |     <EditText | ||||||
|  |         android:id="@+id/et_edit_task_desc" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:hint="输入任务描述" | ||||||
|  |         android:maxLines="1" | ||||||
|  |         android:textSize="14sp" /> | ||||||
|  |  | ||||||
|  |     <RadioGroup | ||||||
|  |         android:id="@+id/rg_distance_condition" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:orientation="horizontal" | ||||||
|  |         android:layout_marginTop="12dp"> | ||||||
|  |  | ||||||
|  |         <RadioButton | ||||||
|  |             android:id="@+id/rb_greater_than" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:text="大于" | ||||||
|  |             android:textColor="@color/black" | ||||||
|  |             android:textSize="14sp" /> | ||||||
|  |  | ||||||
|  |         <RadioButton | ||||||
|  |             android:id="@+id/rb_less_than" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:text="小于" | ||||||
|  |             android:textColor="@color/black" | ||||||
|  |             android:textSize="14sp" | ||||||
|  |             android:layout_marginLeft="24dp" /> | ||||||
|  |  | ||||||
|  |     </RadioGroup> | ||||||
|  |  | ||||||
|  |     <EditText | ||||||
|  |         android:id="@+id/et_edit_distance" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:hint="输入距离(米)" | ||||||
|  |         android:inputType="number" | ||||||
|  |         android:maxLines="1" | ||||||
|  |         android:textSize="14sp" | ||||||
|  |         android:layout_marginTop="8dp" /> | ||||||
|  |  | ||||||
|  |     <LinearLayout | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:orientation="horizontal" | ||||||
|  |         android:gravity="end" | ||||||
|  |         android:layout_marginTop="16dp"> | ||||||
|  |  | ||||||
|  |         <Button | ||||||
|  |             android:id="@+id/btn_dialog_cancel" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="40dp" | ||||||
|  |             android:text="取消" | ||||||
|  |             android:textColor="@color/black" | ||||||
|  |             android:textSize="14sp" | ||||||
|  |             android:layout_marginRight="8dp" | ||||||
|  |             android:background="@color/gray" /> | ||||||
|  |  | ||||||
|  |         <Button | ||||||
|  |             android:id="@+id/btn_dialog_save" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="40dp" | ||||||
|  |             android:text="保存" | ||||||
|  |             android:textSize="14sp" | ||||||
|  |             android:background="@color/blue" | ||||||
|  |             android:textColor="@color/white" /> | ||||||
|  |  | ||||||
|  |     </LinearLayout> | ||||||
|  |  | ||||||
|  | </LinearLayout> | ||||||
|  |  | ||||||
| @@ -1,152 +1,170 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <!-- 编辑视图:新增实时距离开关(单选框组),位于备注输入框与按钮区之间 --> | <LinearLayout | ||||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | 	xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     android:layout_width="match_parent" | 	android:layout_width="match_parent" | ||||||
|     android:layout_height="wrap_content" | 	android:layout_height="wrap_content" | ||||||
|     android:orientation="vertical" | 	android:orientation="vertical" | ||||||
|     android:padding="12dp" | 	android:padding="12dp" | ||||||
|     android:background="@drawable/item_position_bg" | 	android:background="@drawable/item_bg_edit"> | ||||||
|     android:layout_marginBottom="8dp"> |  | ||||||
|  |  | ||||||
|     <!-- 1. 经纬度显示(不变) --> | 	<TextView | ||||||
|     <LinearLayout | 		android:id="@+id/tv_edit_longitude" | ||||||
|         android:layout_width="wrap_content" | 		android:layout_width="wrap_content" | ||||||
|         android:layout_height="wrap_content" | 		android:layout_height="wrap_content" | ||||||
|         android:orientation="vertical" | 		android:textSize="14sp" | ||||||
|         android:layout_marginBottom="12dp"> | 		android:textColor="#999999"/> | ||||||
|  |  | ||||||
|         <TextView | 	<TextView | ||||||
|             android:id="@+id/tv_edit_longitude" | 		android:id="@+id/tv_edit_latitude" | ||||||
|             android:layout_width="wrap_content" | 		android:layout_width="wrap_content" | ||||||
|             android:layout_height="wrap_content" | 		android:layout_height="wrap_content" | ||||||
|             android:textSize="14sp" | 		android:textSize="14sp" | ||||||
|             android:textColor="#666666"/> | 		android:textColor="#999999" | ||||||
|  | 		android:layout_marginTop="4dp"/> | ||||||
|  |  | ||||||
|         <TextView | 	<EditText | ||||||
|             android:id="@+id/tv_edit_latitude" | 		android:id="@+id/et_edit_memo" | ||||||
|             android:layout_width="wrap_content" | 		android:layout_width="match_parent" | ||||||
|             android:layout_height="wrap_content" | 		android:layout_height="wrap_content" | ||||||
|             android:textSize="14sp" | 		android:hint="请输入位置备注" | ||||||
|             android:textColor="#666666" | 		android:textSize="14sp" | ||||||
|             android:layout_marginTop="5dp"/> | 		android:padding="8dp" | ||||||
|  | 		android:layout_marginTop="8dp" | ||||||
|  | 		android:background="@drawable/edittext_bg"/> | ||||||
|  |  | ||||||
|     </LinearLayout> | 	<LinearLayout | ||||||
|  | 		android:layout_width="wrap_content" | ||||||
|  | 		android:layout_height="wrap_content" | ||||||
|  | 		android:orientation="vertical" | ||||||
|  | 		android:layout_marginTop="10dp"> | ||||||
|  |  | ||||||
|     <!-- 2. 备注输入框(不变,调整下方间距为8dp,与新增单选框区过渡更自然) --> | 		<TextView | ||||||
|     <EditText | 			android:layout_width="wrap_content" | ||||||
|         android:id="@+id/et_edit_memo" | 			android:layout_height="wrap_content" | ||||||
|         android:layout_width="match_parent" | 			android:textSize="14sp" | ||||||
|         android:layout_height="wrap_content" | 			android:textColor="#333333" | ||||||
|         android:hint="请输入位置备注(如:公司/家/学校)" | 			android:id="@+id/tv_edit_real_distance"/> | ||||||
|         android:textSize="16sp" |  | ||||||
|         android:inputType="textMultiLine" |  | ||||||
|         android:minLines="2" |  | ||||||
|         android:maxLines="4" |  | ||||||
|         android:padding="10dp" |  | ||||||
|         android:background="@drawable/edittext_bg" |  | ||||||
|         android:layout_marginBottom="8dp"/> <!-- 原12dp→8dp,优化与单选框的间距 --> |  | ||||||
|  |  | ||||||
|     <!-- 新增3:实时距离开关(单选框组)- 控制isEnableRealPositionDistance属性 --> | 		<RadioGroup | ||||||
|     <LinearLayout | 			android:id="@+id/rg_real_distance_switch" | ||||||
|         android:layout_width="wrap_content" | 			android:layout_width="wrap_content" | ||||||
|         android:layout_height="wrap_content" | 			android:layout_height="wrap_content" | ||||||
|         android:orientation="horizontal" | 			android:orientation="horizontal" | ||||||
|         android:gravity="center_vertical" | 			android:layout_marginLeft="8dp"> | ||||||
|         android:layout_marginBottom="12dp"> <!-- 与下方按钮区保持12dp间距,符合原有布局规范 --> |  | ||||||
|  |  | ||||||
|         <!-- 单选框标题(说明用途,文字颜色与经纬度一致,保持风格统一) --> | 			<RadioButton | ||||||
|         <TextView | 				android:id="@+id/rb_disable" | ||||||
|             android:layout_width="wrap_content" | 				android:layout_width="wrap_content" | ||||||
|             android:layout_height="wrap_content" | 				android:layout_height="wrap_content" | ||||||
|             android:text="实时距离计算:" | 				android:text="禁用" | ||||||
|             android:textSize="14sp" | 				android:textSize="14sp" | ||||||
|             android:textColor="#666666" | 				android:checked="true"/> | ||||||
|             android:layout_marginRight="12dp"/> <!-- 与单选框保持12dp间距,避免拥挤 --> |  | ||||||
|  |  | ||||||
|         <!-- 单选框组(RadioGroup):确保“启用/禁用”二选一 --> | 			<RadioButton | ||||||
|         <RadioGroup | 				android:id="@+id/rb_enable" | ||||||
|             android:id="@+id/rg_real_distance_switch" | 				android:layout_width="wrap_content" | ||||||
|             android:layout_width="wrap_content" | 				android:layout_height="wrap_content" | ||||||
|             android:layout_height="wrap_content" | 				android:text="启用" | ||||||
|             android:orientation="horizontal" | 				android:textSize="14sp" | ||||||
|             android:checkedButton="@+id/rb_disable"> <!-- 默认选中“禁用”,匹配isEnable默认值false --> | 				android:layout_marginLeft="15dp"/> | ||||||
|  |  | ||||||
|             <!-- 单选框1:禁用实时距离(对应isEnableRealPositionDistance=false) --> | 		</RadioGroup> | ||||||
|             <RadioButton |  | ||||||
|                 android:id="@+id/rb_disable" |  | ||||||
|                 android:layout_width="wrap_content" |  | ||||||
|                 android:layout_height="wrap_content" |  | ||||||
|                 android:text="禁用" |  | ||||||
|                 android:textSize="13sp" |  | ||||||
|                 android:textColor="#333333" |  | ||||||
|                 android:layout_marginRight="16dp"/> <!-- 与“启用”单选框保持16dp间距 --> |  | ||||||
|  |  | ||||||
|             <!-- 单选框2:启用实时距离(对应isEnableRealPositionDistance=true) --> | 	</LinearLayout> | ||||||
|             <RadioButton |  | ||||||
|                 android:id="@+id/rb_enable" |  | ||||||
|                 android:layout_width="wrap_content" |  | ||||||
|                 android:layout_height="wrap_content" |  | ||||||
|                 android:text="启用" |  | ||||||
|                 android:textSize="13sp" |  | ||||||
|                 android:textColor="#333333"/> |  | ||||||
|  |  | ||||||
|         </RadioGroup> | 	<LinearLayout | ||||||
|     </LinearLayout> | 		android:layout_width="wrap_content" | ||||||
|  | 		android:layout_height="wrap_content" | ||||||
|  | 		android:orientation="horizontal" | ||||||
|  | 		android:layout_marginTop="12dp"> | ||||||
|  |  | ||||||
|     <!-- 4. 按钮区(不变:顺序→删除(左)→取消→确定(右)) --> | 		<Button | ||||||
|     <LinearLayout | 			android:id="@+id/btn_edit_delete" | ||||||
|         android:layout_width="wrap_content" | 			android:layout_width="wrap_content" | ||||||
|         android:layout_height="wrap_content" | 			android:layout_height="32dp" | ||||||
|         android:orientation="horizontal" | 			android:background="@drawable/btn_delete_bg" | ||||||
|         android:gravity="center_vertical"> | 			android:text="删除位置" | ||||||
|  | 			android:textColor="@android:color/white" | ||||||
|  | 			android:textSize="13sp" | ||||||
|  | 			android:paddingStart="15dp" | ||||||
|  | 			android:paddingEnd="15dp"/> | ||||||
|  |  | ||||||
|         <!-- ① 删除按钮(最左侧,红色) --> | 		<View | ||||||
|         <Button | 			android:layout_width="10dp" | ||||||
|             android:id="@+id/btn_edit_delete" | 			android:layout_height="match_parent"/> | ||||||
|             android:layout_width="wrap_content" |  | ||||||
|             android:layout_height="32dp" |  | ||||||
|             android:background="@drawable/btn_delete_bg" |  | ||||||
|             android:text="删除" |  | ||||||
|             android:textColor="@android:color/white" |  | ||||||
|             android:textSize="13sp" |  | ||||||
|             android:paddingStart="15dp" |  | ||||||
|             android:paddingEnd="15dp"/> |  | ||||||
|  |  | ||||||
|         <!-- 间隔(按钮间间距) --> | 		<Button | ||||||
|         <View | 			android:id="@+id/btn_edit_cancel" | ||||||
|             android:layout_width="10dp" | 			android:layout_width="wrap_content" | ||||||
|             android:layout_height="match_parent"/> | 			android:layout_height="32dp" | ||||||
|  | 			android:background="@drawable/btn_cancel_bg" | ||||||
|  | 			android:text="取消" | ||||||
|  | 			android:textColor="@android:color/white" | ||||||
|  | 			android:textSize="13sp" | ||||||
|  | 			android:paddingStart="15dp" | ||||||
|  | 			android:paddingEnd="15dp"/> | ||||||
|  |  | ||||||
|         <!-- ② 取消按钮(中间,灰色,不保存返回简单视图) --> | 		<View | ||||||
|         <Button | 			android:layout_width="10dp" | ||||||
|             android:id="@+id/btn_edit_cancel" | 			android:layout_height="match_parent"/> | ||||||
|             android:layout_width="wrap_content" |  | ||||||
|             android:layout_height="32dp" |  | ||||||
|             android:background="@drawable/btn_cancel_bg" |  | ||||||
|             android:text="取消" |  | ||||||
|             android:textColor="@android:color/white" |  | ||||||
|             android:textSize="13sp" |  | ||||||
|             android:paddingStart="15dp" |  | ||||||
|             android:paddingEnd="15dp"/> |  | ||||||
|  |  | ||||||
|         <!-- 间隔 --> | 		<Button | ||||||
|         <View | 			android:id="@+id/btn_edit_confirm" | ||||||
|             android:layout_width="10dp" | 			android:layout_width="wrap_content" | ||||||
|             android:layout_height="match_parent"/> | 			android:layout_height="32dp" | ||||||
|  | 			android:background="@drawable/btn_confirm_bg" | ||||||
|  | 			android:text="确定" | ||||||
|  | 			android:textColor="@android:color/white" | ||||||
|  | 			android:textSize="13sp" | ||||||
|  | 			android:paddingStart="15dp" | ||||||
|  | 			android:paddingEnd="15dp"/> | ||||||
|  |  | ||||||
|         <!-- ③ 确定按钮(最右侧,蓝色,保存并返回简单视图) --> | 	</LinearLayout> | ||||||
|         <Button |  | ||||||
|             android:id="@+id/btn_edit_confirm" |  | ||||||
|             android:layout_width="wrap_content" |  | ||||||
|             android:layout_height="32dp" |  | ||||||
|             android:background="@drawable/btn_confirm_bg" |  | ||||||
|             android:text="确定" |  | ||||||
|             android:textColor="@android:color/white" |  | ||||||
|             android:textSize="13sp" |  | ||||||
|             android:paddingStart="15dp" |  | ||||||
|             android:paddingEnd="15dp"/> |  | ||||||
|  |  | ||||||
|     </LinearLayout> | 	<LinearLayout | ||||||
|  | 		android:layout_width="match_parent" | ||||||
|  | 		android:layout_height="wrap_content" | ||||||
|  | 		android:orientation="vertical" | ||||||
|  | 		android:layout_marginTop="16dp"> | ||||||
|  |  | ||||||
|  | 		<LinearLayout | ||||||
|  | 			android:layout_width="match_parent" | ||||||
|  | 			android:layout_height="30dp" | ||||||
|  | 			android:orientation="horizontal"> | ||||||
|  |  | ||||||
|  | 			<TextView | ||||||
|  | 				android:layout_width="wrap_content" | ||||||
|  | 				android:layout_height="match_parent" | ||||||
|  | 				android:gravity="center_vertical" | ||||||
|  | 				android:text="关联任务(可编辑):" | ||||||
|  | 				android:textSize="14sp" | ||||||
|  | 				android:textColor="#333333"/> | ||||||
|  |  | ||||||
|  | 			<View | ||||||
|  | 				android:layout_width="0dp" | ||||||
|  | 				android:layout_height="match_parent" | ||||||
|  | 				android:layout_weight="1"/> | ||||||
|  |  | ||||||
|  | 			<Button | ||||||
|  | 				android:id="@+id/btn_add_task" | ||||||
|  | 				android:layout_width="wrap_content" | ||||||
|  | 				android:layout_height="match_parent" | ||||||
|  | 				android:background="@drawable/btn_confirm_bg" | ||||||
|  | 				android:text="添加新任务" | ||||||
|  | 				android:textColor="@android:color/white" | ||||||
|  | 				android:textSize="12sp" | ||||||
|  | 				android:paddingStart="12dp" | ||||||
|  | 				android:paddingEnd="12dp"/> | ||||||
|  |  | ||||||
|  | 		</LinearLayout> | ||||||
|  |  | ||||||
|  | 		<cc.winboll.studio.positions.views.PositionTaskListView | ||||||
|  | 			android:id="@+id/ptlv_edit_tasks" | ||||||
|  | 			android:layout_width="match_parent" | ||||||
|  | 			android:layout_height="400dp" | ||||||
|  | 			android:layout_marginTop="6dp"/> | ||||||
|  |  | ||||||
|  | 	</LinearLayout> | ||||||
|  |  | ||||||
| </LinearLayout> | </LinearLayout> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,49 +1,55 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <!-- 简单视图:新增实时距离显示控件(备注下方) --> |  | ||||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     android:layout_width="match_parent" |     android:layout_width="match_parent" | ||||||
|     android:layout_height="wrap_content" |     android:layout_height="wrap_content" | ||||||
|     android:orientation="vertical" |     android:orientation="vertical" | ||||||
|     android:padding="12dp" |     android:padding="12dp" | ||||||
|     android:background="@drawable/item_position_bg" |     android:background="@drawable/item_bg_simple"> | ||||||
|     android:layout_marginBottom="8dp"> |  | ||||||
|  |  | ||||||
|     <!-- 经度 --> |  | ||||||
|     <TextView |     <TextView | ||||||
|         android:id="@+id/tv_simple_longitude" |         android:id="@+id/tv_simple_longitude" | ||||||
|         android:layout_width="wrap_content" |         android:layout_width="wrap_content" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:textSize="14sp" |         android:textSize="14sp" | ||||||
|         android:textColor="#666666"/> |         android:textColor="#333333"/> | ||||||
|  |  | ||||||
|     <!-- 纬度 --> |  | ||||||
|     <TextView |     <TextView | ||||||
|         android:id="@+id/tv_simple_latitude" |         android:id="@+id/tv_simple_latitude" | ||||||
|         android:layout_width="wrap_content" |         android:layout_width="wrap_content" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:textSize="14sp" |         android:textSize="14sp" | ||||||
|         android:textColor="#666666" |         android:textColor="#333333" | ||||||
|         android:layout_marginTop="5dp"/> |         android:layout_marginTop="4dp"/> | ||||||
|  |  | ||||||
|     <!-- 备注(加粗突出) --> |  | ||||||
|     <TextView |     <TextView | ||||||
|         android:id="@+id/tv_simple_memo" |         android:id="@+id/tv_simple_memo" | ||||||
|         android:layout_width="wrap_content" |         android:layout_width="wrap_content" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:textSize="16sp" |         android:textSize="14sp" | ||||||
|         android:textColor="#000000" |         android:textColor="#666666" | ||||||
|         android:textStyle="bold" |         android:layout_marginTop="4dp"/> | ||||||
|         android:layout_marginTop="8dp"/> |  | ||||||
|  |  | ||||||
|     <!-- 新增:实时距离显示(距离计算结果/未启用提示) --> |  | ||||||
|     <TextView |     <TextView | ||||||
|         android:id="@+id/tv_simple_real_distance" |         android:id="@+id/tv_simple_real_distance" | ||||||
|         android:layout_width="wrap_content" |         android:layout_width="wrap_content" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:textSize="14sp" |         android:textSize="14sp" | ||||||
|         android:textColor="#2196F3" |         android:textColor="#2E8B57" | ||||||
|         android:layout_marginTop="6dp" |         android:layout_marginTop="6dp"/> | ||||||
|         android:text="实时距离未启用"/>  <!-- 默认提示(isEnable=false时显示) --> |  | ||||||
|  |     <TextView | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:text="关联任务(已启用):" | ||||||
|  |         android:textSize="14sp" | ||||||
|  |         android:textColor="#333333" | ||||||
|  |         android:layout_marginTop="8dp" | ||||||
|  |         android:layout_marginBottom="4dp"/> | ||||||
|  |  | ||||||
|  |     <cc.winboll.studio.positions.views.PositionTaskListView | ||||||
|  |         android:id="@+id/ptlv_simple_tasks" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="200dp" | ||||||
|  |         android:layout_marginTop="4dp"/> | ||||||
|  |  | ||||||
| </LinearLayout> | </LinearLayout> | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										184
									
								
								positions/src/main/res/layout/item_position_task_edit.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								positions/src/main/res/layout/item_position_task_edit.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,184 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:orientation="vertical" | ||||||
|  |     android:padding="12dp" | ||||||
|  |     android:background="@drawable/item_bg_edit" | ||||||
|  |     android:layout_marginBottom="8dp"> | ||||||
|  |  | ||||||
|  |     <!-- 1. 不可编辑字段(仅显示,灰色文字) --> | ||||||
|  |     <LinearLayout | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:orientation="vertical" | ||||||
|  |         android:layout_marginBottom="10dp"> | ||||||
|  |  | ||||||
|  |         <TextView | ||||||
|  |             android:id="@+id/tv_edit_task_id" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:textSize="12sp" | ||||||
|  |             android:textColor="#999999" | ||||||
|  |             android:text="任务ID:"/> | ||||||
|  |  | ||||||
|  |         <TextView | ||||||
|  |             android:id="@+id/tv_edit_position_id" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:textSize="12sp" | ||||||
|  |             android:textColor="#999999" | ||||||
|  |             android:layout_marginTop="2dp" | ||||||
|  |             android:text="位置ID:"/> | ||||||
|  |     </LinearLayout> | ||||||
|  |  | ||||||
|  |     <!-- 2. 任务描述(可编辑) --> | ||||||
|  |     <EditText | ||||||
|  |         android:id="@+id/et_edit_task_desc" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:hint="请输入任务描述(如:到达后提醒打卡)" | ||||||
|  |         android:textSize="14sp" | ||||||
|  |         android:padding="8dp" | ||||||
|  |         android:background="@drawable/edittext_bg" | ||||||
|  |         android:layout_marginBottom="10dp"/> | ||||||
|  |  | ||||||
|  |     <!-- 3. 距离条件(单选组:大于/小于) --> | ||||||
|  |     <LinearLayout | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:orientation="horizontal" | ||||||
|  |         android:layout_marginBottom="10dp"> | ||||||
|  |  | ||||||
|  |         <TextView | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:text="距离条件:" | ||||||
|  |             android:textSize="14sp" | ||||||
|  |             android:textColor="#333333" | ||||||
|  |             android:layout_marginRight="10dp"/> | ||||||
|  |  | ||||||
|  |         <RadioGroup | ||||||
|  |             android:id="@+id/rg_edit_distance_cond" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:orientation="horizontal"> | ||||||
|  |  | ||||||
|  |             <RadioButton | ||||||
|  |                 android:id="@+id/rb_edit_greater" | ||||||
|  |                 android:layout_width="wrap_content" | ||||||
|  |                 android:layout_height="wrap_content" | ||||||
|  |                 android:text="大于" | ||||||
|  |                 android:textSize="14sp" | ||||||
|  |                 android:layout_marginRight="15dp"/> | ||||||
|  |  | ||||||
|  |             <RadioButton | ||||||
|  |                 android:id="@+id/rb_edit_less" | ||||||
|  |                 android:layout_width="wrap_content" | ||||||
|  |                 android:layout_height="wrap_content" | ||||||
|  |                 android:text="小于" | ||||||
|  |                 android:textSize="14sp"/> | ||||||
|  |         </RadioGroup> | ||||||
|  |     </LinearLayout> | ||||||
|  |  | ||||||
|  |     <!-- 4. 商议距离(可编辑,仅整数) --> | ||||||
|  |     <EditText | ||||||
|  |         android:id="@+id/et_edit_discuss_dist" | ||||||
|  |         android:layout_width="120dp" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:hint="请输入距离(米)" | ||||||
|  |         android:textSize="14sp" | ||||||
|  |         android:inputType="number" | ||||||
|  |         android:padding="8dp" | ||||||
|  |         android:background="@drawable/edittext_bg" | ||||||
|  |         android:layout_marginBottom="10dp"/> | ||||||
|  |  | ||||||
|  |     <!-- 5. 启用状态(单选组:是/否) --> | ||||||
|  |     <LinearLayout | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:orientation="horizontal" | ||||||
|  |         android:layout_marginBottom="12dp"> | ||||||
|  |  | ||||||
|  |         <TextView | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:text="启用任务:" | ||||||
|  |             android:textSize="14sp" | ||||||
|  |             android:textColor="#333333" | ||||||
|  |             android:layout_marginRight="10dp"/> | ||||||
|  |  | ||||||
|  |         <RadioGroup | ||||||
|  |             android:id="@+id/rg_edit_is_enable" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:orientation="horizontal"> | ||||||
|  |  | ||||||
|  |             <RadioButton | ||||||
|  |                 android:id="@+id/rb_edit_enable_yes" | ||||||
|  |                 android:layout_width="wrap_content" | ||||||
|  |                 android:layout_height="wrap_content" | ||||||
|  |                 android:text="是" | ||||||
|  |                 android:textSize="14sp" | ||||||
|  |                 android:layout_marginRight="15dp"/> | ||||||
|  |  | ||||||
|  |             <RadioButton | ||||||
|  |                 android:id="@+id/rb_edit_enable_no" | ||||||
|  |                 android:layout_width="wrap_content" | ||||||
|  |                 android:layout_height="wrap_content" | ||||||
|  |                 android:text="否" | ||||||
|  |                 android:textSize="14sp"/> | ||||||
|  |         </RadioGroup> | ||||||
|  |     </LinearLayout> | ||||||
|  |  | ||||||
|  |     <!-- 6. 功能按钮(删除-红 / 取消-灰 / 确定-蓝) --> | ||||||
|  |     <LinearLayout | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:orientation="horizontal" | ||||||
|  |         android:gravity="center_vertical"> | ||||||
|  |  | ||||||
|  |         <Button | ||||||
|  |             android:id="@+id/btn_edit_delete" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="32dp" | ||||||
|  |             android:background="@drawable/btn_delete_bg" | ||||||
|  |             android:text="删除" | ||||||
|  |             android:textColor="@android:color/white" | ||||||
|  |             android:textSize="13sp" | ||||||
|  |             android:paddingStart="15dp" | ||||||
|  |             android:paddingEnd="15dp"/> | ||||||
|  |  | ||||||
|  |         <View | ||||||
|  |             android:layout_width="10dp" | ||||||
|  |             android:layout_height="match_parent"/> | ||||||
|  |  | ||||||
|  |         <Button | ||||||
|  |             android:id="@+id/btn_edit_cancel" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="32dp" | ||||||
|  |             android:background="@drawable/btn_cancel_bg" | ||||||
|  |             android:text="取消" | ||||||
|  |             android:textColor="@android:color/white" | ||||||
|  |             android:textSize="13sp" | ||||||
|  |             android:paddingStart="15dp" | ||||||
|  |             android:paddingEnd="15dp"/> | ||||||
|  |  | ||||||
|  |         <View | ||||||
|  |             android:layout_width="10dp" | ||||||
|  |             android:layout_height="match_parent"/> | ||||||
|  |  | ||||||
|  |         <Button | ||||||
|  |             android:id="@+id/btn_edit_confirm" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="32dp" | ||||||
|  |             android:background="@drawable/btn_confirm_bg" | ||||||
|  |             android:text="确定" | ||||||
|  |             android:textColor="@android:color/white" | ||||||
|  |             android:textSize="13sp" | ||||||
|  |             android:paddingStart="15dp" | ||||||
|  |             android:paddingEnd="15dp"/> | ||||||
|  |     </LinearLayout> | ||||||
|  |  | ||||||
|  | </LinearLayout> | ||||||
|  |  | ||||||
							
								
								
									
										40
									
								
								positions/src/main/res/layout/item_position_task_simple.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								positions/src/main/res/layout/item_position_task_simple.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:orientation="vertical" | ||||||
|  |     android:padding="12dp" | ||||||
|  |     android:background="@drawable/item_bg_simple" | ||||||
|  |     android:layout_marginBottom="8dp"> | ||||||
|  |  | ||||||
|  |     <!-- 任务描述 --> | ||||||
|  |     <TextView | ||||||
|  |         android:id="@+id/tv_simple_task_desc" | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:textSize="16sp" | ||||||
|  |         android:textColor="#333333" | ||||||
|  |         android:text="任务:无描述"/> | ||||||
|  |  | ||||||
|  |     <!-- 距离条件 --> | ||||||
|  |     <TextView | ||||||
|  |         android:id="@+id/tv_simple_distance_cond" | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:textSize="14sp" | ||||||
|  |         android:textColor="#666666" | ||||||
|  |         android:layout_marginTop="6dp" | ||||||
|  |         android:text="条件:距离 > 0 米"/> | ||||||
|  |  | ||||||
|  |     <!-- 启用状态(简单视图仅显示“已启用”) --> | ||||||
|  |     <TextView | ||||||
|  |         android:id="@+id/tv_simple_is_enable" | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:textSize="14sp" | ||||||
|  |         android:textColor="#2E8B57" | ||||||
|  |         android:layout_marginTop="4dp" | ||||||
|  |         android:text="状态:已启用"/> | ||||||
|  |  | ||||||
|  | </LinearLayout> | ||||||
|  |  | ||||||
							
								
								
									
										70
									
								
								positions/src/main/res/layout/item_task_content.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								positions/src/main/res/layout/item_task_content.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:orientation="horizontal" | ||||||
|  |     android:gravity="center_vertical" | ||||||
|  |     android:padding="8dp" | ||||||
|  |     android:background="@drawable/bg_task_item" | ||||||
|  |     android:layout_marginVertical="4dp"> | ||||||
|  |  | ||||||
|  |     <CheckBox | ||||||
|  |         android:id="@+id/cb_task_enable" | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_marginRight="12dp" /> | ||||||
|  |  | ||||||
|  |     <LinearLayout | ||||||
|  |         android:layout_width="0dp" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_weight="1" | ||||||
|  |         android:orientation="vertical"> | ||||||
|  |  | ||||||
|  |         <TextView | ||||||
|  |             android:id="@+id/tv_task_desc" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:text="任务描述" | ||||||
|  |             android:textSize="16sp" | ||||||
|  |             android:textColor="@color/black" /> | ||||||
|  |  | ||||||
|  |         <TextView | ||||||
|  |             android:id="@+id/tv_task_distance" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:text="条件:大于 100 米" | ||||||
|  |             android:textSize="12sp" | ||||||
|  |             android:textColor="@color/gray_dark" | ||||||
|  |             android:layout_marginTop="2dp" /> | ||||||
|  |  | ||||||
|  |     </LinearLayout> | ||||||
|  |  | ||||||
|  |     <LinearLayout | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:orientation="horizontal" | ||||||
|  |         android:layout_marginLeft="8dp"> | ||||||
|  |  | ||||||
|  |         <Button | ||||||
|  |             android:id="@+id/btn_edit_task" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="30dp" | ||||||
|  |             android:text="修改" | ||||||
|  |             android:textSize="12sp" | ||||||
|  |             android:layout_marginRight="4dp" | ||||||
|  |             android:background="@color/blue" | ||||||
|  |             android:textColor="@color/white" /> | ||||||
|  |  | ||||||
|  |         <Button | ||||||
|  |             android:id="@+id/btn_delete_task" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="30dp" | ||||||
|  |             android:text="删除" | ||||||
|  |             android:textSize="12sp" | ||||||
|  |             android:background="@color/red" | ||||||
|  |             android:textColor="@color/white" /> | ||||||
|  |  | ||||||
|  |     </LinearLayout> | ||||||
|  |  | ||||||
|  | </LinearLayout> | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								positions/src/main/res/layout/item_task_empty.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								positions/src/main/res/layout/item_task_empty.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="80dp" | ||||||
|  |     android:gravity="center" | ||||||
|  |     android:orientation="vertical" | ||||||
|  |     android:background="@color/background_light"> | ||||||
|  |  | ||||||
|  |     <TextView | ||||||
|  |         android:id="@+id/tv_task_empty_tip" | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:text="暂无任务" | ||||||
|  |         android:textColor="@color/gray" | ||||||
|  |         android:textSize="14sp" /> | ||||||
|  |  | ||||||
|  | </LinearLayout> | ||||||
|  |  | ||||||
							
								
								
									
										16
									
								
								positions/src/main/res/layout/view_position_task_list.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								positions/src/main/res/layout/view_position_task_list.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:orientation="vertical" | ||||||
|  |     android:padding="8dp"> | ||||||
|  |  | ||||||
|  |     <androidx.recyclerview.widget.RecyclerView | ||||||
|  |         android:id="@+id/rv_position_tasks" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:background="@color/white" | ||||||
|  |         android:paddingVertical="4dp" /> | ||||||
|  |  | ||||||
|  | </LinearLayout> | ||||||
|  |  | ||||||
| @@ -3,4 +3,31 @@ | |||||||
|     <color name="colorPrimary">#009688</color> |     <color name="colorPrimary">#009688</color> | ||||||
|     <color name="colorPrimaryDark">#00796B</color> |     <color name="colorPrimaryDark">#00796B</color> | ||||||
|     <color name="colorAccent">#FF9800</color> |     <color name="colorAccent">#FF9800</color> | ||||||
| </resources> | 	<!-- 1. colorEnableGreen:简单视图“已启用”状态文字颜色(柔和绿色,不刺眼) --> | ||||||
|  |     <color name="colorEnableGreen">#2E8B57</color> <!-- 海绿色:比纯绿柔和,适合文字提示 --> | ||||||
|  |  | ||||||
|  |     <!-- 2. colorGrayText:编辑视图“不可编辑字段”文字颜色(浅灰色,暗示“不可修改”) --> | ||||||
|  |     <color name="colorGrayText">#999999</color> <!-- 中浅灰:既不模糊,又能和可编辑文字区分开 --> | ||||||
|  |  | ||||||
|  |     <!-- (可选补充)适配之前的背景资源颜色(避免后续新增资源报错,提前定义) --> | ||||||
|  |     <color name="colorItemSimpleBg">#F5F5F5</color> <!-- item_bg_simple 底色 --> | ||||||
|  |     <color name="colorItemEditBg">#EEEEEE</color>   <!-- item_bg_edit 底色 --> | ||||||
|  |     <color name="colorItemBorder">#E0E0E0</color>   <!-- 列表项边框颜色 --> | ||||||
|  | 	<color name="colorRed">#F44336</color> <!-- 标准 Material 红色,可自定义RGB值 --> | ||||||
|  |  | ||||||
|  | 	<!-- 基础颜色:解决@color/gray未定义问题 --> | ||||||
|  | 	<color name="gray">#9E9E9E</color> <!-- 浅灰色(用于提示文本、次要信息) --> | ||||||
|  | 	<color name="gray_dark">#616161</color> <!-- 深灰色(备用,避免后续扩展缺失) --> | ||||||
|  |  | ||||||
|  | 	<!-- 其他已用颜色:确保布局中所有颜色引用都有定义 --> | ||||||
|  | 	<color name="white">#FFFFFF</color> <!-- 白色(任务列表背景、弹窗背景) --> | ||||||
|  | 	<color name="black">#000000</color> <!-- 黑色(任务描述文本) --> | ||||||
|  | 	<color name="blue">#2196F3</color> <!-- 蓝色(编辑/保存按钮背景) --> | ||||||
|  | 	<color name="red">#F44336</color> <!-- 红色(删除按钮背景) --> | ||||||
|  | 	<color name="background_light">#F5F5F5</color> <!-- 浅灰背景(备用,提升页面质感) --> | ||||||
|  |  | ||||||
|  | 	<!-- 扩展颜色(避免后续新增功能时再次缺失) --> | ||||||
|  | 	<color name="green">#4CAF50</color> <!-- 绿色(备用:如“启用”状态标识) --> | ||||||
|  | 	<color name="yellow">#FFC107</color> <!-- 黄色(备用:如“提醒”标识) --> | ||||||
|  |  | ||||||
|  | </resources> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 ZhanGSKen
					ZhanGSKen