Compare commits
	
		
			12 Commits
		
	
	
		
			positions-
			...
			appbase
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 269e7b30be | ||
|   | ca953adec2 | ||
|   | 916e807f1b | ||
|   | 1f57ac5401 | ||
|   | 1ba19d8f77 | ||
|   | 435f7ced79 | ||
|   | bd3270f8dd | ||
|   | 5489977a1a | ||
|   | 0ede961391 | ||
| e9bb789daa | |||
| dbff19e7f4 | |||
|   | 44679d0c8a | 
| @@ -1,8 +1,8 @@ | |||||||
| #Created by .winboll/winboll_app_build.gradle | #Created by .winboll/winboll_app_build.gradle | ||||||
| #Fri Sep 26 05:36:14 HKT 2025 | #Sat Sep 27 21:03:20 HKT 2025 | ||||||
| stageCount=9 | stageCount=10 | ||||||
| libraryProject=libappbase | libraryProject=libappbase | ||||||
| baseVersion=15.10 | baseVersion=15.10 | ||||||
| publishVersion=15.10.8 | publishVersion=15.10.9 | ||||||
| buildCount=0 | buildCount=0 | ||||||
| baseBetaVersion=15.10.9 | baseBetaVersion=15.10.10 | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| #Created by .winboll/winboll_app_build.gradle | #Created by .winboll/winboll_app_build.gradle | ||||||
| #Fri Sep 26 05:36:14 HKT 2025 | #Sat Sep 27 21:03:08 HKT 2025 | ||||||
| stageCount=9 | stageCount=10 | ||||||
| libraryProject=libappbase | libraryProject=libappbase | ||||||
| baseVersion=15.10 | baseVersion=15.10 | ||||||
| publishVersion=15.10.8 | publishVersion=15.10.9 | ||||||
| buildCount=0 | buildCount=0 | ||||||
| baseBetaVersion=15.10.9 | baseBetaVersion=15.10.10 | ||||||
|   | |||||||
| @@ -22,12 +22,12 @@ public class GlobalApplication extends Application { | |||||||
|         GlobalApplication.isDebuging = isDebuging; |         GlobalApplication.isDebuging = isDebuging; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     public static void saveDebugStatus(GlobalApplication application) { |     public static void saveDebugStatus(Context context) { | ||||||
|         APPModel.saveBeanToFile(application.getAPPModelFilePath(application), new APPModel(GlobalApplication.isDebuging)); |         APPModel.saveBeanToFile(getAPPModelFilePath(context), new APPModel(GlobalApplication.isDebuging)); | ||||||
|     } |     } | ||||||
| 	 | 	 | ||||||
|     static String getAPPModelFilePath(GlobalApplication application) { |     static String getAPPModelFilePath(Context context) { | ||||||
|         return application.getDataDir().getPath() + "/APPModel.json"; |         return context.getDataDir().getPath() + "/APPModel.json"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static boolean isDebuging() { |     public static boolean isDebuging() { | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| #Created by .winboll/winboll_app_build.gradle | #Created by .winboll/winboll_app_build.gradle | ||||||
| #Thu Oct 02 02:27:55 HKT 2025 | #Thu Oct 02 13:16:14 GMT 2025 | ||||||
| stageCount=8 | stageCount=8 | ||||||
| libraryProject= | libraryProject= | ||||||
| baseVersion=15.0 | baseVersion=15.0 | ||||||
| publishVersion=15.0.7 | publishVersion=15.0.7 | ||||||
| buildCount=0 | buildCount=29 | ||||||
| baseBetaVersion=15.0.8 | baseBetaVersion=15.0.8 | ||||||
|   | |||||||
| @@ -114,7 +114,7 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity { | |||||||
|         setSupportActionBar(mToolbar); |         setSupportActionBar(mToolbar); | ||||||
|         // 给ActionBar设置标题(先判断非空,避免空指针异常) |         // 给ActionBar设置标题(先判断非空,避免空指针异常) | ||||||
|         if (getSupportActionBar() != null) { |         if (getSupportActionBar() != null) { | ||||||
|             getSupportActionBar().setTitle("位置管理"); |             getSupportActionBar().setTitle(getString(R.string.app_name)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,372 +3,225 @@ package cc.winboll.studio.positions.activities; | |||||||
| /** | /** | ||||||
|  * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> |  * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> | ||||||
|  * @Date 2025/09/29 18:22 |  * @Date 2025/09/29 18:22 | ||||||
|  * @Describe 位置列表页面(直连服务数据+移除绑定,兼容服务私有字段) |  * @Describe 位置列表页面(适配MainService GPS接口+规范服务交互+完善生命周期) | ||||||
|  */ |  */ | ||||||
| import android.app.Activity; |  | ||||||
| import android.content.Context; |  | ||||||
| import android.content.SharedPreferences; |  | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.view.View; | import android.os.IBinder; | ||||||
| import android.view.inputmethod.InputMethodManager; | import android.app.Activity; | ||||||
|  | import android.content.ComponentName; | ||||||
|  | import android.content.Intent; | ||||||
|  | import android.content.ServiceConnection; | ||||||
| import android.widget.Toast; | import android.widget.Toast; | ||||||
| import androidx.recyclerview.widget.LinearLayoutManager; | import androidx.recyclerview.widget.LinearLayoutManager; | ||||||
| import androidx.recyclerview.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; |  | ||||||
| import cc.winboll.studio.libappbase.LogUtils; | import cc.winboll.studio.libappbase.LogUtils; | ||||||
| import cc.winboll.studio.positions.R; |  | ||||||
| import cc.winboll.studio.positions.adapters.PositionAdapter; | import cc.winboll.studio.positions.adapters.PositionAdapter; | ||||||
| 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.models.PositionTaskModel; | ||||||
| import cc.winboll.studio.positions.services.MainService; | import cc.winboll.studio.positions.services.MainService; | ||||||
|  | import cc.winboll.studio.positions.R; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 核心调整说明: |  * Java 7 语法适配: | ||||||
|  * 1. 移除服务绑定逻辑,但不直接访问服务私有字段(解决mPositionList未知问题) |  * 1. 服务绑定用匿名内部类实现 ServiceConnection | ||||||
|  * 2. 通过服务提供的 PUBLIC 方法(如getPositionList、addPosition)操作数据,符合封装规范 |  * 2. Adapter 初始化传入 MainService 实例,确保数据来源唯一 | ||||||
|  * 3. 保留“直连服务实例”的核心需求,避免绑定,但尊重服务数据私有性 |  * 3. 所有位置/任务操作通过 MainService 接口执行 | ||||||
|  * 4. 兼容Java 7语法,无Lambda、显式类型转换 |  | ||||||
|  */ |  */ | ||||||
| public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivity { | public class LocationActivity extends Activity { | ||||||
|     public static final String TAG = "LocationActivity"; |     private static final String TAG = "LocationActivity"; | ||||||
|  |  | ||||||
| 	// SP配置常量(判断服务是否运行) |     private RecyclerView mRvPosition; | ||||||
|     private static final String SP_SERVICE_CONFIG = "service_config"; |     private PositionAdapter mPositionAdapter; | ||||||
|     private static final String KEY_SERVICE_RUNNING = "is_service_running"; |     private ArrayList<PositionModel> mLocalPosCache; // 本地位置缓存(与MainService同步) | ||||||
|     // 页面核心控件与变量 |  | ||||||
|     private RecyclerView mRecyclerView; |     // MainService 引用+绑定状态 | ||||||
|     private PositionAdapter mAdapter; |  | ||||||
|     // 直连服务实例(替代绑定,通过单例/全局初始化获取) |  | ||||||
|     private MainService mMainService; |     private MainService mMainService; | ||||||
|     // 缓存服务数据(从服务PUBLIC方法获取,避免重复调用) |     private boolean isServiceBound = false; | ||||||
|     private ArrayList<PositionModel> mCachedPositionList;  |  | ||||||
|     private ArrayList<PositionTaskModel> mCachedTaskList; |  | ||||||
|  |  | ||||||
|  |     // 服务连接(Java 7 匿名内部类实现) | ||||||
|  |     private ServiceConnection mServiceConnection = new ServiceConnection() { | ||||||
|  |         @Override | ||||||
|  |         public void onServiceConnected(ComponentName name, IBinder service) { | ||||||
|  |             // 假设 MainService 用 LocalBinder 暴露实例(Java 7 强转) | ||||||
|  |             MainService.LocalBinder binder = (MainService.LocalBinder) service; | ||||||
|  |             mMainService = binder.getService(); | ||||||
|  |             isServiceBound = true; | ||||||
|  |  | ||||||
|     @Override |             LogUtils.d(TAG, "MainService绑定成功,开始同步数据"); | ||||||
|     public Activity getActivity() { |             // 从MainService同步初始数据(位置+任务) | ||||||
|         return this; |             syncDataFromMainService(); | ||||||
|     } |             // 初始化Adapter(传入MainService实例,确保任务数据从服务获取) | ||||||
|  |             initPositionAdapter(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|     @Override |         @Override | ||||||
|     public String getTag() { |         public void onServiceDisconnected(ComponentName name) { | ||||||
|         return TAG; |             LogUtils.w(TAG, "MainService断开连接,清空引用"); | ||||||
|     } |             mMainService = null; | ||||||
|  |             isServiceBound = false; | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     // ---------------------- 页面生命周期(简化资源管理,无服务绑定) ---------------------- |  | ||||||
|     @Override |     @Override | ||||||
|     protected void onCreate(Bundle savedInstanceState) { |     protected void onCreate(Bundle savedInstanceState) { | ||||||
|         super.onCreate(savedInstanceState); |         super.onCreate(savedInstanceState); | ||||||
|         setContentView(R.layout.activity_location); |         setContentView(R.layout.activity_location); | ||||||
|  |  | ||||||
|         // 1. 初始化服务实例(通过单例/全局方式,避免直接new导致数据重置) |         // 初始化视图+本地缓存 | ||||||
|         initServiceInstance(); |         initView(); | ||||||
|         // 2. 检查服务状态(未运行/未初始化则提示关闭) |         mLocalPosCache = new ArrayList<PositionModel>(); | ||||||
|         //checkServiceStatus(); |  | ||||||
|         // 3. 初始化RecyclerView(布局+性能优化) |         // 绑定MainService(确保Activity启动时就拿到服务实例) | ||||||
|         initRecyclerViewConfig(); |         bindMainService(); | ||||||
|         // 4. 缓存服务数据(从服务PUBLIC方法获取,不访问私有字段) |     } | ||||||
|         cacheServiceData(); |  | ||||||
|         // 5. 初始化Adapter(传缓存数据,而非直接访问服务私有字段) |     /** | ||||||
|         initAdapter(); |      * 初始化视图(RecyclerView) | ||||||
|  |      */ | ||||||
|  |     private void initView() { | ||||||
|  |         mRvPosition = (RecyclerView) findViewById(R.id.rv_position_list); | ||||||
|  |         // Java 7 显式设置布局管理器(LinearLayoutManager) | ||||||
|  |         LinearLayoutManager layoutManager = new LinearLayoutManager(this); | ||||||
|  |         layoutManager.setOrientation(LinearLayoutManager.VERTICAL); | ||||||
|  |         mRvPosition.setLayoutManager(layoutManager); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 绑定MainService(Java 7 显式Intent) | ||||||
|  |      */ | ||||||
|  |     private void bindMainService() { | ||||||
|  |         Intent serviceIntent = new Intent(this, MainService.class); | ||||||
|  |         // 绑定服务(BIND_AUTO_CREATE:服务不存在时自动创建) | ||||||
|  |         bindService(serviceIntent, mServiceConnection, BIND_AUTO_CREATE); | ||||||
|  |         LogUtils.d(TAG, "发起MainService绑定请求"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 从MainService同步数据(位置+任务) | ||||||
|  |      */ | ||||||
|  |     private void syncDataFromMainService() { | ||||||
|  |         if (!isServiceBound || mMainService == null) { | ||||||
|  |             LogUtils.w(TAG, "同步数据失败:MainService未绑定"); | ||||||
|  |             showToast("服务未就绪,无法加载数据"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 同步位置数据(从服务获取最新列表) | ||||||
|  |         ArrayList<PositionModel> servicePosList = mMainService.getPositionList(); | ||||||
|  |         if (servicePosList != null && !servicePosList.isEmpty()) { | ||||||
|  |             mLocalPosCache.clear(); | ||||||
|  |             mLocalPosCache.addAll(servicePosList); | ||||||
|  |             LogUtils.d(TAG, "从MainService同步位置数据完成:数量=" + mLocalPosCache.size()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 同步任务数据(无需本地缓存,Adapter直接从服务获取) | ||||||
|  |         ArrayList<PositionTaskModel> serviceTaskList = mMainService.getAllTasks(); | ||||||
|  |         LogUtils.d(TAG, "从MainService同步任务数据完成:数量=" + serviceTaskList.size()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 初始化PositionAdapter(核心:传入MainService实例) | ||||||
|  |      */ | ||||||
|  |     private void initPositionAdapter() { | ||||||
|  |         if (mMainService == null) { | ||||||
|  |             LogUtils.e(TAG, "初始化Adapter失败:MainService为空"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Java 7 显式初始化Adapter,传入上下文+本地位置缓存+MainService实例 | ||||||
|  |         mPositionAdapter = new PositionAdapter(this, mLocalPosCache, mMainService); | ||||||
|  |  | ||||||
|  |         // 设置Adapter回调(处理位置删除/保存,最终同步到MainService) | ||||||
|  |         mPositionAdapter.setOnDeleteClickListener(new PositionAdapter.OnDeleteClickListener() { | ||||||
|  | 				@Override | ||||||
|  | 				public void onDeleteClick(int position) { | ||||||
|  | 					// 删除逻辑:先删本地缓存,再调用MainService接口删服务数据 | ||||||
|  | 					if (position < 0 || position >= mLocalPosCache.size()) { | ||||||
|  | 						LogUtils.w(TAG, "删除位置失败:无效索引=" + position); | ||||||
|  | 						return; | ||||||
|  | 					} | ||||||
|  | 					PositionModel deletePos = mLocalPosCache.get(position); | ||||||
|  | 					if (deletePos != null && !deletePos.getPositionId().isEmpty()) { | ||||||
|  | 						// 1. 调用MainService接口删除服务端数据 | ||||||
|  | 						mMainService.removePosition(deletePos.getPositionId()); | ||||||
|  | 						// 2. 删除本地缓存数据 | ||||||
|  | 						mLocalPosCache.remove(position); | ||||||
|  | 						// 3. 通知Adapter刷新 | ||||||
|  | 						mPositionAdapter.notifyItemRemoved(position); | ||||||
|  | 						showToast("删除位置成功:" + deletePos.getMemo()); | ||||||
|  | 						LogUtils.d(TAG, "删除位置完成:ID=" + deletePos.getPositionId() + "(已同步MainService)"); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  |         mPositionAdapter.setOnSavePositionClickListener(new PositionAdapter.OnSavePositionClickListener() { | ||||||
|  | 				@Override | ||||||
|  | 				public void onSavePositionClick(int position, PositionModel updatedPos) { | ||||||
|  | 					// 保存逻辑:先更本地缓存,再调用MainService接口更新服务数据 | ||||||
|  | 					if (!isServiceBound || mMainService == null) { | ||||||
|  | 						LogUtils.w(TAG, "保存位置失败:MainService未绑定"); | ||||||
|  | 						showToast("服务未就绪,保存失败"); | ||||||
|  | 						return; | ||||||
|  | 					} | ||||||
|  | 					if (position < 0 || position >= mLocalPosCache.size()) { | ||||||
|  | 						LogUtils.w(TAG, "保存位置失败:无效索引=" + position); | ||||||
|  | 						return; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// 1. 调用MainService接口更新服务端数据 | ||||||
|  | 					mMainService.updatePosition(updatedPos); | ||||||
|  | 					// 2. 更新本地缓存数据 | ||||||
|  | 					mLocalPosCache.set(position, updatedPos); | ||||||
|  | 					// 3. 通知Adapter刷新(可选,Adapter已本地同步) | ||||||
|  | 					mPositionAdapter.notifyItemChanged(position); | ||||||
|  | 					showToast("保存位置成功:" + updatedPos.getMemo()); | ||||||
|  | 					LogUtils.d(TAG, "保存位置完成:ID=" + updatedPos.getPositionId() + "(已同步MainService)"); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  |         // 设置Adapter到RecyclerView | ||||||
|  |         mRvPosition.setAdapter(mPositionAdapter); | ||||||
|  |         LogUtils.d(TAG, "PositionAdapter初始化完成(已绑定MainService)"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 显示Toast(Java 7 显式Toast.makeText) | ||||||
|  |      */ | ||||||
|  |     private void showToast(String content) { | ||||||
|  |         Toast.makeText(this, content, Toast.LENGTH_SHORT).show(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     protected void onDestroy() { |     protected void onDestroy() { | ||||||
|         super.onDestroy(); |         super.onDestroy(); | ||||||
|         // 1. 清理Adapter资源 |  | ||||||
|         if (mAdapter != null) { |         // 1. 释放Adapter资源(反注册服务监听,避免内存泄漏) | ||||||
|             mAdapter.release(); |         if (mPositionAdapter != null) { | ||||||
|             mAdapter = null; |             mPositionAdapter.release(); | ||||||
|         } |         } | ||||||
|         // 2. 置空服务实例+缓存数据(帮助GC回收) |  | ||||||
|         mMainService = null; |  | ||||||
|         mCachedPositionList = null; |  | ||||||
|         mCachedTaskList = null; |  | ||||||
|         mRecyclerView = null; |  | ||||||
|         LogUtils.d(TAG, "页面销毁:已清理服务实例及缓存数据"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // ---------------------- 核心初始化(服务实例+数据缓存+状态检查) ---------------------- |         // 2. 解绑MainService(避免Activity销毁后服务仍被持有) | ||||||
|     /** |         if (isServiceBound) { | ||||||
|      * 初始化服务实例(关键:通过单例获取,确保全局唯一,避免数据重复) |             unbindService(mServiceConnection); | ||||||
|      * 注:需在DistanceRefreshService中实现单例(getInstance()方法) |             LogUtils.d(TAG, "MainService解绑完成"); | ||||||
|      */ |  | ||||||
|     private void initServiceInstance() { |  | ||||||
| 		// 步骤1:先启动Service(确保Service已创建,实例能被初始化) |  | ||||||
|          |  | ||||||
|         // 步骤2:初始化Service实例(延迟100-200ms,确保Service onCreate执行完成) |  | ||||||
|         // (或在startService后,通过ServiceConnection绑定获取实例,更可靠) |  | ||||||
|         //initServiceInstance(); |  | ||||||
| 		 |  | ||||||
|         // 方式:通过服务PUBLIC单例方法获取实例(不直接new,避免私有数据初始化重复) |  | ||||||
|         mMainService = MainService.getInstance(); |  | ||||||
|          |  | ||||||
|         // 容错:若单例未初始化(如首次启动),提示并处理 |  | ||||||
|         if (mMainService == null) { |  | ||||||
|             LogUtils.e(TAG, "服务实例初始化失败:单例未创建"); |  | ||||||
|             Toast.makeText(this, "位置服务初始化失败,请重启应用", Toast.LENGTH_SHORT).show(); |  | ||||||
|             finish(); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 	 | 	 | ||||||
|     /** | 	public static class LocalBinder extends android.os.Binder { | ||||||
|      * 检查服务状态(替代原绑定检查,通过服务PUBLIC方法判断) |         // 持有 MainService 实例引用 | ||||||
|      */ |         private MainService mService; | ||||||
|     private void checkServiceStatus() { |  | ||||||
|         // 1. 服务实例未初始化 |         // 构造时传入服务实例 | ||||||
|         if (mMainService == null) { |         public LocalBinder(MainService service) { | ||||||
|             return; // 已在initServiceInstance中处理,此处避免重复finish |             this.mService = service; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 2. 检查服务运行状态(通过服务PUBLIC方法isServiceRunning()获取,不访问私有字段) |         // 对外提供获取服务实例的方法(供Activity调用) | ||||||
| //        if (!mMainService.isServiceRunning()) { |         public MainService getService() { | ||||||
| //            // 服务未运行:手动触发启动(调用服务PUBLIC方法run()) |             return mService; | ||||||
| //            mMainService.run(); |  | ||||||
| //            LogUtils.d(TAG, "服务未运行,已通过PUBLIC方法触发启动"); |  | ||||||
| //        } |  | ||||||
|  |  | ||||||
|         // 3. 检查SP中服务配置(双重校验,确保一致性) |  | ||||||
|         SharedPreferences sp = getSharedPreferences(SP_SERVICE_CONFIG, Context.MODE_PRIVATE); |  | ||||||
|         boolean isServiceConfigRunning = sp.getBoolean(KEY_SERVICE_RUNNING, false); |  | ||||||
|         if (!isServiceConfigRunning) { |  | ||||||
|             Toast.makeText(this, "位置服务配置未启用,数据可能无法更新", Toast.LENGTH_SHORT).show(); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 缓存服务数据(从服务PUBLIC方法获取,解决私有字段未知问题) |  | ||||||
|      */ |  | ||||||
|     private void cacheServiceData() { |  | ||||||
|         if (mMainService == null) { |  | ||||||
|             LogUtils.e(TAG, "缓存数据失败:服务实例为空"); |  | ||||||
|             mCachedPositionList = new ArrayList<PositionModel>(); |  | ||||||
|             mCachedTaskList = new ArrayList<PositionTaskModel>(); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // 从服务PUBLIC方法获取数据(不访问mPositionList,而是调用getPositionList()) |  | ||||||
|         mCachedPositionList = mMainService.getPositionList(); |  | ||||||
|         mCachedTaskList = mMainService.getPositionTasksList(); |  | ||||||
|          |  | ||||||
|         // 容错:若服务返回null,初始化空列表避免空指针 |  | ||||||
|         if (mCachedPositionList == null) mCachedPositionList = new ArrayList<PositionModel>(); |  | ||||||
|         if (mCachedTaskList == null) mCachedTaskList = new ArrayList<PositionTaskModel>(); |  | ||||||
|          |  | ||||||
|         LogUtils.d(TAG, "缓存服务数据成功:位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 初始化RecyclerView(保留原性能优化) |  | ||||||
|      */ |  | ||||||
|     private void initRecyclerViewConfig() { |  | ||||||
|         mRecyclerView = (RecyclerView) findViewById(R.id.rv_position_list); |  | ||||||
|         LinearLayoutManager layoutManager = new LinearLayoutManager(this); |  | ||||||
|         layoutManager.setOrientation(LinearLayoutManager.VERTICAL); |  | ||||||
|         mRecyclerView.setLayoutManager(layoutManager); |  | ||||||
|         mRecyclerView.setHasFixedSize(true); // 固定大小,优化绘制 |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // ---------------------- Adapter初始化与数据交互(全通过服务PUBLIC方法) ---------------------- |  | ||||||
|     /** |  | ||||||
|      * 初始化Adapter(通过缓存数据初始化,数据更新时同步调用服务方法) |  | ||||||
|      */ |  | ||||||
|     private void initAdapter() { |  | ||||||
|         if (mCachedPositionList == null || mCachedTaskList == null) { |  | ||||||
|             LogUtils.e(TAG, "初始化Adapter失败:缓存数据为空"); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // 1. 初始化Adapter(传缓存数据+服务实例,Adapter数据从缓存取,操作通过服务) |  | ||||||
|         mAdapter = new PositionAdapter(this, mCachedPositionList, mCachedTaskList); |  | ||||||
|         mRecyclerView.setAdapter(mAdapter); |  | ||||||
|  |  | ||||||
|         // 2. 删除回调:通过服务PUBLIC方法removePosition()删除,不直接操作私有字段 |  | ||||||
|         mAdapter.setOnDeleteClickListener(new PositionAdapter.OnDeleteClickListener() { |  | ||||||
|             @Override |  | ||||||
|             public void onDeleteClick(int position) { |  | ||||||
|                 // 校验:服务实例有效+索引合法 |  | ||||||
|                 if (mMainService == null || mCachedPositionList == null) { |  | ||||||
|                     Toast.makeText(LocationActivity.this, "删除失败:服务未就绪", Toast.LENGTH_SHORT).show(); |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|                 if (position < 0 || position >= mCachedPositionList.size()) { |  | ||||||
|                     LogUtils.w(TAG, "删除失败:索引无效(" + position + ")"); |  | ||||||
|                     Toast.makeText(LocationActivity.this, "删除失败:数据位置异常", Toast.LENGTH_SHORT).show(); |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // 1. 获取要删除的位置(从缓存取,与服务数据一致) |  | ||||||
|                 PositionModel targetPos = mCachedPositionList.get(position); |  | ||||||
|                 String targetPosId = targetPos.getPositionId(); |  | ||||||
|  |  | ||||||
|                 // 2. 调用服务PUBLIC方法删除(服务内部处理mPositionList/mTaskList,符合封装) |  | ||||||
|                 mMainService.removePosition(targetPosId); |  | ||||||
|                 LogUtils.d(TAG, "通过服务删除位置:ID=" + targetPosId); |  | ||||||
|  |  | ||||||
|                 // 3. 刷新缓存+Adapter(从服务重新获取数据,确保与服务一致) |  | ||||||
|                 refreshCachedDataAndAdapter(); |  | ||||||
|                 Toast.makeText(LocationActivity.this, "位置已删除(含关联任务)", Toast.LENGTH_SHORT).show(); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         // 3. 位置保存回调:通过服务PUBLIC方法updatePosition()更新 |  | ||||||
|         mAdapter.setOnSavePositionClickListener(new PositionAdapter.OnSavePositionClickListener() { |  | ||||||
|             @Override |  | ||||||
|             public void onSavePositionClick(int position, PositionModel updatedPos) { |  | ||||||
|                 // 校验:服务+数据+参数有效 |  | ||||||
|                 if (mMainService == null || mCachedPositionList == null || updatedPos == null) { |  | ||||||
|                     Toast.makeText(LocationActivity.this, "保存失败:服务或数据异常", Toast.LENGTH_SHORT).show(); |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|                 if (position < 0 || position >= mCachedPositionList.size()) { |  | ||||||
|                     LogUtils.w(TAG, "保存失败:位置索引无效"); |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // 1. 调用服务PUBLIC方法更新数据(服务内部修改mPositionList) |  | ||||||
|                 mMainService.updatePosition(updatedPos); |  | ||||||
|                 LogUtils.d(TAG, "通过服务保存位置:ID=" + updatedPos.getPositionId()); |  | ||||||
|  |  | ||||||
|                 // 2. 刷新缓存+Adapter(确保本地显示与服务一致) |  | ||||||
|                 refreshCachedDataAndAdapter(); |  | ||||||
|                 Toast.makeText(LocationActivity.this, "位置信息已保存", Toast.LENGTH_SHORT).show(); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         // 4. 任务保存回调:通过服务PUBLIC方法syncAllPositionTasks()同步 |  | ||||||
|         mAdapter.setOnSavePositionTaskClickListener(new PositionAdapter.OnSavePositionTaskClickListener() { |  | ||||||
|             @Override |  | ||||||
|             public void onSavePositionTaskClick(PositionTaskModel newTask) { |  | ||||||
|                 if (mMainService == null || newTask == null) { |  | ||||||
|                     Toast.makeText(LocationActivity.this, "保存失败:服务或任务数据为空", Toast.LENGTH_SHORT).show(); |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // 1. 构建新任务列表(缓存任务+新任务,去重) |  | ||||||
|                 ArrayList<PositionTaskModel> newTaskList = new ArrayList<PositionTaskModel>(mCachedTaskList); |  | ||||||
|                 // 先移除同ID旧任务(避免重复) |  | ||||||
|                 for (int i = 0; i < newTaskList.size(); i++) { |  | ||||||
|                     PositionTaskModel oldTask = newTaskList.get(i); |  | ||||||
|                     if (newTask.getTaskId().equals(oldTask.getTaskId())) { |  | ||||||
|                         newTaskList.remove(i); |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 // 添加新任务 |  | ||||||
|                 newTaskList.add(newTask); |  | ||||||
|  |  | ||||||
|                 // 2. 调用服务PUBLIC方法同步任务(服务内部更新mTaskList) |  | ||||||
|                 mMainService.syncAllPositionTasks(newTaskList); |  | ||||||
|                 LogUtils.d(TAG, "通过服务保存任务:ID=" + newTask.getTaskId()); |  | ||||||
|  |  | ||||||
|                 // 3. 刷新缓存+Adapter |  | ||||||
|                 refreshCachedDataAndAdapter(); |  | ||||||
|                 Toast.makeText(LocationActivity.this, "任务信息已保存", Toast.LENGTH_SHORT).show(); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // ---------------------- 页面交互(新增位置、同步GPS,全走服务PUBLIC方法) ---------------------- |  | ||||||
|     /** |  | ||||||
|      * 新增位置(调用服务addPosition(),不直接操作mPositionList) |  | ||||||
|      */ |  | ||||||
|     public void addNewPosition(View view) { |  | ||||||
|         // 1. 隐藏软键盘 |  | ||||||
|         InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); |  | ||||||
|         if (imm != null && getCurrentFocus() != null) { |  | ||||||
|             imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // 2. 校验服务状态 |  | ||||||
|         if (mMainService == null) { |  | ||||||
|             Toast.makeText(this, "新增失败:服务未初始化", Toast.LENGTH_SHORT).show(); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // 3. 创建新位置模型 |  | ||||||
|         PositionModel newPos = new PositionModel(); |  | ||||||
|         newPos.setPositionId(PositionModel.genPositionId()); // 生成唯一ID(PositionModel需实现) |  | ||||||
|         newPos.setLongitude(116.404267); // 示例经度(北京) |  | ||||||
|         newPos.setLatitude(39.915119);  // 示例纬度 |  | ||||||
|         newPos.setMemo("测试位置(可编辑备注)"); |  | ||||||
|         newPos.setIsSimpleView(true);   // 默认简单视图 |  | ||||||
|         newPos.setIsEnableRealPositionDistance(true); // 启用距离计算 |  | ||||||
|  |  | ||||||
|         // 4. 调用服务PUBLIC方法新增(服务内部处理mPositionList去重+添加) |  | ||||||
|         mMainService.addPosition(newPos); |  | ||||||
|         LogUtils.d(TAG, "通过服务新增位置:ID=" + newPos.getPositionId()); |  | ||||||
|  |  | ||||||
|         // 5. 刷新缓存+Adapter(显示新增结果) |  | ||||||
|         refreshCachedDataAndAdapter(); |  | ||||||
|         Toast.makeText(this, "新增位置成功(已启用距离计算)", Toast.LENGTH_SHORT).show(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 同步GPS位置到服务(调用服务syncCurrentGpsPosition(),不访问私有字段) |  | ||||||
|      */ |  | ||||||
|     public void syncGpsPositionToService(PositionModel gpsModel) { |  | ||||||
|         if (mMainService == null || gpsModel == null) { |  | ||||||
|             LogUtils.w(TAG, "同步GPS失败:服务未就绪或GPS数据无效"); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // 调用服务PUBLIC方法同步GPS(服务内部更新mCurrentGpsPosition) |  | ||||||
|         mMainService.syncCurrentGpsPosition(gpsModel); |  | ||||||
|         // 强制刷新距离(通过服务PUBLIC方法,触发mPositionList距离计算) |  | ||||||
|         mMainService.forceRefreshDistance(); |  | ||||||
|         LogUtils.d(TAG, "GPS位置已同步,通过服务触发距离计算"); |  | ||||||
|  |  | ||||||
|         // 刷新缓存+Adapter(显示最新距离) |  | ||||||
|         refreshCachedDataAndAdapter(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // ---------------------- 辅助方法(统一刷新缓存与Adapter,确保数据一致) ---------------------- |  | ||||||
|     /** |  | ||||||
|      * 刷新缓存数据+Adapter(从服务重新获取数据,避免本地缓存与服务不一致) |  | ||||||
|      */ |  | ||||||
|     private void refreshCachedDataAndAdapter() { |  | ||||||
|         if (mMainService == null) { |  | ||||||
|             LogUtils.w(TAG, "刷新失败:服务实例为空"); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // 1. 从服务重新获取数据(更新缓存,不访问私有字段) |  | ||||||
|         mCachedPositionList = mMainService.getPositionList(); |  | ||||||
|         mCachedTaskList = mMainService.getPositionTasksList(); |  | ||||||
|         // 容错处理 |  | ||||||
|         if (mCachedPositionList == null) mCachedPositionList = new ArrayList<PositionModel>(); |  | ||||||
|         if (mCachedTaskList == null) mCachedTaskList = new ArrayList<PositionTaskModel>(); |  | ||||||
|  |  | ||||||
|         // 2. 刷新Adapter(全量刷新,简单可靠;若需性能优化可改为局部刷新) |  | ||||||
|         if (mAdapter != null) { |  | ||||||
|             mAdapter.updateAllData(mCachedPositionList, mCachedTaskList); |  | ||||||
|         } |  | ||||||
|         LogUtils.d(TAG, "刷新完成:当前位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // ---------------------- 补充:DistanceRefreshService单例实现(需在服务中添加) ---------------------- |  | ||||||
|     // 注:以下代码需复制到 DistanceRefreshService 类中,确保实例唯一(解决直接new的问题) |  | ||||||
|     /* |  | ||||||
|     // 服务单例实例(私有静态) |  | ||||||
|     private static DistanceRefreshService sInstance; |  | ||||||
|  |  | ||||||
|     // 单例PUBLIC方法(供外部获取实例,确保全局唯一) |  | ||||||
|     public static synchronized DistanceRefreshService getInstance() { |  | ||||||
|         if (sInstance == null) { |  | ||||||
|             sInstance = new DistanceRefreshService(); |  | ||||||
|             // 初始化服务核心逻辑(如GPS管理器、线程池,避免重复初始化) |  | ||||||
|             sInstance.mLocationManager = (LocationManager) sInstance.getApplicationContext().getSystemService(Context.LOCATION_SERVICE); |  | ||||||
|             sInstance.initGpsLocationListener(); |  | ||||||
|         } |  | ||||||
|         return sInstance; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 重写构造方法为私有(禁止外部直接new,确保单例) |  | ||||||
|     private DistanceRefreshService() { |  | ||||||
|         distanceExecutor = Executors.newSingleThreadScheduledExecutor(); |  | ||||||
|     } |  | ||||||
|     */ |  | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ package cc.winboll.studio.positions.adapters; | |||||||
|  * @Date 2025/09/29 20:25 |  * @Date 2025/09/29 20:25 | ||||||
|  * @Describe 位置数据适配器(完全独立,无未知接口依赖,仅用LocationActivity缓存数据) |  * @Describe 位置数据适配器(完全独立,无未知接口依赖,仅用LocationActivity缓存数据) | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| @@ -15,30 +14,36 @@ import android.widget.Button; | |||||||
| import android.widget.EditText; | import android.widget.EditText; | ||||||
| import android.widget.RadioGroup; | import android.widget.RadioGroup; | ||||||
| import android.widget.TextView; | import android.widget.TextView; | ||||||
|  | import android.text.TextUtils; | ||||||
| import androidx.recyclerview.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
|  |  | ||||||
| import cc.winboll.studio.libappbase.LogUtils; | import cc.winboll.studio.libappbase.LogUtils; | ||||||
| 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.models.PositionTaskModel; | ||||||
|  | import cc.winboll.studio.positions.services.MainService; | ||||||
|  |  | ||||||
|  | import java.lang.ref.WeakReference; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.ConcurrentModificationException; | import java.util.ConcurrentModificationException; | ||||||
| import java.util.Iterator; | import java.util.Iterator; | ||||||
| import java.util.concurrent.ConcurrentHashMap; | import java.util.concurrent.ConcurrentHashMap; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 核心调整: |  * Java 7 语法适配: | ||||||
|  * 1. 彻底删除所有与 DistanceServiceInterface 相关的代码(解决未知类型问题) |  * 1. 移除 Lambda/方法引用,用匿名内部类替代 | ||||||
|  * 2. 完全基于 LocationActivity 传入的缓存数据(mCachedPositionList/mCachedTaskList)实现所有功能 |  * 2. 集合操作使用迭代器(避免 ConcurrentModificationException) | ||||||
|  * 3. 移除服务回调、可见位置通知等冗余逻辑,仅保留“数据显示+用户交互→通知Activity处理”的核心流程 |  * 3. 弱引用管理 MainService,避免内存泄漏 | ||||||
|  * 4. 确保无未定义接口、无外部服务依赖,代码可直接编译运行 |  * 4. 所有任务数据从 MainService 获取,更新通过 MainService 接口 | ||||||
|  */ |  */ | ||||||
| public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { | public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements MainService.TaskUpdateListener { | ||||||
|     public static final String TAG = "PositionAdapter"; |     public static final String TAG = "PositionAdapter"; | ||||||
|  |  | ||||||
|     // 1. 视图类型常量(简单视图/编辑视图,无魔法值) |     // 视图类型常量(Java 7 静态常量定义) | ||||||
|     private static final int VIEW_TYPE_SIMPLE = 0; |     private static final int VIEW_TYPE_SIMPLE = 0; | ||||||
|     private static final int VIEW_TYPE_EDIT = 1; |     private static final int VIEW_TYPE_EDIT = 1; | ||||||
|     // 2. 默认配置(文本显示/任务默认值,统一管理) |  | ||||||
|  |     // 默认配置常量(统一管理,避免魔法值) | ||||||
|     private static final String DEFAULT_MEMO = "无备注"; |     private static final String DEFAULT_MEMO = "无备注"; | ||||||
|     private static final String DEFAULT_TASK_DESC = "新任务"; |     private static final String DEFAULT_TASK_DESC = "新任务"; | ||||||
|     private static final int DEFAULT_TASK_DISTANCE = 50; // 单位:米 |     private static final int DEFAULT_TASK_DISTANCE = 50; // 单位:米 | ||||||
| @@ -46,81 +51,61 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
|     private static final String DISTANCE_DISABLED = "实时距离:未启用"; |     private static final String DISTANCE_DISABLED = "实时距离:未启用"; | ||||||
|     private static final String DISTANCE_ERROR = "实时距离:计算失败"; |     private static final String DISTANCE_ERROR = "实时距离:计算失败"; | ||||||
|  |  | ||||||
|     // 3. 唯一数据源(完全依赖 LocationActivity 传入的缓存数据,无其他来源) |     // 核心依赖(Java 7 弱引用+集合定义) | ||||||
|     private final Context mContext; |     private final Context mContext; | ||||||
|     private ArrayList<PositionModel> mCachedPositionList; // 位置缓存(来自Activity) |     private final ArrayList<PositionModel> mCachedPositionList; // 位置缓存(从Activity传入,最终需与MainService同步) | ||||||
|     private ArrayList<PositionTaskModel> mCachedTaskList;  // 任务缓存(来自Activity) |     private final WeakReference<MainService> mMainServiceRef;   // 弱引用MainService,避免内存泄漏 | ||||||
|  |     private final ConcurrentHashMap<String, TextView> mPosDistanceViewMap; // 距离控件缓存(优化UI更新) | ||||||
|  |  | ||||||
|     // 4. 本地辅助缓存(优化性能:避免重复遍历,不依赖外部服务) |     // 回调接口(与Activity交互,仅处理位置逻辑,任务逻辑直接调用MainService) | ||||||
|     private final ConcurrentHashMap<String, ArrayList<PositionTaskModel>> mPosToTasksMap; // 位置ID→关联任务(本地分组) |  | ||||||
|     private final ConcurrentHashMap<String, TextView> mPosDistanceViewMap; // 位置ID→距离控件(更新UI用) |  | ||||||
|  |  | ||||||
|     // ========================================================================= |  | ||||||
|     // 与 LocationActivity 交互的回调接口(仅定义必要功能,无冗余) |  | ||||||
|     // ========================================================================= |  | ||||||
|     // 删除位置:通知Activity删除指定索引的位置数据 |  | ||||||
|     public interface OnDeleteClickListener { |     public interface OnDeleteClickListener { | ||||||
|         void onDeleteClick(int position); |         void onDeleteClick(int position); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // 保存位置:通知Activity保存更新后的位置模型(带索引,方便Activity定位修改) |  | ||||||
|     public interface OnSavePositionClickListener { |     public interface OnSavePositionClickListener { | ||||||
|         void onSavePositionClick(int position, PositionModel updatedPos); |         void onSavePositionClick(int position, PositionModel updatedPos); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // 保存任务:通知Activity保存新增/修改的任务(带任务模型,Activity处理数据同步) |  | ||||||
|     public interface OnSavePositionTaskClickListener { |  | ||||||
|         void onSavePositionTaskClick(PositionTaskModel newTask); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 回调实例(由LocationActivity初始化时设置,解耦Activity与Adapter) |  | ||||||
|     private OnDeleteClickListener mOnDeleteListener; |     private OnDeleteClickListener mOnDeleteListener; | ||||||
|     private OnSavePositionClickListener mOnSavePosListener; |     private OnSavePositionClickListener mOnSavePosListener; | ||||||
|     private OnSavePositionTaskClickListener mOnSaveTaskListener; |  | ||||||
|  |  | ||||||
|     // ========================================================================= |     // ========================================================================= | ||||||
|     // 构造函数(仅接收上下文+Activity缓存数据,无其他依赖,解决接口未定义问题) |     // 构造函数(Java 7 风格:初始化依赖+注册任务监听) | ||||||
|     // ========================================================================= |     // ========================================================================= | ||||||
|     /** |     public PositionAdapter(Context context, ArrayList<PositionModel> cachedPositionList, MainService mainService) { | ||||||
|      * 唯一构造函数(确保无未知接口依赖,直接使用Activity提供的数据) |  | ||||||
|      * @param context 上下文(加载布局、操作软键盘、获取资源) |  | ||||||
|      * @param cachedPositionList LocationActivity中的位置缓存(唯一位置数据源) |  | ||||||
|      * @param cachedTaskList LocationActivity中的任务缓存(唯一任务数据源) |  | ||||||
|      */ |  | ||||||
|     public PositionAdapter(Context context, ArrayList<PositionModel> cachedPositionList, ArrayList<PositionTaskModel> cachedTaskList) { |  | ||||||
|         this.mContext = context; |         this.mContext = context; | ||||||
|         // 初始化数据源(容错处理:若Activity传入null,初始化空列表避免空指针) |         // 容错处理:避免传入null导致空指针 | ||||||
|         this.mCachedPositionList = (cachedPositionList != null) ? cachedPositionList : new ArrayList<PositionModel>(); |         this.mCachedPositionList = (cachedPositionList != null) ? cachedPositionList : new ArrayList<PositionModel>(); | ||||||
|         this.mCachedTaskList = (cachedTaskList != null) ? cachedTaskList : new ArrayList<PositionTaskModel>(); |         // 弱引用MainService:防止Adapter持有Service导致内存泄漏(Java 7 弱引用语法) | ||||||
|  |         this.mMainServiceRef = new WeakReference<MainService>(mainService); | ||||||
|         // 初始化本地辅助缓存(基于Activity传入的数据构建,提升后续操作性能) |         // 初始化距离控件缓存(线程安全集合,适配多线程更新场景) | ||||||
|         this.mPosToTasksMap = new ConcurrentHashMap<String, ArrayList<PositionTaskModel>>(); |  | ||||||
|         this.mPosDistanceViewMap = new ConcurrentHashMap<String, TextView>(); |         this.mPosDistanceViewMap = new ConcurrentHashMap<String, TextView>(); | ||||||
|  |  | ||||||
|         // 初始化“位置→任务”映射(从Activity任务缓存中分组,避免每次显示都遍历全量任务) |         // 注册MainService任务监听:服务任务变化时自动刷新Adapter(Java 7 接口实现) | ||||||
|         refreshPositionTaskMap(); |         if (mainService != null) { | ||||||
|         LogUtils.d(TAG, "Adapter初始化完成:位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size()); |             mainService.registerTaskUpdateListener(this); | ||||||
|  |             LogUtils.d(TAG, "已注册MainService任务监听,确保任务数据与服务同步"); | ||||||
|  |         } else { | ||||||
|  |             LogUtils.w(TAG, "构造函数:MainService为空,任务数据无法同步"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         LogUtils.d(TAG, "Adapter初始化完成:位置数量=" + mCachedPositionList.size()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // ========================================================================= |     // ========================================================================= | ||||||
|     // RecyclerView 核心方法(完全基于Activity缓存数据实现,无接口调用) |     // RecyclerView 核心方法(Java 7 语法适配) | ||||||
|     // ========================================================================= |     // ========================================================================= | ||||||
|     /** |  | ||||||
|      * 判断当前项的视图类型(简单/编辑):从Activity缓存数据中读取状态 |  | ||||||
|      */ |  | ||||||
|     @Override |     @Override | ||||||
|     public int getItemViewType(int position) { |     public int getItemViewType(int position) { | ||||||
|  |         // 从位置缓存获取状态,判断视图类型(简单/编辑) | ||||||
|         PositionModel posModel = getPositionByIndex(position); |         PositionModel posModel = getPositionByIndex(position); | ||||||
|         // 若位置模型为空或标记为简单视图,返回简单视图类型 |  | ||||||
|         return (posModel != null && posModel.isSimpleView()) ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT; |         return (posModel != null && posModel.isSimpleView()) ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 创建视图Holder(根据类型加载布局,无外部依赖) |  | ||||||
|      */ |  | ||||||
|     @Override |     @Override | ||||||
|     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { | ||||||
|         LayoutInflater inflater = LayoutInflater.from(mContext); |         LayoutInflater inflater = LayoutInflater.from(mContext); | ||||||
|  |         // 根据视图类型加载对应布局(Java 7 条件判断) | ||||||
|         if (viewType == VIEW_TYPE_SIMPLE) { |         if (viewType == VIEW_TYPE_SIMPLE) { | ||||||
|             View simpleView = inflater.inflate(R.layout.item_position_simple, parent, false); |             View simpleView = inflater.inflate(R.layout.item_position_simple, parent, false); | ||||||
|             return new SimpleViewHolder(simpleView); |             return new SimpleViewHolder(simpleView); | ||||||
| @@ -130,111 +115,117 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 绑定视图数据(核心逻辑:从Activity缓存取数,不调用任何外部接口) |  | ||||||
|      */ |  | ||||||
|     @Override |     @Override | ||||||
|     public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { |     public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { | ||||||
|         PositionModel posModel = getPositionByIndex(position); |         PositionModel posModel = getPositionByIndex(position); | ||||||
|         if (posModel == null) { |         if (posModel == null) { | ||||||
|             LogUtils.w(TAG, "绑定数据失败:位置模型为空(索引=" + position + ")"); |             LogUtils.w(TAG, "onBindViewHolder:位置模型为空(索引=" + position + "),跳过绑定"); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         String posId = posModel.getPositionId(); |         String posId = posModel.getPositionId(); | ||||||
|  |  | ||||||
|         // 根据视图类型绑定数据(简单视图/编辑视图分别处理) |         // 按视图类型绑定数据(Java 7 类型判断) | ||||||
|         if (holder instanceof SimpleViewHolder) { |         if (holder instanceof SimpleViewHolder) { | ||||||
|             bindSimpleView((SimpleViewHolder) holder, posModel); |             bindSimpleView((SimpleViewHolder) holder, posModel); | ||||||
|         } else if (holder instanceof EditViewHolder) { |         } else if (holder instanceof EditViewHolder) { | ||||||
|             bindEditView((EditViewHolder) holder, posModel, position); |             bindEditView((EditViewHolder) holder, posModel, position); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 缓存当前位置的距离控件(后续更新距离UI时直接使用,避免重新查找) |         // 缓存当前位置的距离控件(后续局部更新距离时直接使用) | ||||||
|         TextView distanceView = (holder instanceof SimpleViewHolder) |         TextView distanceView = (holder instanceof SimpleViewHolder) | ||||||
| 			? ((SimpleViewHolder) holder).tvSimpleDistance | 			? ((SimpleViewHolder) holder).tvSimpleDistance | ||||||
| 			: ((EditViewHolder) holder).tvEditDistance; | 			: ((EditViewHolder) holder).tvEditDistance; | ||||||
|         mPosDistanceViewMap.put(posId, distanceView); |         if (distanceView != null && !TextUtils.isEmpty(posId)) { | ||||||
|     } |             mPosDistanceViewMap.put(posId, distanceView); | ||||||
|  |         } | ||||||
|     /** |     } | ||||||
|      * 视图离开屏幕(清理距离控件缓存,避免内存泄漏) |  | ||||||
|      */ |     @Override | ||||||
|     @Override |     public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) { | ||||||
|     public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) { |         super.onViewDetachedFromWindow(holder); | ||||||
|         super.onViewDetachedFromWindow(holder); |         // 视图离开屏幕时,移除距离控件缓存(避免内存泄漏+引用失效控件) | ||||||
|         PositionModel posModel = getPositionByIndex(holder.getAdapterPosition()); |         PositionModel posModel = getPositionByIndex(holder.getAdapterPosition()); | ||||||
|         if (posModel != null) { |         if (posModel != null && !TextUtils.isEmpty(posModel.getPositionId())) { | ||||||
|             mPosDistanceViewMap.remove(posModel.getPositionId()); // 移除不再显示的控件引用 |             mPosDistanceViewMap.remove(posModel.getPositionId()); | ||||||
|  |             LogUtils.d(TAG, "视图脱离屏幕:移除位置ID=" + posModel.getPositionId() + "的距离控件缓存"); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 获取列表项数量(直接从Activity位置缓存中取,无中间层) |  | ||||||
|      */ |  | ||||||
|     @Override |     @Override | ||||||
|     public int getItemCount() { |     public int getItemCount() { | ||||||
|  |         // 直接从位置缓存获取数量(数据源唯一) | ||||||
|         return mCachedPositionList.size(); |         return mCachedPositionList.size(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // ========================================================================= |     // ========================================================================= | ||||||
|     // 视图绑定细节(完全基于缓存数据,操作后通过回调通知Activity) |     // 视图绑定逻辑(Java 7 风格:任务数据从MainService获取) | ||||||
|     // ========================================================================= |     // ========================================================================= | ||||||
|     /** |     /** | ||||||
|      * 绑定“简单视图”(仅显示数据,点击切换到编辑视图) |      * 绑定简单视图(仅显示数据,点击切换到编辑视图) | ||||||
|      */ |      */ | ||||||
|     private void bindSimpleView(final SimpleViewHolder holder, final PositionModel posModel) { |     private void bindSimpleView(final SimpleViewHolder holder, final PositionModel posModel) { | ||||||
|         // 1. 显示经纬度(从Activity缓存数据中取,格式化6位小数提升可读性) |         // 1. 显示经纬度(Java 7 String.format格式化) | ||||||
|         holder.tvSimpleLon.setText(String.format("经度:%.6f", posModel.getLongitude())); |         holder.tvSimpleLon.setText(String.format("经度:%.6f", posModel.getLongitude())); | ||||||
|         holder.tvSimpleLat.setText(String.format("纬度:%.6f", posModel.getLatitude())); |         holder.tvSimpleLat.setText(String.format("纬度:%.6f", posModel.getLatitude())); | ||||||
|         // 2. 显示备注(无备注时显示默认文本,避免空白) |  | ||||||
|  |         // 2. 显示备注(无备注时显示默认文本) | ||||||
|         String memo = posModel.getMemo(); |         String memo = posModel.getMemo(); | ||||||
|         holder.tvSimpleMemo.setText("备注:" + (memo != null && !memo.isEmpty() ? memo : DEFAULT_MEMO)); |         holder.tvSimpleMemo.setText("备注:" + (TextUtils.isEmpty(memo) ? DEFAULT_MEMO : memo)); | ||||||
|         // 3. 显示实时距离(从缓存数据的distance字段取数,按状态显示不同内容) |  | ||||||
|  |         // 3. 显示实时距离(从位置模型取数,调用工具方法更新显示) | ||||||
|         updateDistanceDisplay(holder.tvSimpleDistance, posModel); |         updateDistanceDisplay(holder.tvSimpleDistance, posModel); | ||||||
|         // 4. 点击视图→切换到编辑模式(修改缓存数据中的状态,通知RecyclerView刷新) |  | ||||||
|  |         // 4. 点击切换到编辑视图(Java 7 匿名内部类实现点击事件) | ||||||
|         holder.itemView.setOnClickListener(new View.OnClickListener() { |         holder.itemView.setOnClickListener(new View.OnClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public void onClick(View v) { | 				public void onClick(View v) { | ||||||
| 					posModel.setIsSimpleView(false); // 修改缓存数据中的视图状态 | 					posModel.setIsSimpleView(false); // 修改位置缓存状态 | ||||||
| 					notifyItemChanged(getPositionIndexById(posModel.getPositionId())); // 刷新对应项 | 					// 通知RecyclerView刷新当前项(精准更新,避免全量刷新) | ||||||
|  | 					notifyItemChanged(getPositionIndexById(posModel.getPositionId())); | ||||||
|  | 					LogUtils.d(TAG, "简单视图点击:位置ID=" + posModel.getPositionId() + ",切换到编辑视图"); | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 绑定“编辑视图”(支持修改备注、开关距离、删除/保存位置、新增任务) |      * 绑定编辑视图(支持修改备注、开关距离、删除/保存位置、新增任务) | ||||||
|      */ |      */ | ||||||
|     private void bindEditView(final EditViewHolder holder, final PositionModel posModel, final int position) { |     private void bindEditView(final EditViewHolder holder, final PositionModel posModel, final int position) { | ||||||
|         final String posId = posModel.getPositionId(); |         final String posId = posModel.getPositionId(); | ||||||
|  |  | ||||||
|         // 1. 显示经纬度(不可编辑,仅展示,从缓存数据取) |         // 1. 显示经纬度(不可编辑,仅展示) | ||||||
|         holder.tvEditLon.setText(String.format("经度:%.6f", posModel.getLongitude())); |         holder.tvEditLon.setText(String.format("经度:%.6f", posModel.getLongitude())); | ||||||
|         holder.tvEditLat.setText(String.format("纬度:%.6f", posModel.getLatitude())); |         holder.tvEditLat.setText(String.format("纬度:%.6f", posModel.getLatitude())); | ||||||
|         // 2. 显示备注(编辑框赋值,光标定位到末尾,方便用户直接编辑) |  | ||||||
|  |         // 2. 显示备注(编辑框赋值,光标定位到末尾) | ||||||
|         String memo = posModel.getMemo(); |         String memo = posModel.getMemo(); | ||||||
|         if (memo != null && !memo.isEmpty()) { |         if (!TextUtils.isEmpty(memo)) { | ||||||
|             holder.etEditMemo.setText(memo); |             holder.etEditMemo.setText(memo); | ||||||
|             holder.etEditMemo.setSelection(memo.length()); |             holder.etEditMemo.setSelection(memo.length()); // 光标定位到文本末尾 | ||||||
|         } else { |         } else { | ||||||
|             holder.etEditMemo.setText(""); // 无备注时清空编辑框 |             holder.etEditMemo.setText(""); // 无备注时清空编辑框 | ||||||
|         } |         } | ||||||
|         // 3. 显示实时距离(与简单视图逻辑一致,从缓存数据取数) |  | ||||||
|         updateDistanceDisplay(holder.tvEditDistance, posModel); |  | ||||||
|         // 4. 设置距离开关状态(匹配缓存数据中的启用状态,确保UI与数据一致) |  | ||||||
|         holder.rgDistanceSwitch.check(posModel.isEnableRealPositionDistance()  |  | ||||||
| 									  ? R.id.rb_distance_enable : R.id.rb_distance_disable); |  | ||||||
|  |  | ||||||
|         // 5. 取消编辑→切换回简单视图(修改缓存状态,刷新UI,隐藏软键盘) |         // 3. 显示实时距离(与简单视图逻辑一致) | ||||||
|  |         updateDistanceDisplay(holder.tvEditDistance, posModel); | ||||||
|  |  | ||||||
|  |         // 4. 设置距离开关状态(匹配位置缓存中的启用状态) | ||||||
|  |         holder.rgDistanceSwitch.check(posModel.isEnableRealPositionDistance() | ||||||
|  | 									  ? R.id.rb_distance_enable | ||||||
|  | 									  : R.id.rb_distance_disable); | ||||||
|  |  | ||||||
|  |         // 5. 取消编辑:切换回简单视图(Java 7 匿名内部类) | ||||||
|         holder.btnCancel.setOnClickListener(new View.OnClickListener() { |         holder.btnCancel.setOnClickListener(new View.OnClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public void onClick(View v) { | 				public void onClick(View v) { | ||||||
| 					posModel.setIsSimpleView(true); | 					posModel.setIsSimpleView(true); | ||||||
| 					notifyItemChanged(position); | 					notifyItemChanged(position); | ||||||
| 					hideSoftKeyboard(v); | 					hideSoftKeyboard(v); // 隐藏软键盘(提升用户体验) | ||||||
|  | 					LogUtils.d(TAG, "取消编辑:位置ID=" + posId + ",切换回简单视图"); | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
|         // 6. 删除位置→回调Activity处理(Adapter不直接删数据,由Activity操作缓存+服务) |         // 6. 删除位置:回调Activity处理(Adapter不直接删数据,由Activity同步MainService) | ||||||
|         holder.btnDelete.setOnClickListener(new View.OnClickListener() { |         holder.btnDelete.setOnClickListener(new View.OnClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public void onClick(View v) { | 				public void onClick(View v) { | ||||||
| @@ -242,10 +233,11 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
| 						mOnDeleteListener.onDeleteClick(position); // 通知Activity删除指定索引 | 						mOnDeleteListener.onDeleteClick(position); // 通知Activity删除指定索引 | ||||||
| 					} | 					} | ||||||
| 					hideSoftKeyboard(v); | 					hideSoftKeyboard(v); | ||||||
|  | 					LogUtils.d(TAG, "触发删除:通知Activity处理位置ID=" + posId + "的删除逻辑"); | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
|         // 7. 保存位置→回调Activity保存(收集编辑后的参数,传更新后的模型) |         // 7. 保存位置:回调Activity保存(收集参数→构建更新模型→通知Activity) | ||||||
|         holder.btnSave.setOnClickListener(new View.OnClickListener() { |         holder.btnSave.setOnClickListener(new View.OnClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public void onClick(View v) { | 				public void onClick(View v) { | ||||||
| @@ -262,298 +254,303 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
| 					updatedPos.setIsEnableRealPositionDistance(isDistanceEnable); // 更新距离状态 | 					updatedPos.setIsEnableRealPositionDistance(isDistanceEnable); // 更新距离状态 | ||||||
| 					updatedPos.setIsSimpleView(true);                 // 切换回简单视图 | 					updatedPos.setIsSimpleView(true);                 // 切换回简单视图 | ||||||
|  |  | ||||||
| 					// 回调Activity保存(由Activity同步缓存数据+服务数据,Adapter不处理逻辑) | 					// 回调Activity保存(由Activity同步MainService+位置缓存,Adapter不处理逻辑) | ||||||
| 					if (mOnSavePosListener != null) { | 					if (mOnSavePosListener != null) { | ||||||
| 						mOnSavePosListener.onSavePositionClick(position, updatedPos); | 						mOnSavePosListener.onSavePositionClick(position, updatedPos); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					// 本地同步状态(避免刷新延迟,直接修改Activity传入的缓存数据) | 					// 本地同步状态(避免刷新延迟,直接修改位置缓存) | ||||||
| 					posModel.setMemo(newMemo); | 					posModel.setMemo(newMemo); | ||||||
| 					posModel.setIsEnableRealPositionDistance(isDistanceEnable); | 					posModel.setIsEnableRealPositionDistance(isDistanceEnable); | ||||||
| 					posModel.setIsSimpleView(true); | 					posModel.setIsSimpleView(true); | ||||||
| 					notifyItemChanged(position); // 刷新当前项,显示更新后的状态 | 					notifyItemChanged(position); // 刷新当前项,显示更新后的状态 | ||||||
| 					hideSoftKeyboard(v); | 					hideSoftKeyboard(v); | ||||||
| 					LogUtils.d(TAG, "触发位置保存:ID=" + posId + ",新备注=" + newMemo); | 					LogUtils.d(TAG, "触发保存:位置ID=" + posId + ",新备注=" + newMemo + ",距离启用=" + isDistanceEnable); | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
|         // 8. 绑定任务相关视图(显示任务数量+新增任务,基于Activity任务缓存) |         // 8. 绑定任务视图(显示任务数量+新增任务,数据从MainService获取) | ||||||
|         bindTaskView(holder, posId); |         bindTaskView(holder, posId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 绑定任务视图(编辑模式专属:显示当前位置的任务数量+新增任务) |      * 绑定任务视图(编辑模式专属:从MainService获取任务数据,新增任务调用服务接口) | ||||||
|      */ |      */ | ||||||
|     private void bindTaskView(final EditViewHolder holder, final String posId) { |     private void bindTaskView(final EditViewHolder holder, final String posId) { | ||||||
|         // 从本地映射获取当前位置的任务(基于Activity缓存数据构建,避免重复遍历) |         // 1. 从MainService获取当前位置的任务数量(Java 7 迭代器遍历服务数据) | ||||||
|         ArrayList<PositionTaskModel> posTasks = mPosToTasksMap.get(posId); |         int taskCount = 0; | ||||||
|         if (posTasks == null) { |         MainService mainService = mMainServiceRef.get(); | ||||||
|             posTasks = new ArrayList<PositionTaskModel>(); |         if (mainService != null) { | ||||||
|  |             ArrayList<PositionTaskModel> posTasks = mainService.getTasksByPositionId(posId); | ||||||
|  |             taskCount = (posTasks != null) ? posTasks.size() : 0; | ||||||
|         } |         } | ||||||
|  |         // 显示任务数量(简化设计,实际可扩展为任务列表) | ||||||
|  |         holder.tvTaskCount.setText("任务数量:" + taskCount); | ||||||
|  |  | ||||||
|         // 显示当前位置的任务数量(简化设计,实际项目可替换为任务列表RecyclerView) |         // 2. 新增任务:调用MainService接口(不操作本地缓存,数据直接写入服务) | ||||||
|         holder.tvTaskCount.setText("任务数量:" + posTasks.size()); |  | ||||||
|  |  | ||||||
|         // 新增任务→回调Activity处理(Adapter不直接操作任务数据,由Activity同步) |  | ||||||
|         holder.btnAddTask.setOnClickListener(new View.OnClickListener() { |         holder.btnAddTask.setOnClickListener(new View.OnClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public void onClick(View v) { | 				public void onClick(View v) { | ||||||
| 					// 创建默认任务模型(生成唯一ID,关联当前位置) | 					MainService mainService = mMainServiceRef.get(); | ||||||
|  | 					if (mainService == null) { | ||||||
|  | 						LogUtils.e(TAG, "新增任务失败:MainService已回收(弱引用失效)"); | ||||||
|  | 						return; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// 构建默认任务模型(Java 7 显式初始化) | ||||||
| 					PositionTaskModel newTask = new PositionTaskModel(); | 					PositionTaskModel newTask = new PositionTaskModel(); | ||||||
| 					newTask.setTaskId(PositionTaskModel.genTaskId()); // 需在PositionTaskModel实现静态生成ID方法 | 					newTask.setTaskId(PositionTaskModel.genTaskId()); // 生成唯一任务ID(需在PositionTaskModel实现静态方法) | ||||||
| 					newTask.setPositionId(posId);                     // 关联当前位置ID(确保任务归属正确) | 					newTask.setPositionId(posId);                     // 绑定当前位置ID | ||||||
| 					newTask.setTaskDescription(DEFAULT_TASK_DESC);    // 默认任务描述 | 					newTask.setTaskDescription(DEFAULT_TASK_DESC);    // 默认任务描述 | ||||||
| 					newTask.setIsEnable(true);                        // 默认启用任务 | 					newTask.setIsEnable(true);                        // 默认启用任务 | ||||||
| 					newTask.setDiscussDistance(DEFAULT_TASK_DISTANCE);// 默认任务距离(50米) | 					newTask.setDiscussDistance(DEFAULT_TASK_DISTANCE);// 默认任务距离(50米) | ||||||
|  |  | ||||||
| 					// 回调Activity保存任务(由Activity添加到缓存+同步服务,Adapter仅通知) | 					// 调用MainService接口新增任务(数据写入服务,由服务处理持久化+通知刷新) | ||||||
| 					if (mOnSaveTaskListener != null) { | 					mainService.addPositionTask(newTask); | ||||||
| 						mOnSaveTaskListener.onSavePositionTaskClick(newTask); | 					hideSoftKeyboard(v); | ||||||
| 					} | 					LogUtils.d(TAG, "触发新增任务:调用MainService接口,位置ID=" + posId + ",任务ID=" + newTask.getTaskId()); | ||||||
|  |  | ||||||
| 					// 本地同步任务数量(避免刷新延迟,更新本地映射+UI) |  | ||||||
| 					ArrayList<PositionTaskModel> updatedTasks = mPosToTasksMap.get(posId); |  | ||||||
| 					if (updatedTasks == null) { |  | ||||||
| 						updatedTasks = new ArrayList<PositionTaskModel>(); |  | ||||||
| 					} |  | ||||||
| 					updatedTasks.add(newTask); |  | ||||||
| 					mPosToTasksMap.put(posId, updatedTasks); |  | ||||||
| 					holder.tvTaskCount.setText("任务数量:" + updatedTasks.size()); // 实时更新显示 |  | ||||||
| 					LogUtils.d(TAG, "触发任务新增:位置ID=" + posId + ",任务ID=" + newTask.getTaskId()); |  | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // ========================================================================= |     // ========================================================================= | ||||||
|     // 核心工具方法(无接口依赖,基于缓存数据实现,确保功能完整) |     // 工具方法(Java 7 风格:无Lambda,纯匿名内部类+迭代器) | ||||||
|     // ========================================================================= |     // ========================================================================= | ||||||
|     /** |     /** | ||||||
|      * 更新距离显示(根据位置模型状态,显示不同文本和颜色,无外部依赖) |      * 更新距离显示(根据位置模型状态,显示不同文本+颜色) | ||||||
|      */ |      */ | ||||||
|     private void updateDistanceDisplay(TextView distanceView, PositionModel posModel) { |     private void updateDistanceDisplay(TextView distanceView, PositionModel posModel) { | ||||||
|         if (distanceView == null || posModel == null) return; |         if (distanceView == null || posModel == null) { | ||||||
|  |             LogUtils.w(TAG, "updateDistanceDisplay:参数为空(控件/位置模型)"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // 场景1:距离计算未启用(从缓存数据状态判断) |         // 场景1:距离未启用 | ||||||
|         if (!posModel.isEnableRealPositionDistance()) { |         if (!posModel.isEnableRealPositionDistance()) { | ||||||
|             distanceView.setText(DISTANCE_DISABLED); |             distanceView.setText(DISTANCE_DISABLED); | ||||||
|             distanceView.setTextColor(mContext.getResources().getColor(R.color.gray)); |             distanceView.setTextColor(mContext.getResources().getColor(R.color.gray)); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 场景2:距离计算失败(用-1标记失败,从缓存数据取distance字段) |         // 场景2:距离计算失败(用-1标记失败状态) | ||||||
|         double distance = posModel.getRealPositionDistance(); |         double distance = posModel.getRealPositionDistance(); | ||||||
|         if (distance < 0) { |         if (distance < 0) { | ||||||
| 			distanceView.setText(DISTANCE_ERROR); |             distanceView.setText(DISTANCE_ERROR); | ||||||
| 			distanceView.setTextColor(mContext.getResources().getColor(R.color.red)); |             distanceView.setTextColor(mContext.getResources().getColor(R.color.red)); | ||||||
| 			return; |             return; | ||||||
| 		} |         } | ||||||
|  |  | ||||||
| // 场景3:正常显示距离(按距离范围设置颜色,提升视觉区分度) |         // 场景3:正常显示距离(按距离范围设置颜色,提升视觉区分度) | ||||||
| 		distanceView.setText(String.format(DISTANCE_FORMAT, distance)); |         distanceView.setText(String.format(DISTANCE_FORMAT, distance)); | ||||||
| 		if (distance <= 100) { // 近距离(≤100米):绿色 |         if (distance <= 100) { | ||||||
| 			distanceView.setTextColor(mContext.getResources().getColor(R.color.green)); |             distanceView.setTextColor(mContext.getResources().getColor(R.color.green)); // 近距离(≤100米) | ||||||
| 		} else if (distance <= 500) { // 中距离(≤500米):黄色 |         } else if (distance <= 500) { | ||||||
| 			distanceView.setTextColor(mContext.getResources().getColor(R.color.yellow)); |             distanceView.setTextColor(mContext.getResources().getColor(R.color.yellow));// 中距离(≤500米) | ||||||
| 		} else { // 远距离(>500米):红色 |         } else { | ||||||
| 			distanceView.setTextColor(mContext.getResources().getColor(R.color.red)); |             distanceView.setTextColor(mContext.getResources().getColor(R.color.red));   // 远距离(>500米) | ||||||
| 		} |         } | ||||||
| 	} |     } | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
|  |      * 根据索引获取位置模型(从位置缓存取数,容错处理) | ||||||
|  |      */ | ||||||
|  |     private PositionModel getPositionByIndex(int index) { | ||||||
|  |         if (mCachedPositionList == null || index < 0 || index >= mCachedPositionList.size()) { | ||||||
|  |             LogUtils.w(TAG, "getPositionByIndex:无效索引(" + index + ")或位置缓存为空"); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         return mCachedPositionList.get(index); | ||||||
|  |     } | ||||||
|  |  | ||||||
| 	 - 根据索引获取位置模型(从Activity缓存数据直接读取,无中间层,避免数据不一致) |     /** | ||||||
| 	 */ |      * 根据位置ID获取列表索引(用于精准刷新视图) | ||||||
| 	private PositionModel getPositionByIndex(int index) { |      */ | ||||||
| 		if (mCachedPositionList == null || index < 0 || index >= mCachedPositionList.size()) { |     private int getPositionIndexById(String positionId) { | ||||||
| 			LogUtils.w(TAG, "获取位置失败:索引无效(" + index + ")或缓存数据为空"); |         if (TextUtils.isEmpty(positionId) || mCachedPositionList == null || mCachedPositionList.isEmpty()) { | ||||||
| 			return null; |             LogUtils.w(TAG, "getPositionIndexById:参数无效(位置ID/缓存为空)"); | ||||||
| 		} |             return -1; | ||||||
| 		return mCachedPositionList.get(index); |         } | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/** |         // Java 7 增强for循环遍历(替代Lambda,适配Java 7语法) | ||||||
|  |         for (int i = 0; i < mCachedPositionList.size(); i++) { | ||||||
|  |             PositionModel pos = mCachedPositionList.get(i); | ||||||
|  |             if (positionId.equals(pos.getPositionId())) { | ||||||
|  |                 return i; // 找到匹配ID,返回索引 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         LogUtils.w(TAG, "getPositionIndexById:未找到位置ID=" + positionId); | ||||||
|  |         return -1; | ||||||
|  |     } | ||||||
|  |  | ||||||
| 	 - 根据位置ID获取列表索引(从Activity缓存数据遍历,用于视图切换后精准刷新) |     /** | ||||||
| 	 */ |      * 局部更新距离UI(仅更新指定位置的距离,避免全量刷新卡顿) | ||||||
| 	private int getPositionIndexById(String positionId) { |      */ | ||||||
| 		if (mCachedPositionList == null || positionId == null || positionId.isEmpty()) { |     public void updateSinglePositionDistance(String positionId) { | ||||||
| 			LogUtils.w(TAG, "获取位置索引失败:缓存数据为空或位置ID无效"); |         // 校验参数:位置ID无效或控件未缓存,直接返回 | ||||||
| 			return -1; |         if (TextUtils.isEmpty(positionId) || !mPosDistanceViewMap.containsKey(positionId)) { | ||||||
| 		} |             LogUtils.w(TAG, "updateSinglePositionDistance:位置ID无效或控件未缓存(ID=" + positionId + ")"); | ||||||
| 		for (int i = 0; i < mCachedPositionList.size(); i++) { |             return; | ||||||
| 			PositionModel pos = mCachedPositionList.get(i); |         } | ||||||
| 			if (positionId.equals(pos.getPositionId())) { |  | ||||||
| 				return i; // 找到匹配ID,返回对应索引 |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		LogUtils.w(TAG, "未找到位置ID:" + positionId + ",返回无效索引-1"); |  | ||||||
| 		return -1; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/** |         // 从MainService获取最新位置模型(确保距离值是服务端最新) | ||||||
|  |         PositionModel latestPos = null; | ||||||
|  |         MainService mainService = mMainServiceRef.get(); | ||||||
|  |         if (mainService != null) { | ||||||
|  |             ArrayList<PositionModel> servicePosList = mainService.getPositionList(); | ||||||
|  |             if (servicePosList != null && !servicePosList.isEmpty()) { | ||||||
|  |                 // Java 7 迭代器遍历服务端位置列表,找到目标位置 | ||||||
|  |                 Iterator<PositionModel> posIter = servicePosList.iterator(); | ||||||
|  |                 while (posIter.hasNext()) { | ||||||
|  |                     PositionModel pos = posIter.next(); | ||||||
|  |                     if (positionId.equals(pos.getPositionId())) { | ||||||
|  |                         latestPos = pos; | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
| 	 - 刷新“位置→任务”映射(基于Activity最新任务缓存重建,确保本地映射与Activity数据一致) |         // 用服务端最新距离更新UI(直接操作缓存的距离控件,无需刷新整个项) | ||||||
| 	 - (Activity任务数据更新后调用,避免本地映射滞后) |         if (latestPos != null) { | ||||||
| 	 */ |             TextView distanceView = mPosDistanceViewMap.get(positionId); | ||||||
| 	public void refreshPositionTaskMap() { |             updateDistanceDisplay(distanceView, latestPos); | ||||||
| 		if (mCachedTaskList == null) { |             LogUtils.d(TAG, "局部更新距离完成:位置ID=" + positionId + ",最新距离=" + latestPos.getRealPositionDistance() + "米"); | ||||||
| 			mPosToTasksMap.clear(); // 任务缓存为空时,清空本地映射 |         } else { | ||||||
| 			LogUtils.d(TAG, "刷新任务映射:Activity任务缓存为空,已清空本地映射"); |             LogUtils.w(TAG, "局部更新距离失败:未在MainService找到位置ID=" + positionId); | ||||||
| 			return; |         } | ||||||
| 		}try { |     } | ||||||
| 			mPosToTasksMap.clear(); // 先清空旧映射,避免数据残留 |  | ||||||
| 			Iterator taskIter = mCachedTaskList.iterator(); |  | ||||||
| 			while (taskIter.hasNext()) { |  | ||||||
| 				PositionTaskModel task = (PositionTaskModel)taskIter.next(); |  | ||||||
| 				String posId = task.getPositionId(); |  | ||||||
| // 按位置ID分组:同一位置的任务放入同一个列表 |  | ||||||
| 				if (!mPosToTasksMap.containsKey(posId)) { |  | ||||||
| 					mPosToTasksMap.put(posId, new ArrayList()); |  | ||||||
| 				} |  | ||||||
| 				mPosToTasksMap.get(posId).add(task); |  | ||||||
| 			} |  | ||||||
| 			LogUtils.d(TAG, "刷新任务映射完成:已分组" + mPosToTasksMap.size() + "个位置的任务"); |  | ||||||
| 		} catch (ConcurrentModificationException e) { |  | ||||||
| 			LogUtils.d(TAG, "刷新任务映射失败:并发修改Activity任务缓存。" + e); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
|  |      * 全量更新位置数据(从MainService同步最新位置列表,刷新UI) | ||||||
|  |      */ | ||||||
|  |     public void updateAllPositionData(ArrayList<PositionModel> newPosList) { | ||||||
|  |         if (newPosList == null) { | ||||||
|  |             LogUtils.w(TAG, "updateAllPositionData:新位置列表为空,跳过更新"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
| 	 - 全量更新数据(接收Activity最新缓存数据,同步本地数据源+辅助缓存,刷新UI) |         // 同步服务端最新位置数据到本地缓存 | ||||||
| 	 - (Activity新增/删除位置/任务后调用,确保Adapter与Activity数据100%一致) |         this.mCachedPositionList.clear(); | ||||||
| 	 */ |         this.mCachedPositionList.addAll(newPosList); | ||||||
| 	public void updateAllData(ArrayList newPosList, ArrayList newTaskList) { |         // 清空旧距离控件缓存(避免引用失效控件) | ||||||
| // 同步Activity最新缓存数据(容错处理:避免接收null导致空指针) |         mPosDistanceViewMap.clear(); | ||||||
| 		this.mCachedPositionList = (newPosList != null) ? newPosList : new ArrayList(); |         // 通知RecyclerView全量刷新UI | ||||||
| 		this.mCachedTaskList = (newTaskList != null) ? newTaskList : new ArrayList();// 刷新本地辅助缓存(确保映射与最新数据同步) |         notifyDataSetChanged(); | ||||||
| 		refreshPositionTaskMap(); |         LogUtils.d(TAG, "全量更新位置数据完成:当前位置数量=" + mCachedPositionList.size() + "(数据来源:MainService)"); | ||||||
| 		mPosDistanceViewMap.clear(); // 清空旧距离控件缓存,避免引用失效控件// 通知RecyclerView全量刷新(数据已同步,更新UI显示) |     } | ||||||
| 		notifyDataSetChanged(); |  | ||||||
| 		LogUtils.d(TAG, "全量更新数据完成:当前位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size()); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
|  |      * 隐藏软键盘(编辑完成后调用,提升用户体验) | ||||||
|  |      */ | ||||||
|  |     private void hideSoftKeyboard(View view) { | ||||||
|  |         if (mContext == null || view == null) { | ||||||
|  |             LogUtils.w(TAG, "hideSoftKeyboard:参数为空(上下文/视图),无法隐藏键盘"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
| 	 - 局部更新距离UI(仅更新指定位置的距离显示,避免全量刷新卡顿) |         // Java 7 显式获取输入法服务,避免Lambda | ||||||
| 	 - (Activity同步服务计算的最新距离后调用,精准更新受影响的位置) |         InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); | ||||||
| 	 */ |         if (imm != null) { | ||||||
| 	public void updateSinglePositionDistance(String positionId) { |             imm.hideSoftInputFromWindow(view.getWindowToken(), 0); // 强制隐藏软键盘 | ||||||
| // 校验参数:位置ID无效或控件未缓存时,直接返回 |         } | ||||||
| 		if (positionId == null || positionId.isEmpty() || !mPosDistanceViewMap.containsKey(positionId)) { |     } | ||||||
| 			LogUtils.w(TAG, "局部更新距离失败:位置ID无效或距离控件未缓存(ID=" + positionId + ")"); |  | ||||||
| 			return; |  | ||||||
| 		}// 从Activity缓存数据中获取最新位置模型(确保距离值是最新的) |  | ||||||
| 		PositionModel latestPos = null; |  | ||||||
| 		for (PositionModel pos : mCachedPositionList) { |  | ||||||
| 			if (positionId.equals(pos.getPositionId())) { |  | ||||||
| 				latestPos = pos; |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
| 		}// 用最新距离值更新UI(直接操作缓存的距离控件,无需刷新整个项) |  | ||||||
| 		if (latestPos != null) { |  | ||||||
| 			updateDistanceDisplay(mPosDistanceViewMap.get(positionId), latestPos); |  | ||||||
| 			LogUtils.d(TAG, "局部更新距离完成:位置ID=" + positionId + ",最新距离=" + latestPos.getRealPositionDistance() + "米"); |  | ||||||
| 		} else { |  | ||||||
| 			LogUtils.w(TAG, "局部更新距离失败:未在Activity缓存中找到位置ID=" + positionId); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/** |     // ========================================================================= | ||||||
|  |     // 实现 MainService.TaskUpdateListener 接口(服务任务变化时回调) | ||||||
|  |     // ========================================================================= | ||||||
|  |     @Override | ||||||
|  |     public void onTaskUpdated() { | ||||||
|  |         LogUtils.d(TAG, "收到MainService任务更新通知(任务新增/删除/状态变化),刷新UI"); | ||||||
|  |         // 任务数据变化时,全量刷新Adapter(确保任务数量等显示同步) | ||||||
|  |         notifyDataSetChanged(); | ||||||
|  |     } | ||||||
|  |  | ||||||
| 	 - 隐藏软键盘(编辑完成后调用,提升用户体验,避免键盘遮挡) |     // ========================================================================= | ||||||
| 	 */ |     // 回调设置方法(供LocationActivity调用,绑定交互逻辑) | ||||||
| 	private void hideSoftKeyboard(View view) { |     // ========================================================================= | ||||||
| 		if (mContext == null || view == null) return; |     public void setOnDeleteClickListener(OnDeleteClickListener listener) { | ||||||
| 		InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); |         this.mOnDeleteListener = listener; | ||||||
| 		if (imm != null) { |     } | ||||||
| 			imm.hideSoftInputFromWindow(view.getWindowToken(), 0); // 强制隐藏软键盘 |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/** |     public void setOnSavePositionClickListener(OnSavePositionClickListener listener) { | ||||||
|  |         this.mOnSavePosListener = listener; | ||||||
|  |     } | ||||||
|  |  | ||||||
| 	 - 释放资源(Activity销毁时调用,彻底避免内存泄漏) |     // ========================================================================= | ||||||
| 	 */ |     // 资源释放(Activity销毁时调用,避免内存泄漏) | ||||||
| 	public void release() { |     // ========================================================================= | ||||||
| // 清空本地辅助缓存(解除控件、数据映射引用) |     public void release() { | ||||||
| 		mPosToTasksMap.clear(); |         // 1. 反注册MainService任务监听(解除与服务的绑定,避免内存泄漏) | ||||||
| 		mPosDistanceViewMap.clear(); |         MainService mainService = mMainServiceRef.get(); | ||||||
| // 置空回调实例(避免持有Activity引用导致内存泄漏) |         if (mainService != null) { | ||||||
| 		mOnDeleteListener = null; |             mainService.unregisterTaskUpdateListener(this); | ||||||
| 		mOnSavePosListener = null; |             LogUtils.d(TAG, "已反注册MainService任务监听,避免内存泄漏"); | ||||||
| 		mOnSaveTaskListener = null; |         } | ||||||
| // 置空数据源引用(帮助GC回收,避免残留数据占用内存) |  | ||||||
| 		mCachedPositionList = null; |  | ||||||
| 		mCachedTaskList = null; |  | ||||||
| 		LogUtils.d(TAG, "Adapter资源已完全释放"); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| // ========================================================================= |         // 2. 清空本地缓存(解除控件/数据引用,帮助GC回收) | ||||||
| // 回调设置方法(LocationActivity调用,绑定交互逻辑,无冗余参数) |         mPosDistanceViewMap.clear(); | ||||||
| // ========================================================================= |         if (mCachedPositionList != null) { | ||||||
| 	public void setOnDeleteClickListener(OnDeleteClickListener listener) { |             mCachedPositionList.clear(); | ||||||
| 		this.mOnDeleteListener = listener; |         } | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	public void setOnSavePositionClickListener(OnSavePositionClickListener listener) { |         // 3. 置空回调实例(避免持有Activity引用导致内存泄漏) | ||||||
| 		this.mOnSavePosListener = listener; |         mOnDeleteListener = null; | ||||||
| 	} |         mOnSavePosListener = null; | ||||||
|  |  | ||||||
| 	public void setOnSavePositionTaskClickListener(OnSavePositionTaskClickListener listener) { |         LogUtils.d(TAG, "Adapter资源已完全释放(缓存清空+监听反注册)"); | ||||||
| 		this.mOnSaveTaskListener = listener; |     } | ||||||
| 	} |  | ||||||
|  |  | ||||||
| // ========================================================================= |     // ========================================================================= | ||||||
| // 视图Holder类(静态内部类,不持有外部引用,彻底避免内存泄漏) |     // 静态内部类:视图Holder(Java 7 静态内部类,不持有外部引用,避免内存泄漏) | ||||||
| // ========================================================================= |     // ========================================================================= | ||||||
| 	/** |     /** | ||||||
|  |      * 简单视图Holder(仅显示数据,对应布局:item_position_simple.xml) | ||||||
|  |      */ | ||||||
|  |     public static class SimpleViewHolder extends RecyclerView.ViewHolder { | ||||||
|  |         TextView tvSimpleLon;     // 经度显示控件 | ||||||
|  |         TextView tvSimpleLat;     // 纬度显示控件 | ||||||
|  |         TextView tvSimpleMemo;    // 备注显示控件 | ||||||
|  |         TextView tvSimpleDistance;// 实时距离显示控件 | ||||||
|  |  | ||||||
| 	 - 简单视图Holder(仅显示数据,对应布局:item_position_simple.xml) |         public SimpleViewHolder(View itemView) { | ||||||
| 	 */ |             super(itemView); | ||||||
| 	public static class SimpleViewHolder extends RecyclerView.ViewHolder { |             // 绑定布局控件(与XML中ID严格对应,避免运行时空指针) | ||||||
| 		TextView tvSimpleLon;     // 经度显示控件 |             tvSimpleLon = (TextView) itemView.findViewById(R.id.tv_simple_longitude); | ||||||
| 		TextView tvSimpleLat;     // 纬度显示控件 |             tvSimpleLat = (TextView) itemView.findViewById(R.id.tv_simple_latitude); | ||||||
| 		TextView tvSimpleMemo;    // 备注显示控件 |             tvSimpleMemo = (TextView) itemView.findViewById(R.id.tv_simple_memo); | ||||||
| 		TextView tvSimpleDistance;// 实时距离显示控件 |             tvSimpleDistance = (TextView) itemView.findViewById(R.id.tv_simple_distance); | ||||||
| 		public SimpleViewHolder(View itemView) { |         } | ||||||
| 		super(itemView); |     } | ||||||
| // 绑定布局控件(与XML中ID严格对应,避免运行时空指针) |  | ||||||
| 		tvSimpleLon = (TextView) itemView.findViewById(R.id.tv_simple_longitude); |  | ||||||
| 		tvSimpleLat = (TextView) itemView.findViewById(R.id.tv_simple_latitude); |  | ||||||
| 		tvSimpleMemo = (TextView) itemView.findViewById(R.id.tv_simple_memo); |  | ||||||
| 		tvSimpleDistance = (TextView) itemView.findViewById(R.id.tv_simple_distance); |  | ||||||
| 	} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
|  |      * 编辑视图Holder(含编辑控件+功能按钮,对应布局:item_position_edit.xml) | ||||||
|  |      */ | ||||||
|  |     public static class EditViewHolder extends RecyclerView.ViewHolder { | ||||||
|  |         TextView tvEditLon;       // 经度显示控件(不可编辑) | ||||||
|  |         TextView tvEditLat;       // 纬度显示控件(不可编辑) | ||||||
|  |         EditText etEditMemo;      // 备注编辑控件 | ||||||
|  |         TextView tvEditDistance;  // 实时距离显示控件 | ||||||
|  |         RadioGroup rgDistanceSwitch; // 距离启用/禁用开关组 | ||||||
|  |         Button btnCancel;         // 取消编辑按钮 | ||||||
|  |         Button btnDelete;         // 删除位置按钮 | ||||||
|  |         Button btnSave;           // 保存位置按钮 | ||||||
|  |         Button btnAddTask;        // 新增任务按钮 | ||||||
|  |         TextView tvTaskCount;     // 任务数量显示控件 | ||||||
|  |  | ||||||
| 	 - 编辑视图Holder(含编辑控件+功能按钮,对应布局:item_position_edit.xml) |         public EditViewHolder(View itemView) { | ||||||
| 	 */ |             super(itemView); | ||||||
| 	public static class EditViewHolder extends RecyclerView.ViewHolder { |             // 绑定布局控件(与XML中ID严格对应,避免运行时空指针) | ||||||
| 		TextView tvEditLon;       // 经度显示控件(不可编辑) |             tvEditLon = (TextView) itemView.findViewById(R.id.tv_edit_longitude); | ||||||
| 		TextView tvEditLat;       // 纬度显示控件(不可编辑) |             tvEditLat = (TextView) itemView.findViewById(R.id.tv_edit_latitude); | ||||||
| 		EditText etEditMemo;      // 备注编辑控件 |             etEditMemo = (EditText) itemView.findViewById(R.id.et_edit_memo); | ||||||
| 		TextView tvEditDistance;  // 实时距离显示控件 |             tvEditDistance = (TextView) itemView.findViewById(R.id.tv_edit_distance); | ||||||
| 		RadioGroup rgDistanceSwitch; // 距离启用/禁用开关组 |             rgDistanceSwitch = (RadioGroup) itemView.findViewById(R.id.rg_distance_switch); | ||||||
| 		Button btnCancel;         // 取消编辑按钮 |             btnCancel = (Button) itemView.findViewById(R.id.btn_edit_cancel); | ||||||
| 		Button btnDelete;         // 删除位置按钮 |             btnDelete = (Button) itemView.findViewById(R.id.btn_edit_delete); | ||||||
| 		Button btnSave;           // 保存位置按钮 |             btnSave = (Button) itemView.findViewById(R.id.btn_edit_save); | ||||||
| 		Button btnAddTask;        // 新增任务按钮 |             btnAddTask = (Button) itemView.findViewById(R.id.btn_add_task); | ||||||
| 		TextView tvTaskCount;     // 任务数量显示控件(简化设计) |             tvTaskCount = (TextView) itemView.findViewById(R.id.tv_task_count); | ||||||
| 		public EditViewHolder(View itemView) { |         } | ||||||
| 		super(itemView); |     } | ||||||
| // 绑定布局控件(与XML中ID严格对应,避免运行时空指针) |  | ||||||
| 		tvEditLon = (TextView) itemView.findViewById(R.id.tv_edit_longitude); |  | ||||||
| 		tvEditLat = (TextView) itemView.findViewById(R.id.tv_edit_latitude); |  | ||||||
| 		etEditMemo = (EditText) itemView.findViewById(R.id.et_edit_memo); |  | ||||||
| 		tvEditDistance = (TextView) itemView.findViewById(R.id.tv_edit_distance); |  | ||||||
| 		rgDistanceSwitch = (RadioGroup) itemView.findViewById(R.id.rg_distance_switch); |  | ||||||
| 		btnCancel = (Button) itemView.findViewById(R.id.btn_edit_cancel); |  | ||||||
| 		btnDelete = (Button) itemView.findViewById(R.id.btn_edit_delete); |  | ||||||
| 		btnSave = (Button) itemView.findViewById(R.id.btn_edit_save); |  | ||||||
| 		btnAddTask = (Button) itemView.findViewById(R.id.btn_add_task); |  | ||||||
| 		tvTaskCount = (TextView) itemView.findViewById(R.id.tv_task_count); |  | ||||||
| 	} |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,71 +1,67 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | <RelativeLayout | ||||||
|     android:layout_width="match_parent" | 	xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     android:layout_height="match_parent" | 	android:layout_width="match_parent" | ||||||
|     android:padding="20dp"> | 	android:layout_height="match_parent" | ||||||
|  | 	android:padding="20dp"> | ||||||
|  |  | ||||||
|     <!-- 1. 经纬度显示区域(保持居中,上方) --> | 	<LinearLayout | ||||||
|     <LinearLayout | 		android:id="@+id/layout_location_info" | ||||||
|         android:id="@+id/layout_location_info" | 		android:layout_width="wrap_content" | ||||||
|         android:layout_width="wrap_content" | 		android:layout_height="wrap_content" | ||||||
|         android:layout_height="wrap_content" | 		android:layout_centerHorizontal="true" | ||||||
|         android:layout_centerHorizontal="true" | 		android:layout_marginTop="30dp" | ||||||
|         android:layout_marginTop="30dp" | 		android:orientation="vertical" | ||||||
|         android:orientation="vertical" | 		android:gravity="center_horizontal"> | ||||||
|         android:gravity="center_horizontal"> |  | ||||||
|  |  | ||||||
|         <!-- 标题 --> | 		<TextView | ||||||
|         <TextView | 			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="22sp" | ||||||
|             android:textSize="22sp" | 			android:textStyle="bold"/> | ||||||
|             android:textStyle="bold"/> |  | ||||||
|  |  | ||||||
|         <!-- 经度显示 --> | 		<TextView | ||||||
|         <TextView | 			android:id="@+id/tv_longitude" | ||||||
|             android:id="@+id/tv_longitude" | 			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="18sp" | ||||||
|             android:textSize="18sp" | 			android:layout_marginTop="15dp"/> | ||||||
|             android:layout_marginTop="15dp"/> |  | ||||||
|  |  | ||||||
|         <!-- 纬度显示 --> | 		<TextView | ||||||
|         <TextView | 			android:id="@+id/tv_latitude" | ||||||
|             android:id="@+id/tv_latitude" | 			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="18sp" | ||||||
|             android:textSize="18sp" | 			android:layout_marginTop="10dp"/> | ||||||
|             android:layout_marginTop="10dp"/> |  | ||||||
|  |  | ||||||
|     </LinearLayout> | 	</LinearLayout> | ||||||
|  |  | ||||||
|     <!-- 2. 新增:位置列表(RecyclerView)- 位于经纬度下方,悬浮按钮上方 --> | 	<androidx.recyclerview.widget.RecyclerView | ||||||
|     <androidx.recyclerview.widget.RecyclerView | 		android:id="@+id/rv_position_list" | ||||||
|         android:id="@+id/rv_position_list" | 		android:layout_width="match_parent" | ||||||
|         android:layout_width="match_parent" | 		android:layout_height="match_parent" | ||||||
|         android:layout_height="match_parent" | 		android:layout_below="@id/layout_location_info" | ||||||
|         android:layout_below="@id/layout_location_info" | 		android:layout_above="@id/fab_p_button" | ||||||
|         android:layout_above="@id/fab_p_button" | 		android:layout_marginTop="20dp" | ||||||
|         android:layout_marginTop="20dp" | 		android:paddingBottom="10dp"/> | ||||||
|         android:paddingBottom="10dp"/> |  | ||||||
|  |  | ||||||
|     <!-- 3. 右下角圆形悬浮按钮(不变) --> | 	<Button | ||||||
|     <Button | 		android:id="@+id/fab_p_button" | ||||||
|         android:id="@+id/fab_p_button" | 		android:layout_width="60dp" | ||||||
|         android:layout_width="60dp" | 		android:layout_height="60dp" | ||||||
|         android:layout_height="60dp" | 		android:layout_alignParentBottom="true" | ||||||
|         android:layout_alignParentBottom="true" | 		android:layout_alignParentRight="true" | ||||||
|         android:layout_alignParentRight="true" | 		android:layout_margin="20dp" | ||||||
|         android:layout_margin="20dp" | 		android:background="@drawable/circle_button_bg" | ||||||
|         android:background="@drawable/circle_button_bg" | 		android:text="P" | ||||||
|         android:text="P" | 		android:textColor="@android:color/white" | ||||||
|         android:textColor="@android:color/white" | 		android:textSize="24sp" | ||||||
|         android:textSize="24sp" | 		android:elevation="6dp" | ||||||
|         android:elevation="6dp" | 		android:padding="0dp" | ||||||
|         android:padding="0dp"/> | 		android:onClick="addNewPosition"/> | ||||||
|  |  | ||||||
| </RelativeLayout> | </RelativeLayout> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ | |||||||
| 	<Switch | 	<Switch | ||||||
| 		android:id="@+id/switch_service_control" | 		android:id="@+id/switch_service_control" | ||||||
| 		android:layout_margin="16dp" | 		android:layout_margin="16dp" | ||||||
| 		android:text="主要服务开关" | 		android:text="GPS服务开关" | ||||||
| 		android:layout_width="match_parent" | 		android:layout_width="match_parent" | ||||||
| 		android:layout_height="wrap_content"/> | 		android:layout_height="wrap_content"/> | ||||||
|  |  | ||||||
| @@ -25,7 +25,7 @@ | |||||||
| 		android:layout_height="wrap_content" | 		android:layout_height="wrap_content" | ||||||
| 		android:layout_margin="16dp" | 		android:layout_margin="16dp" | ||||||
| 		android:onClick="onPositions" | 		android:onClick="onPositions" | ||||||
| 		android:text="进入位置管理" | 		android:text="位置与任务管理" | ||||||
| 		android:id="@+id/btn_manage_positions"/> | 		android:id="@+id/btn_manage_positions"/> | ||||||
|  |  | ||||||
| 	<Button | 	<Button | ||||||
| @@ -33,7 +33,7 @@ | |||||||
| 		android:layout_height="wrap_content" | 		android:layout_height="wrap_content" | ||||||
| 		android:layout_margin="16dp" | 		android:layout_margin="16dp" | ||||||
| 		android:onClick="onLog" | 		android:onClick="onLog" | ||||||
| 		android:text="查看操作日志"/> | 		android:text="查看应用日志"/> | ||||||
|  |  | ||||||
| </LinearLayout> | </LinearLayout> | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user