20251002_212237_535
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Thu Oct 02 08:02:02 GMT 2025
|
||||
#Thu Oct 02 13:16:14 GMT 2025
|
||||
stageCount=8
|
||||
libraryProject=
|
||||
baseVersion=15.0
|
||||
publishVersion=15.0.7
|
||||
buildCount=27
|
||||
buildCount=29
|
||||
baseBetaVersion=15.0.8
|
||||
|
||||
@@ -5,495 +5,223 @@ package cc.winboll.studio.positions.activities;
|
||||
* @Date 2025/09/29 18:22
|
||||
* @Describe 位置列表页面(适配MainService GPS接口+规范服务交互+完善生命周期)
|
||||
*/
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.os.IBinder;
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.widget.Toast;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.positions.R;
|
||||
import cc.winboll.studio.positions.adapters.PositionAdapter;
|
||||
import cc.winboll.studio.positions.models.PositionModel;
|
||||
import cc.winboll.studio.positions.models.PositionTaskModel;
|
||||
import cc.winboll.studio.positions.services.MainService;
|
||||
import cc.winboll.studio.positions.R;
|
||||
import java.util.ArrayList;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.positions.utils.AppConfigsUtil;
|
||||
|
||||
/**
|
||||
* 核心调整说明:
|
||||
* 1. 新增 MainService.GpsUpdateListener 实现,接收实时GPS数据(经纬度+状态)
|
||||
* 2. 完善 MainService 引用逻辑:修复实例获取可靠性(启动+延迟初始化)、补全反注册
|
||||
* 3. 新增GPS数据应用:同步GPS到服务、刷新位置距离、显示GPS状态
|
||||
* 4. 强化生命周期管理:页面销毁时反注册GPS监听,避免内存泄漏
|
||||
* Java 7 语法适配:
|
||||
* 1. 服务绑定用匿名内部类实现 ServiceConnection
|
||||
* 2. Adapter 初始化传入 MainService 实例,确保数据来源唯一
|
||||
* 3. 所有位置/任务操作通过 MainService 接口执行
|
||||
*/
|
||||
public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
public static final String TAG = "LocationActivity";
|
||||
public class LocationActivity extends Activity {
|
||||
private static final String TAG = "LocationActivity";
|
||||
|
||||
// SP配置常量(判断服务是否运行)
|
||||
private static final String SP_SERVICE_CONFIG = "service_config";
|
||||
private static final String KEY_SERVICE_RUNNING = "is_service_running";
|
||||
// 页面核心控件与变量
|
||||
private RecyclerView mRecyclerView;
|
||||
private PositionAdapter mAdapter;
|
||||
// 直连服务实例(通过单例获取,全局唯一)
|
||||
private MainService mMainService;
|
||||
// 缓存服务数据(从服务PUBLIC方法获取,避免重复调用)
|
||||
private ArrayList<PositionModel> mCachedPositionList;
|
||||
private ArrayList<PositionTaskModel> mCachedTaskList;
|
||||
// ---------------------- 新增:GPS监听核心变量 ----------------------
|
||||
private MainService.GpsUpdateListener mGpsUpdateListener; // GPS监听实例
|
||||
private PositionModel mCurrentGpsPos; // 缓存当前GPS位置(供页面使用)
|
||||
private RecyclerView mRvPosition;
|
||||
private PositionAdapter mPositionAdapter;
|
||||
private ArrayList<PositionModel> mLocalPosCache; // 本地位置缓存(与MainService同步)
|
||||
|
||||
// MainService 引用+绑定状态
|
||||
private MainService mMainService;
|
||||
private boolean isServiceBound = false;
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
// 服务连接(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
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
LogUtils.d(TAG, "MainService绑定成功,开始同步数据");
|
||||
// 从MainService同步初始数据(位置+任务)
|
||||
syncDataFromMainService();
|
||||
// 初始化Adapter(传入MainService实例,确保任务数据从服务获取)
|
||||
initPositionAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
LogUtils.w(TAG, "MainService断开连接,清空引用");
|
||||
mMainService = null;
|
||||
isServiceBound = false;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------- 页面生命周期(新增GPS监听注册/反注册) ----------------------
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_location);
|
||||
|
||||
LogUtils.d(TAG, "onCreate");
|
||||
// 初始化视图+本地缓存
|
||||
initView();
|
||||
mLocalPosCache = new ArrayList<PositionModel>();
|
||||
|
||||
// 初始化GPS监听(提前创建,避免空指针)
|
||||
initGpsUpdateListener();
|
||||
// 启动+初始化MainService(确保服务已创建,实例可获取)
|
||||
startAndInitMainService();
|
||||
// 初始化RecyclerView(布局+性能优化)
|
||||
initRecyclerViewConfig();
|
||||
// 检查服务状态(未运行则启动,配置未启用则提示)
|
||||
//checkServiceStatus();
|
||||
// 缓存服务数据(从服务PUBLIC方法获取,不访问私有字段)
|
||||
cacheServiceData();
|
||||
// 初始化Adapter(传缓存数据+当前GPS位置,支持距离计算显示)
|
||||
initAdapter();
|
||||
// 绑定MainService(确保Activity启动时就拿到服务实例)
|
||||
bindMainService();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// 页面可见时:注册GPS监听(确保能接收实时数据)
|
||||
registerGpsListener();
|
||||
// 刷新数据(避免页面切后台后数据不一致)
|
||||
if (mAdapter != null) {
|
||||
refreshCachedDataAndAdapter();
|
||||
/**
|
||||
* 初始化视图(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());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
// 页面不可见时:反注册GPS监听(减少资源占用,避免内存泄漏)
|
||||
unregisterGpsListener();
|
||||
/**
|
||||
* 初始化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
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
// 1. 最终反注册GPS监听(双重保险,避免遗漏)
|
||||
unregisterGpsListener();
|
||||
// 2. 清理Adapter资源
|
||||
if (mAdapter != null) {
|
||||
mAdapter.release();
|
||||
mAdapter = null;
|
||||
|
||||
// 1. 释放Adapter资源(反注册服务监听,避免内存泄漏)
|
||||
if (mPositionAdapter != null) {
|
||||
mPositionAdapter.release();
|
||||
}
|
||||
|
||||
// 2. 解绑MainService(避免Activity销毁后服务仍被持有)
|
||||
if (isServiceBound) {
|
||||
unbindService(mServiceConnection);
|
||||
LogUtils.d(TAG, "MainService解绑完成");
|
||||
}
|
||||
// 3. 置空服务实例+缓存数据+GPS数据(帮助GC回收)
|
||||
mMainService = null;
|
||||
mCachedPositionList = null;
|
||||
mCachedTaskList = null;
|
||||
mCurrentGpsPos = null;
|
||||
mGpsUpdateListener = null;
|
||||
mRecyclerView = null;
|
||||
LogUtils.d(TAG, "页面销毁:已清理所有资源(服务/缓存/GPS监听)");
|
||||
}
|
||||
|
||||
public static class LocalBinder extends android.os.Binder {
|
||||
// 持有 MainService 实例引用
|
||||
private MainService mService;
|
||||
|
||||
// ---------------------- 新增:GPS监听初始化+注册/反注册(核心适配逻辑) ----------------------
|
||||
/**
|
||||
* 初始化GPS监听:实现MainService.GpsUpdateListener,接收实时GPS数据
|
||||
*/
|
||||
private void initGpsUpdateListener() {
|
||||
mGpsUpdateListener = new MainService.GpsUpdateListener() {
|
||||
// 回调1:GPS位置更新(实时接收经纬度,更新缓存+刷新Adapter)
|
||||
@Override
|
||||
public void onGpsPositionUpdated(PositionModel currentGpsPos) {
|
||||
if (currentGpsPos == null) {
|
||||
LogUtils.w(TAG, "GPS位置更新:数据为空");
|
||||
return;
|
||||
}
|
||||
// 缓存当前GPS位置(供页面其他逻辑使用)
|
||||
mCurrentGpsPos = currentGpsPos;
|
||||
LogUtils.d(TAG, String.format("收到GPS更新:纬度=%.4f,经度=%.4f"
|
||||
, currentGpsPos.getLatitude(), currentGpsPos.getLongitude()));
|
||||
// 构造时传入服务实例
|
||||
public LocalBinder(MainService service) {
|
||||
this.mService = service;
|
||||
}
|
||||
|
||||
// 1. 同步GPS位置到MainService(确保服务数据与页面一致,触发距离计算)
|
||||
if (mMainService != null) {
|
||||
mMainService.syncCurrentGpsPosition(currentGpsPos);
|
||||
// 2. 强制刷新距离计算+Adapter(显示最新距离)
|
||||
mMainService.forceRefreshDistance();
|
||||
refreshCachedDataAndAdapter();
|
||||
}
|
||||
|
||||
// 3. (可选)显示GPS位置Toast提示(如调试场景)
|
||||
// ToastUtils.show("GPS更新:" + currentGpsPos.getLatitude() + "," + currentGpsPos.getLongitude());
|
||||
}
|
||||
|
||||
// 回调2:GPS状态变化(如开启/关闭、信号弱,提示用户)
|
||||
@Override
|
||||
public void onGpsStatusChanged(String status) {
|
||||
if (status == null) return;
|
||||
LogUtils.d(TAG, "GPS状态变化:" + status);
|
||||
// 显示GPS状态(可通过TextView在页面上展示,此处用Toast示例)
|
||||
if (status.contains("未开启") || status.contains("权限") || status.contains("失败")) {
|
||||
// 异常状态:弹出提示引导用户处理
|
||||
ToastUtils.show("GPS提示:" + status);
|
||||
}
|
||||
}
|
||||
};
|
||||
// 对外提供获取服务实例的方法(供Activity调用)
|
||||
public MainService getService() {
|
||||
return mService;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册GPS监听:调用MainService的PUBLIC方法,绑定监听
|
||||
*/
|
||||
private void registerGpsListener() {
|
||||
if (mMainService == null || mGpsUpdateListener == null) {
|
||||
LogUtils.w(TAG, "GPS监听注册失败:服务未初始化或监听未创建");
|
||||
return;
|
||||
}
|
||||
// 调用MainService的registerGpsUpdateListener方法注册
|
||||
mMainService.registerGpsUpdateListener(mGpsUpdateListener);
|
||||
LogUtils.d(TAG, "GPS监听已注册");
|
||||
}
|
||||
|
||||
/**
|
||||
* 反注册GPS监听:调用MainService的PUBLIC方法,解绑监听(核心防内存泄漏)
|
||||
*/
|
||||
private void unregisterGpsListener() {
|
||||
if (mMainService == null || mGpsUpdateListener == null) {
|
||||
LogUtils.w(TAG, "GPS监听反注册失败:服务未初始化或监听未创建");
|
||||
return;
|
||||
}
|
||||
// 调用MainService的unregisterGpsUpdateListener方法反注册
|
||||
mMainService.unregisterGpsUpdateListener(mGpsUpdateListener);
|
||||
LogUtils.d(TAG, "GPS监听已反注册");
|
||||
}
|
||||
|
||||
// ---------------------- 完善:MainService 引用逻辑(修复实例获取可靠性) ----------------------
|
||||
/**
|
||||
* 启动+初始化MainService:先启动服务,再延迟获取实例(避免服务未创建导致null)
|
||||
*/
|
||||
private void startAndInitMainService() {
|
||||
// 步骤1:先启动MainService(确保服务进程已启动,onCreate执行)
|
||||
Intent serviceIntent = new Intent(this, MainService.class);
|
||||
startService(serviceIntent);
|
||||
LogUtils.d(TAG, "已触发MainService启动");
|
||||
|
||||
// 步骤2:延迟200ms获取实例(等待服务onCreate初始化完成,避免返回null)
|
||||
getWindow().getDecorView().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mMainService = MainService.getInstance(LocationActivity.this);
|
||||
if (mMainService == null) {
|
||||
// 容错:1秒后再次尝试获取(应对服务启动慢的场景)
|
||||
getWindow().getDecorView().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mMainService = MainService.getInstance(LocationActivity.this);
|
||||
if (mMainService == null) {
|
||||
LogUtils.e(TAG, "MainService实例获取失败(重试后仍失败)");
|
||||
Toast.makeText(LocationActivity.this, "位置服务初始化失败,请重启应用", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
} else {
|
||||
LogUtils.d(TAG, "MainService实例重试获取成功");
|
||||
// 实例获取成功后,补注册GPS监听+刷新数据
|
||||
registerGpsListener();
|
||||
refreshCachedDataAndAdapter();
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
LogUtils.d(TAG, "MainService实例获取成功");
|
||||
refreshCachedDataAndAdapter();
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
LogUtils.d(TAG, "startAndInitMainService end.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查服务状态(通过服务PUBLIC方法,不访问私有字段)
|
||||
*/
|
||||
// private void checkServiceStatus() {
|
||||
// LogUtils.d(TAG, "checkServiceStatus");
|
||||
// // 1. 服务实例未初始化(等待延迟获取,不重复处理)
|
||||
// if (mMainService == null) {
|
||||
// LogUtils.d(TAG, "服务实例未就绪,等待初始化...");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 2. 检查服务运行状态(调用isServiceRunning())
|
||||
// if (!mMainService.isServiceRunning()) {
|
||||
// // 服务未运行:调用run()启动
|
||||
// mMainService.run();
|
||||
// LogUtils.d(TAG, "服务未运行,已通过run()触发启动");
|
||||
// }
|
||||
//
|
||||
// // 3. 检查服务配置是否启用(调用AppConfigsUtil的PUBLIC方法)
|
||||
// if (!AppConfigsUtil.getInstance(this).isEnableMainService(true)) {
|
||||
// Toast.makeText(this, "位置服务配置未启用,数据可能无法更新", Toast.LENGTH_SHORT).show();
|
||||
// LogUtils.w(TAG, "位置服务配置未启用");
|
||||
// }
|
||||
//
|
||||
// // 4. (新增)检查GPS状态(通过服务逻辑间接判断,提示用户)
|
||||
// if (mCurrentGpsPos == null && mMainService.isServiceRunning()) {
|
||||
// ToastUtils.show("等待GPS信号...请确保GPS已开启且权限已授予");
|
||||
// }
|
||||
// }
|
||||
|
||||
// ---------------------- 原有逻辑完善(适配GPS数据,同步服务交互) ----------------------
|
||||
/**
|
||||
* 缓存服务数据(新增GPS位置缓存,供Adapter使用)
|
||||
*/
|
||||
private void cacheServiceData() {
|
||||
LogUtils.d(TAG, "cacheServiceData()");
|
||||
if (mMainService == null) {
|
||||
ToastUtils.show("缓存数据失败:服务实例为空");
|
||||
LogUtils.e(TAG, "缓存数据失败:服务实例为空");
|
||||
mCachedPositionList = new ArrayList<PositionModel>();
|
||||
mCachedTaskList = new ArrayList<PositionTaskModel>();
|
||||
return;
|
||||
}
|
||||
|
||||
// 从服务PUBLIC方法获取核心数据
|
||||
mCachedPositionList = mMainService.getPositionList();
|
||||
mCachedTaskList = mMainService.getPositionTasksList();
|
||||
// (新增)从服务同步最新GPS位置(避免页面缓存与服务不一致)
|
||||
if (mCurrentGpsPos == null) {
|
||||
// 若页面未收到GPS回调,从服务获取最近位置(需MainService新增getCurrentGpsPosition()方法)
|
||||
// 【注意】需在MainService中添加PUBLIC方法:返回mCurrentGpsPosition
|
||||
// mCurrentGpsPos = mMainService.getCurrentGpsPosition();
|
||||
}
|
||||
|
||||
// 容错:初始化空列表避免空指针
|
||||
if (mCachedPositionList == null) mCachedPositionList = new ArrayList<PositionModel>();
|
||||
if (mCachedTaskList == null) mCachedTaskList = new ArrayList<PositionTaskModel>();
|
||||
|
||||
ToastUtils.show("缓存服务数据成功:位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size());
|
||||
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(新增传入GPS位置,支持Adapter显示距离信息)
|
||||
*/
|
||||
private void initAdapter() {
|
||||
LogUtils.d(TAG, "initAdapter");
|
||||
if (mCachedPositionList == null || mCachedTaskList == null) {
|
||||
LogUtils.e(TAG, "初始化Adapter失败:缓存数据为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 初始化Adapter(传入缓存数据+当前GPS位置,供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;
|
||||
}
|
||||
|
||||
PositionModel targetPos = mCachedPositionList.get(position);
|
||||
String targetPosId = targetPos.getPositionId();
|
||||
// 调用服务PUBLIC方法删除
|
||||
mMainService.removePosition(targetPosId);
|
||||
LogUtils.d(TAG, "通过服务删除位置:ID=" + targetPosId);
|
||||
|
||||
// 刷新缓存+Adapter(包含GPS距离数据)
|
||||
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;
|
||||
}
|
||||
|
||||
// 调用服务PUBLIC方法更新
|
||||
mMainService.updatePosition(updatedPos);
|
||||
LogUtils.d(TAG, "通过服务保存位置:ID=" + updatedPos.getPositionId());
|
||||
|
||||
// 刷新缓存+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;
|
||||
}
|
||||
|
||||
// 构建新任务列表(缓存任务+新任务,去重)
|
||||
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);
|
||||
|
||||
// 调用服务PUBLIC方法同步任务
|
||||
mMainService.syncAllPositionTasks(newTaskList);
|
||||
LogUtils.d(TAG, "通过服务保存任务:ID=" + newTask.getTaskId());
|
||||
|
||||
// 刷新缓存+Adapter
|
||||
refreshCachedDataAndAdapter();
|
||||
Toast.makeText(LocationActivity.this, "任务信息已保存", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
// (新增)GPS同步按钮回调:手动触发GPS位置同步(可选,供页面主动调用)
|
||||
// mAdapter.setOnSyncGpsClickListener(new PositionAdapter.OnSyncGpsClickListener() {
|
||||
// @Override
|
||||
// public void onSyncGpsClick() {
|
||||
// if (mMainService == null || mCurrentGpsPos == null) {
|
||||
// Toast.makeText(LocationActivity.this, "同步失败:GPS未获取到位置", Toast.LENGTH_SHORT).show();
|
||||
// return;
|
||||
// }
|
||||
// refreshCachedDataAndAdapter();
|
||||
// Toast.makeText(LocationActivity.this, "已同步最新GPS位置,距离已更新", Toast.LENGTH_SHORT).show();
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
// ---------------------- 页面交互(新增位置逻辑保留,适配GPS数据) ----------------------
|
||||
/**
|
||||
* 新增位置(调用服务addPosition(),可选:用当前GPS位置初始化新位置)
|
||||
*/
|
||||
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. 创建新位置模型(优化:优先用当前GPS位置初始化,无则用默认值)
|
||||
PositionModel newPos = new PositionModel();
|
||||
newPos.setPositionId(PositionModel.genPositionId()); // 生成唯一ID(需PositionModel实现)
|
||||
// (新增)用当前GPS位置初始化新位置(提升用户体验,无需手动输入经纬度)
|
||||
if (mCurrentGpsPos != null) {
|
||||
newPos.setLongitude(mCurrentGpsPos.getLongitude());
|
||||
newPos.setLatitude(mCurrentGpsPos.getLatitude());
|
||||
newPos.setMemo("当前GPS位置(可编辑)");
|
||||
} else {
|
||||
// 无GPS位置时用默认值
|
||||
newPos.setLongitude(116.404267); // 北京经度
|
||||
newPos.setLatitude(39.915119); // 北京纬度
|
||||
newPos.setMemo("默认位置(可编辑备注)");
|
||||
}
|
||||
newPos.setIsSimpleView(true); // 默认简单视图
|
||||
newPos.setIsEnableRealPositionDistance(true); // 启用距离计算(依赖GPS)
|
||||
|
||||
// 4. 调用服务PUBLIC方法新增
|
||||
mMainService.addPosition(newPos);
|
||||
LogUtils.d(TAG, "通过服务新增位置:ID=" + newPos.getPositionId() + ",纬度=" + newPos.getLatitude());
|
||||
|
||||
// 5. 刷新缓存+Adapter(显示新增结果+距离)
|
||||
refreshCachedDataAndAdapter();
|
||||
Toast.makeText(this, "新增位置成功(已启用GPS距离计算)", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
// ---------------------- 辅助方法(完善数据刷新逻辑,包含GPS距离) ----------------------
|
||||
/**
|
||||
* 刷新缓存数据+Adapter(从服务重新获取数据,确保GPS距离同步)
|
||||
*/
|
||||
private void refreshCachedDataAndAdapter() {
|
||||
if (mMainService == null) {
|
||||
LogUtils.w(TAG, "刷新失败:服务实例为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 从服务重新获取所有数据(包括更新后的位置距离)
|
||||
mCachedPositionList = mMainService.getPositionList();
|
||||
mCachedTaskList = mMainService.getPositionTasksList();
|
||||
// (新增)同步服务最新GPS位置(避免页面缓存滞后)
|
||||
// 【需在MainService中添加以下PUBLIC方法】
|
||||
// if (mMainService.getCurrentGpsPosition() != null) {
|
||||
// mCurrentGpsPos = mMainService.getCurrentGpsPosition();
|
||||
// }
|
||||
|
||||
// 2. 容错处理(避免空指针)
|
||||
if (mCachedPositionList == null) mCachedPositionList = new ArrayList<PositionModel>();
|
||||
if (mCachedTaskList == null) mCachedTaskList = new ArrayList<PositionTaskModel>();
|
||||
|
||||
// 3. 刷新Adapter(传递最新数据+GPS位置,更新距离显示)
|
||||
if (mAdapter != null) {
|
||||
mAdapter.updateAllData(mCachedPositionList, mCachedTaskList);
|
||||
}
|
||||
LogUtils.d(TAG, "刷新完成:位置数=" + mCachedPositionList.size() + ",GPS位置=" + (mCurrentGpsPos != null ? "已获取" : "未获取"));
|
||||
}
|
||||
|
||||
// ---------------------- 补充:MainService 需新增的 PUBLIC 方法(确保交互完整) ----------------------
|
||||
/*
|
||||
* 注:以下方法需手动添加到 MainService 类中,否则 LocationActivity 会报“方法未定义”错误
|
||||
* 核心作用:暴露当前GPS位置给外部(如LocationActivity),确保数据一致性
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ package cc.winboll.studio.positions.adapters;
|
||||
* @Date 2025/09/29 20:25
|
||||
* @Describe 位置数据适配器(完全独立,无未知接口依赖,仅用LocationActivity缓存数据)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -15,30 +14,36 @@ import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.TextView;
|
||||
import android.text.TextUtils;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.positions.R;
|
||||
import cc.winboll.studio.positions.models.PositionModel;
|
||||
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.ConcurrentModificationException;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 核心调整:
|
||||
* 1. 彻底删除所有与 DistanceServiceInterface 相关的代码(解决未知类型问题)
|
||||
* 2. 完全基于 LocationActivity 传入的缓存数据(mCachedPositionList/mCachedTaskList)实现所有功能
|
||||
* 3. 移除服务回调、可见位置通知等冗余逻辑,仅保留“数据显示+用户交互→通知Activity处理”的核心流程
|
||||
* 4. 确保无未定义接口、无外部服务依赖,代码可直接编译运行
|
||||
* Java 7 语法适配:
|
||||
* 1. 移除 Lambda/方法引用,用匿名内部类替代
|
||||
* 2. 集合操作使用迭代器(避免 ConcurrentModificationException)
|
||||
* 3. 弱引用管理 MainService,避免内存泄漏
|
||||
* 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";
|
||||
|
||||
// 1. 视图类型常量(简单视图/编辑视图,无魔法值)
|
||||
// 视图类型常量(Java 7 静态常量定义)
|
||||
private static final int VIEW_TYPE_SIMPLE = 0;
|
||||
private static final int VIEW_TYPE_EDIT = 1;
|
||||
// 2. 默认配置(文本显示/任务默认值,统一管理)
|
||||
|
||||
// 默认配置常量(统一管理,避免魔法值)
|
||||
private static final String DEFAULT_MEMO = "无备注";
|
||||
private static final String DEFAULT_TASK_DESC = "新任务";
|
||||
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_ERROR = "实时距离:计算失败";
|
||||
|
||||
// 3. 唯一数据源(完全依赖 LocationActivity 传入的缓存数据,无其他来源)
|
||||
// 核心依赖(Java 7 弱引用+集合定义)
|
||||
private final Context mContext;
|
||||
private ArrayList<PositionModel> mCachedPositionList; // 位置缓存(来自Activity)
|
||||
private ArrayList<PositionTaskModel> mCachedTaskList; // 任务缓存(来自Activity)
|
||||
private final ArrayList<PositionModel> mCachedPositionList; // 位置缓存(从Activity传入,最终需与MainService同步)
|
||||
private final WeakReference<MainService> mMainServiceRef; // 弱引用MainService,避免内存泄漏
|
||||
private final ConcurrentHashMap<String, TextView> mPosDistanceViewMap; // 距离控件缓存(优化UI更新)
|
||||
|
||||
// 4. 本地辅助缓存(优化性能:避免重复遍历,不依赖外部服务)
|
||||
private final ConcurrentHashMap<String, ArrayList<PositionTaskModel>> mPosToTasksMap; // 位置ID→关联任务(本地分组)
|
||||
private final ConcurrentHashMap<String, TextView> mPosDistanceViewMap; // 位置ID→距离控件(更新UI用)
|
||||
|
||||
// =========================================================================
|
||||
// 与 LocationActivity 交互的回调接口(仅定义必要功能,无冗余)
|
||||
// =========================================================================
|
||||
// 删除位置:通知Activity删除指定索引的位置数据
|
||||
// 回调接口(与Activity交互,仅处理位置逻辑,任务逻辑直接调用MainService)
|
||||
public interface OnDeleteClickListener {
|
||||
void onDeleteClick(int position);
|
||||
}
|
||||
|
||||
// 保存位置:通知Activity保存更新后的位置模型(带索引,方便Activity定位修改)
|
||||
public interface OnSavePositionClickListener {
|
||||
void onSavePositionClick(int position, PositionModel updatedPos);
|
||||
}
|
||||
|
||||
// 保存任务:通知Activity保存新增/修改的任务(带任务模型,Activity处理数据同步)
|
||||
public interface OnSavePositionTaskClickListener {
|
||||
void onSavePositionTaskClick(PositionTaskModel newTask);
|
||||
}
|
||||
|
||||
// 回调实例(由LocationActivity初始化时设置,解耦Activity与Adapter)
|
||||
private OnDeleteClickListener mOnDeleteListener;
|
||||
private OnSavePositionClickListener mOnSavePosListener;
|
||||
private OnSavePositionTaskClickListener mOnSaveTaskListener;
|
||||
|
||||
// =========================================================================
|
||||
// 构造函数(仅接收上下文+Activity缓存数据,无其他依赖,解决接口未定义问题)
|
||||
// 构造函数(Java 7 风格:初始化依赖+注册任务监听)
|
||||
// =========================================================================
|
||||
/**
|
||||
* 唯一构造函数(确保无未知接口依赖,直接使用Activity提供的数据)
|
||||
* @param context 上下文(加载布局、操作软键盘、获取资源)
|
||||
* @param cachedPositionList LocationActivity中的位置缓存(唯一位置数据源)
|
||||
* @param cachedTaskList LocationActivity中的任务缓存(唯一任务数据源)
|
||||
*/
|
||||
public PositionAdapter(Context context, ArrayList<PositionModel> cachedPositionList, ArrayList<PositionTaskModel> cachedTaskList) {
|
||||
public PositionAdapter(Context context, ArrayList<PositionModel> cachedPositionList, MainService mainService) {
|
||||
this.mContext = context;
|
||||
// 初始化数据源(容错处理:若Activity传入null,初始化空列表避免空指针)
|
||||
// 容错处理:避免传入null导致空指针
|
||||
this.mCachedPositionList = (cachedPositionList != null) ? cachedPositionList : new ArrayList<PositionModel>();
|
||||
this.mCachedTaskList = (cachedTaskList != null) ? cachedTaskList : new ArrayList<PositionTaskModel>();
|
||||
|
||||
// 初始化本地辅助缓存(基于Activity传入的数据构建,提升后续操作性能)
|
||||
this.mPosToTasksMap = new ConcurrentHashMap<String, ArrayList<PositionTaskModel>>();
|
||||
// 弱引用MainService:防止Adapter持有Service导致内存泄漏(Java 7 弱引用语法)
|
||||
this.mMainServiceRef = new WeakReference<MainService>(mainService);
|
||||
// 初始化距离控件缓存(线程安全集合,适配多线程更新场景)
|
||||
this.mPosDistanceViewMap = new ConcurrentHashMap<String, TextView>();
|
||||
|
||||
// 初始化“位置→任务”映射(从Activity任务缓存中分组,避免每次显示都遍历全量任务)
|
||||
refreshPositionTaskMap();
|
||||
LogUtils.d(TAG, "Adapter初始化完成:位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size());
|
||||
// 注册MainService任务监听:服务任务变化时自动刷新Adapter(Java 7 接口实现)
|
||||
if (mainService != null) {
|
||||
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
|
||||
public int getItemViewType(int position) {
|
||||
// 从位置缓存获取状态,判断视图类型(简单/编辑)
|
||||
PositionModel posModel = getPositionByIndex(position);
|
||||
// 若位置模型为空或标记为简单视图,返回简单视图类型
|
||||
return (posModel != null && posModel.isSimpleView()) ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建视图Holder(根据类型加载布局,无外部依赖)
|
||||
*/
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
// 根据视图类型加载对应布局(Java 7 条件判断)
|
||||
if (viewType == VIEW_TYPE_SIMPLE) {
|
||||
View simpleView = inflater.inflate(R.layout.item_position_simple, parent, false);
|
||||
return new SimpleViewHolder(simpleView);
|
||||
@@ -130,111 +115,117 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定视图数据(核心逻辑:从Activity缓存取数,不调用任何外部接口)
|
||||
*/
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||
PositionModel posModel = getPositionByIndex(position);
|
||||
if (posModel == null) {
|
||||
LogUtils.w(TAG, "绑定数据失败:位置模型为空(索引=" + position + ")");
|
||||
LogUtils.w(TAG, "onBindViewHolder:位置模型为空(索引=" + position + "),跳过绑定");
|
||||
return;
|
||||
}
|
||||
String posId = posModel.getPositionId();
|
||||
|
||||
// 根据视图类型绑定数据(简单视图/编辑视图分别处理)
|
||||
// 按视图类型绑定数据(Java 7 类型判断)
|
||||
if (holder instanceof SimpleViewHolder) {
|
||||
bindSimpleView((SimpleViewHolder) holder, posModel);
|
||||
} else if (holder instanceof EditViewHolder) {
|
||||
bindEditView((EditViewHolder) holder, posModel, position);
|
||||
}
|
||||
|
||||
// 缓存当前位置的距离控件(后续更新距离UI时直接使用,避免重新查找)
|
||||
TextView distanceView = (holder instanceof SimpleViewHolder)
|
||||
? ((SimpleViewHolder) holder).tvSimpleDistance
|
||||
// 缓存当前位置的距离控件(后续局部更新距离时直接使用)
|
||||
TextView distanceView = (holder instanceof SimpleViewHolder)
|
||||
? ((SimpleViewHolder) holder).tvSimpleDistance
|
||||
: ((EditViewHolder) holder).tvEditDistance;
|
||||
mPosDistanceViewMap.put(posId, distanceView);
|
||||
}
|
||||
|
||||
/**
|
||||
* 视图离开屏幕(清理距离控件缓存,避免内存泄漏)
|
||||
*/
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
|
||||
super.onViewDetachedFromWindow(holder);
|
||||
PositionModel posModel = getPositionByIndex(holder.getAdapterPosition());
|
||||
if (posModel != null) {
|
||||
mPosDistanceViewMap.remove(posModel.getPositionId()); // 移除不再显示的控件引用
|
||||
if (distanceView != null && !TextUtils.isEmpty(posId)) {
|
||||
mPosDistanceViewMap.put(posId, distanceView);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
|
||||
super.onViewDetachedFromWindow(holder);
|
||||
// 视图离开屏幕时,移除距离控件缓存(避免内存泄漏+引用失效控件)
|
||||
PositionModel posModel = getPositionByIndex(holder.getAdapterPosition());
|
||||
if (posModel != null && !TextUtils.isEmpty(posModel.getPositionId())) {
|
||||
mPosDistanceViewMap.remove(posModel.getPositionId());
|
||||
LogUtils.d(TAG, "视图脱离屏幕:移除位置ID=" + posModel.getPositionId() + "的距离控件缓存");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取列表项数量(直接从Activity位置缓存中取,无中间层)
|
||||
*/
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
// 直接从位置缓存获取数量(数据源唯一)
|
||||
return mCachedPositionList.size();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 视图绑定细节(完全基于缓存数据,操作后通过回调通知Activity)
|
||||
// 视图绑定逻辑(Java 7 风格:任务数据从MainService获取)
|
||||
// =========================================================================
|
||||
/**
|
||||
* 绑定“简单视图”(仅显示数据,点击切换到编辑视图)
|
||||
* 绑定简单视图(仅显示数据,点击切换到编辑视图)
|
||||
*/
|
||||
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.tvSimpleLat.setText(String.format("纬度:%.6f", posModel.getLatitude()));
|
||||
// 2. 显示备注(无备注时显示默认文本,避免空白)
|
||||
|
||||
// 2. 显示备注(无备注时显示默认文本)
|
||||
String memo = posModel.getMemo();
|
||||
holder.tvSimpleMemo.setText("备注:" + (memo != null && !memo.isEmpty() ? memo : DEFAULT_MEMO));
|
||||
// 3. 显示实时距离(从缓存数据的distance字段取数,按状态显示不同内容)
|
||||
holder.tvSimpleMemo.setText("备注:" + (TextUtils.isEmpty(memo) ? DEFAULT_MEMO : memo));
|
||||
|
||||
// 3. 显示实时距离(从位置模型取数,调用工具方法更新显示)
|
||||
updateDistanceDisplay(holder.tvSimpleDistance, posModel);
|
||||
// 4. 点击视图→切换到编辑模式(修改缓存数据中的状态,通知RecyclerView刷新)
|
||||
|
||||
// 4. 点击切换到编辑视图(Java 7 匿名内部类实现点击事件)
|
||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
posModel.setIsSimpleView(false); // 修改缓存数据中的视图状态
|
||||
notifyItemChanged(getPositionIndexById(posModel.getPositionId())); // 刷新对应项
|
||||
posModel.setIsSimpleView(false); // 修改位置缓存状态
|
||||
// 通知RecyclerView刷新当前项(精准更新,避免全量刷新)
|
||||
notifyItemChanged(getPositionIndexById(posModel.getPositionId()));
|
||||
LogUtils.d(TAG, "简单视图点击:位置ID=" + posModel.getPositionId() + ",切换到编辑视图");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定“编辑视图”(支持修改备注、开关距离、删除/保存位置、新增任务)
|
||||
* 绑定编辑视图(支持修改备注、开关距离、删除/保存位置、新增任务)
|
||||
*/
|
||||
private void bindEditView(final EditViewHolder holder, final PositionModel posModel, final int position) {
|
||||
final String posId = posModel.getPositionId();
|
||||
|
||||
// 1. 显示经纬度(不可编辑,仅展示,从缓存数据取)
|
||||
// 1. 显示经纬度(不可编辑,仅展示)
|
||||
holder.tvEditLon.setText(String.format("经度:%.6f", posModel.getLongitude()));
|
||||
holder.tvEditLat.setText(String.format("纬度:%.6f", posModel.getLatitude()));
|
||||
// 2. 显示备注(编辑框赋值,光标定位到末尾,方便用户直接编辑)
|
||||
|
||||
// 2. 显示备注(编辑框赋值,光标定位到末尾)
|
||||
String memo = posModel.getMemo();
|
||||
if (memo != null && !memo.isEmpty()) {
|
||||
if (!TextUtils.isEmpty(memo)) {
|
||||
holder.etEditMemo.setText(memo);
|
||||
holder.etEditMemo.setSelection(memo.length());
|
||||
holder.etEditMemo.setSelection(memo.length()); // 光标定位到文本末尾
|
||||
} else {
|
||||
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() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
posModel.setIsSimpleView(true);
|
||||
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() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -242,10 +233,11 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
mOnDeleteListener.onDeleteClick(position); // 通知Activity删除指定索引
|
||||
}
|
||||
hideSoftKeyboard(v);
|
||||
LogUtils.d(TAG, "触发删除:通知Activity处理位置ID=" + posId + "的删除逻辑");
|
||||
}
|
||||
});
|
||||
|
||||
// 7. 保存位置→回调Activity保存(收集编辑后的参数,传更新后的模型)
|
||||
// 7. 保存位置:回调Activity保存(收集参数→构建更新模型→通知Activity)
|
||||
holder.btnSave.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -262,298 +254,303 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
updatedPos.setIsEnableRealPositionDistance(isDistanceEnable); // 更新距离状态
|
||||
updatedPos.setIsSimpleView(true); // 切换回简单视图
|
||||
|
||||
// 回调Activity保存(由Activity同步缓存数据+服务数据,Adapter不处理逻辑)
|
||||
// 回调Activity保存(由Activity同步MainService+位置缓存,Adapter不处理逻辑)
|
||||
if (mOnSavePosListener != null) {
|
||||
mOnSavePosListener.onSavePositionClick(position, updatedPos);
|
||||
}
|
||||
|
||||
// 本地同步状态(避免刷新延迟,直接修改Activity传入的缓存数据)
|
||||
// 本地同步状态(避免刷新延迟,直接修改位置缓存)
|
||||
posModel.setMemo(newMemo);
|
||||
posModel.setIsEnableRealPositionDistance(isDistanceEnable);
|
||||
posModel.setIsSimpleView(true);
|
||||
notifyItemChanged(position); // 刷新当前项,显示更新后的状态
|
||||
hideSoftKeyboard(v);
|
||||
LogUtils.d(TAG, "触发位置保存:ID=" + posId + ",新备注=" + newMemo);
|
||||
LogUtils.d(TAG, "触发保存:位置ID=" + posId + ",新备注=" + newMemo + ",距离启用=" + isDistanceEnable);
|
||||
}
|
||||
});
|
||||
|
||||
// 8. 绑定任务相关视图(显示任务数量+新增任务,基于Activity任务缓存)
|
||||
// 8. 绑定任务视图(显示任务数量+新增任务,数据从MainService获取)
|
||||
bindTaskView(holder, posId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定任务视图(编辑模式专属:显示当前位置的任务数量+新增任务)
|
||||
* 绑定任务视图(编辑模式专属:从MainService获取任务数据,新增任务调用服务接口)
|
||||
*/
|
||||
private void bindTaskView(final EditViewHolder holder, final String posId) {
|
||||
// 从本地映射获取当前位置的任务(基于Activity缓存数据构建,避免重复遍历)
|
||||
ArrayList<PositionTaskModel> posTasks = mPosToTasksMap.get(posId);
|
||||
if (posTasks == null) {
|
||||
posTasks = new ArrayList<PositionTaskModel>();
|
||||
// 1. 从MainService获取当前位置的任务数量(Java 7 迭代器遍历服务数据)
|
||||
int taskCount = 0;
|
||||
MainService mainService = mMainServiceRef.get();
|
||||
if (mainService != null) {
|
||||
ArrayList<PositionTaskModel> posTasks = mainService.getTasksByPositionId(posId);
|
||||
taskCount = (posTasks != null) ? posTasks.size() : 0;
|
||||
}
|
||||
// 显示任务数量(简化设计,实际可扩展为任务列表)
|
||||
holder.tvTaskCount.setText("任务数量:" + taskCount);
|
||||
|
||||
// 显示当前位置的任务数量(简化设计,实际项目可替换为任务列表RecyclerView)
|
||||
holder.tvTaskCount.setText("任务数量:" + posTasks.size());
|
||||
|
||||
// 新增任务→回调Activity处理(Adapter不直接操作任务数据,由Activity同步)
|
||||
// 2. 新增任务:调用MainService接口(不操作本地缓存,数据直接写入服务)
|
||||
holder.btnAddTask.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// 创建默认任务模型(生成唯一ID,关联当前位置)
|
||||
MainService mainService = mMainServiceRef.get();
|
||||
if (mainService == null) {
|
||||
LogUtils.e(TAG, "新增任务失败:MainService已回收(弱引用失效)");
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建默认任务模型(Java 7 显式初始化)
|
||||
PositionTaskModel newTask = new PositionTaskModel();
|
||||
newTask.setTaskId(PositionTaskModel.genTaskId()); // 需在PositionTaskModel实现静态生成ID方法
|
||||
newTask.setPositionId(posId); // 关联当前位置ID(确保任务归属正确)
|
||||
newTask.setTaskId(PositionTaskModel.genTaskId()); // 生成唯一任务ID(需在PositionTaskModel实现静态方法)
|
||||
newTask.setPositionId(posId); // 绑定当前位置ID
|
||||
newTask.setTaskDescription(DEFAULT_TASK_DESC); // 默认任务描述
|
||||
newTask.setIsEnable(true); // 默认启用任务
|
||||
newTask.setDiscussDistance(DEFAULT_TASK_DISTANCE);// 默认任务距离(50米)
|
||||
|
||||
// 回调Activity保存任务(由Activity添加到缓存+同步服务,Adapter仅通知)
|
||||
if (mOnSaveTaskListener != null) {
|
||||
mOnSaveTaskListener.onSavePositionTaskClick(newTask);
|
||||
}
|
||||
|
||||
// 本地同步任务数量(避免刷新延迟,更新本地映射+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());
|
||||
// 调用MainService接口新增任务(数据写入服务,由服务处理持久化+通知刷新)
|
||||
mainService.addPositionTask(newTask);
|
||||
hideSoftKeyboard(v);
|
||||
LogUtils.d(TAG, "触发新增任务:调用MainService接口,位置ID=" + posId + ",任务ID=" + newTask.getTaskId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 核心工具方法(无接口依赖,基于缓存数据实现,确保功能完整)
|
||||
// 工具方法(Java 7 风格:无Lambda,纯匿名内部类+迭代器)
|
||||
// =========================================================================
|
||||
/**
|
||||
* 更新距离显示(根据位置模型状态,显示不同文本和颜色,无外部依赖)
|
||||
* 更新距离显示(根据位置模型状态,显示不同文本+颜色)
|
||||
*/
|
||||
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()) {
|
||||
distanceView.setText(DISTANCE_DISABLED);
|
||||
distanceView.setTextColor(mContext.getResources().getColor(R.color.gray));
|
||||
return;
|
||||
}
|
||||
|
||||
// 场景2:距离计算失败(用-1标记失败,从缓存数据取distance字段)
|
||||
// 场景2:距离计算失败(用-1标记失败状态)
|
||||
double distance = posModel.getRealPositionDistance();
|
||||
if (distance < 0) {
|
||||
distanceView.setText(DISTANCE_ERROR);
|
||||
distanceView.setTextColor(mContext.getResources().getColor(R.color.red));
|
||||
return;
|
||||
}
|
||||
distanceView.setText(DISTANCE_ERROR);
|
||||
distanceView.setTextColor(mContext.getResources().getColor(R.color.red));
|
||||
return;
|
||||
}
|
||||
|
||||
// 场景3:正常显示距离(按距离范围设置颜色,提升视觉区分度)
|
||||
distanceView.setText(String.format(DISTANCE_FORMAT, distance));
|
||||
if (distance <= 100) { // 近距离(≤100米):绿色
|
||||
distanceView.setTextColor(mContext.getResources().getColor(R.color.green));
|
||||
} else if (distance <= 500) { // 中距离(≤500米):黄色
|
||||
distanceView.setTextColor(mContext.getResources().getColor(R.color.yellow));
|
||||
} else { // 远距离(>500米):红色
|
||||
distanceView.setTextColor(mContext.getResources().getColor(R.color.red));
|
||||
}
|
||||
}
|
||||
// 场景3:正常显示距离(按距离范围设置颜色,提升视觉区分度)
|
||||
distanceView.setText(String.format(DISTANCE_FORMAT, distance));
|
||||
if (distance <= 100) {
|
||||
distanceView.setTextColor(mContext.getResources().getColor(R.color.green)); // 近距离(≤100米)
|
||||
} else if (distance <= 500) {
|
||||
distanceView.setTextColor(mContext.getResources().getColor(R.color.yellow));// 中距离(≤500米)
|
||||
} else {
|
||||
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缓存数据直接读取,无中间层,避免数据不一致)
|
||||
*/
|
||||
private PositionModel getPositionByIndex(int index) {
|
||||
if (mCachedPositionList == null || index < 0 || index >= mCachedPositionList.size()) {
|
||||
LogUtils.w(TAG, "获取位置失败:索引无效(" + index + ")或缓存数据为空");
|
||||
return null;
|
||||
}
|
||||
return mCachedPositionList.get(index);
|
||||
}
|
||||
/**
|
||||
* 根据位置ID获取列表索引(用于精准刷新视图)
|
||||
*/
|
||||
private int getPositionIndexById(String positionId) {
|
||||
if (TextUtils.isEmpty(positionId) || mCachedPositionList == null || mCachedPositionList.isEmpty()) {
|
||||
LogUtils.w(TAG, "getPositionIndexById:参数无效(位置ID/缓存为空)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
// 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缓存数据遍历,用于视图切换后精准刷新)
|
||||
*/
|
||||
private int getPositionIndexById(String positionId) {
|
||||
if (mCachedPositionList == null || positionId == null || positionId.isEmpty()) {
|
||||
LogUtils.w(TAG, "获取位置索引失败:缓存数据为空或位置ID无效");
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < mCachedPositionList.size(); i++) {
|
||||
PositionModel pos = mCachedPositionList.get(i);
|
||||
if (positionId.equals(pos.getPositionId())) {
|
||||
return i; // 找到匹配ID,返回对应索引
|
||||
}
|
||||
}
|
||||
LogUtils.w(TAG, "未找到位置ID:" + positionId + ",返回无效索引-1");
|
||||
return -1;
|
||||
}
|
||||
/**
|
||||
* 局部更新距离UI(仅更新指定位置的距离,避免全量刷新卡顿)
|
||||
*/
|
||||
public void updateSinglePositionDistance(String positionId) {
|
||||
// 校验参数:位置ID无效或控件未缓存,直接返回
|
||||
if (TextUtils.isEmpty(positionId) || !mPosDistanceViewMap.containsKey(positionId)) {
|
||||
LogUtils.w(TAG, "updateSinglePositionDistance:位置ID无效或控件未缓存(ID=" + positionId + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
// 从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数据一致)
|
||||
- (Activity任务数据更新后调用,避免本地映射滞后)
|
||||
*/
|
||||
public void refreshPositionTaskMap() {
|
||||
if (mCachedTaskList == null) {
|
||||
mPosToTasksMap.clear(); // 任务缓存为空时,清空本地映射
|
||||
LogUtils.d(TAG, "刷新任务映射:Activity任务缓存为空,已清空本地映射");
|
||||
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);
|
||||
}
|
||||
}
|
||||
// 用服务端最新距离更新UI(直接操作缓存的距离控件,无需刷新整个项)
|
||||
if (latestPos != null) {
|
||||
TextView distanceView = mPosDistanceViewMap.get(positionId);
|
||||
updateDistanceDisplay(distanceView, latestPos);
|
||||
LogUtils.d(TAG, "局部更新距离完成:位置ID=" + positionId + ",最新距离=" + latestPos.getRealPositionDistance() + "米");
|
||||
} else {
|
||||
LogUtils.w(TAG, "局部更新距离失败:未在MainService找到位置ID=" + positionId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* 全量更新位置数据(从MainService同步最新位置列表,刷新UI)
|
||||
*/
|
||||
public void updateAllPositionData(ArrayList<PositionModel> newPosList) {
|
||||
if (newPosList == null) {
|
||||
LogUtils.w(TAG, "updateAllPositionData:新位置列表为空,跳过更新");
|
||||
return;
|
||||
}
|
||||
|
||||
- 全量更新数据(接收Activity最新缓存数据,同步本地数据源+辅助缓存,刷新UI)
|
||||
- (Activity新增/删除位置/任务后调用,确保Adapter与Activity数据100%一致)
|
||||
*/
|
||||
public void updateAllData(ArrayList newPosList, ArrayList newTaskList) {
|
||||
// 同步Activity最新缓存数据(容错处理:避免接收null导致空指针)
|
||||
this.mCachedPositionList = (newPosList != null) ? newPosList : new ArrayList();
|
||||
this.mCachedTaskList = (newTaskList != null) ? newTaskList : new ArrayList();// 刷新本地辅助缓存(确保映射与最新数据同步)
|
||||
refreshPositionTaskMap();
|
||||
mPosDistanceViewMap.clear(); // 清空旧距离控件缓存,避免引用失效控件// 通知RecyclerView全量刷新(数据已同步,更新UI显示)
|
||||
notifyDataSetChanged();
|
||||
LogUtils.d(TAG, "全量更新数据完成:当前位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size());
|
||||
}
|
||||
// 同步服务端最新位置数据到本地缓存
|
||||
this.mCachedPositionList.clear();
|
||||
this.mCachedPositionList.addAll(newPosList);
|
||||
// 清空旧距离控件缓存(避免引用失效控件)
|
||||
mPosDistanceViewMap.clear();
|
||||
// 通知RecyclerView全量刷新UI
|
||||
notifyDataSetChanged();
|
||||
LogUtils.d(TAG, "全量更新位置数据完成:当前位置数量=" + mCachedPositionList.size() + "(数据来源:MainService)");
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* 隐藏软键盘(编辑完成后调用,提升用户体验)
|
||||
*/
|
||||
private void hideSoftKeyboard(View view) {
|
||||
if (mContext == null || view == null) {
|
||||
LogUtils.w(TAG, "hideSoftKeyboard:参数为空(上下文/视图),无法隐藏键盘");
|
||||
return;
|
||||
}
|
||||
|
||||
- 局部更新距离UI(仅更新指定位置的距离显示,避免全量刷新卡顿)
|
||||
- (Activity同步服务计算的最新距离后调用,精准更新受影响的位置)
|
||||
*/
|
||||
public void updateSinglePositionDistance(String positionId) {
|
||||
// 校验参数:位置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);
|
||||
}
|
||||
}
|
||||
// Java 7 显式获取输入法服务,避免Lambda
|
||||
InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imm != null) {
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0); // 强制隐藏软键盘
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
// =========================================================================
|
||||
// 实现 MainService.TaskUpdateListener 接口(服务任务变化时回调)
|
||||
// =========================================================================
|
||||
@Override
|
||||
public void onTaskUpdated() {
|
||||
LogUtils.d(TAG, "收到MainService任务更新通知(任务新增/删除/状态变化),刷新UI");
|
||||
// 任务数据变化时,全量刷新Adapter(确保任务数量等显示同步)
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
- 隐藏软键盘(编辑完成后调用,提升用户体验,避免键盘遮挡)
|
||||
*/
|
||||
private void hideSoftKeyboard(View view) {
|
||||
if (mContext == null || view == null) return;
|
||||
InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imm != null) {
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0); // 强制隐藏软键盘
|
||||
}
|
||||
}
|
||||
// =========================================================================
|
||||
// 回调设置方法(供LocationActivity调用,绑定交互逻辑)
|
||||
// =========================================================================
|
||||
public void setOnDeleteClickListener(OnDeleteClickListener listener) {
|
||||
this.mOnDeleteListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
public void setOnSavePositionClickListener(OnSavePositionClickListener listener) {
|
||||
this.mOnSavePosListener = listener;
|
||||
}
|
||||
|
||||
- 释放资源(Activity销毁时调用,彻底避免内存泄漏)
|
||||
*/
|
||||
public void release() {
|
||||
// 清空本地辅助缓存(解除控件、数据映射引用)
|
||||
mPosToTasksMap.clear();
|
||||
mPosDistanceViewMap.clear();
|
||||
// 置空回调实例(避免持有Activity引用导致内存泄漏)
|
||||
mOnDeleteListener = null;
|
||||
mOnSavePosListener = null;
|
||||
mOnSaveTaskListener = null;
|
||||
// 置空数据源引用(帮助GC回收,避免残留数据占用内存)
|
||||
mCachedPositionList = null;
|
||||
mCachedTaskList = null;
|
||||
LogUtils.d(TAG, "Adapter资源已完全释放");
|
||||
}
|
||||
// =========================================================================
|
||||
// 资源释放(Activity销毁时调用,避免内存泄漏)
|
||||
// =========================================================================
|
||||
public void release() {
|
||||
// 1. 反注册MainService任务监听(解除与服务的绑定,避免内存泄漏)
|
||||
MainService mainService = mMainServiceRef.get();
|
||||
if (mainService != null) {
|
||||
mainService.unregisterTaskUpdateListener(this);
|
||||
LogUtils.d(TAG, "已反注册MainService任务监听,避免内存泄漏");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 回调设置方法(LocationActivity调用,绑定交互逻辑,无冗余参数)
|
||||
// =========================================================================
|
||||
public void setOnDeleteClickListener(OnDeleteClickListener listener) {
|
||||
this.mOnDeleteListener = listener;
|
||||
}
|
||||
// 2. 清空本地缓存(解除控件/数据引用,帮助GC回收)
|
||||
mPosDistanceViewMap.clear();
|
||||
if (mCachedPositionList != null) {
|
||||
mCachedPositionList.clear();
|
||||
}
|
||||
|
||||
public void setOnSavePositionClickListener(OnSavePositionClickListener listener) {
|
||||
this.mOnSavePosListener = listener;
|
||||
}
|
||||
// 3. 置空回调实例(避免持有Activity引用导致内存泄漏)
|
||||
mOnDeleteListener = null;
|
||||
mOnSavePosListener = null;
|
||||
|
||||
public void setOnSavePositionTaskClickListener(OnSavePositionTaskClickListener listener) {
|
||||
this.mOnSaveTaskListener = listener;
|
||||
}
|
||||
LogUtils.d(TAG, "Adapter资源已完全释放(缓存清空+监听反注册)");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 视图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 static class SimpleViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView tvSimpleLon; // 经度显示控件
|
||||
TextView tvSimpleLat; // 纬度显示控件
|
||||
TextView tvSimpleMemo; // 备注显示控件
|
||||
TextView tvSimpleDistance;// 实时距离显示控件
|
||||
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);
|
||||
}
|
||||
}
|
||||
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 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; // 任务数量显示控件(简化设计)
|
||||
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);
|
||||
}
|
||||
}
|
||||
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
Reference in New Issue
Block a user