From 94054c39e8a68f7a05842d73195a2a3385997bf2 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Tue, 30 Sep 2025 02:39:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AE=9E=E6=97=B6=E8=B7=9D?= =?UTF-8?q?=E7=A6=BB=E8=AE=A1=E7=AE=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- positions/build.properties | 4 +- .../activities/LocationActivity.java | 450 +++++++++--------- .../positions/adapters/PositionAdapter.java | 170 +++++-- .../positions/models/PositionModel.java | 70 ++- .../main/res/layout/item_position_edit.xml | 57 ++- .../main/res/layout/item_position_simple.xml | 12 +- 6 files changed, 506 insertions(+), 257 deletions(-) diff --git a/positions/build.properties b/positions/build.properties index b21727d..c1fa335 100644 --- a/positions/build.properties +++ b/positions/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Mon Sep 29 17:49:24 GMT 2025 +#Mon Sep 29 18:33:47 GMT 2025 stageCount=3 libraryProject= baseVersion=15.0 publishVersion=15.0.2 -buildCount=12 +buildCount=15 baseBetaVersion=15.0.3 diff --git a/positions/src/main/java/cc/winboll/studio/positions/activities/LocationActivity.java b/positions/src/main/java/cc/winboll/studio/positions/activities/LocationActivity.java index 884dc3f..41f1854 100644 --- a/positions/src/main/java/cc/winboll/studio/positions/activities/LocationActivity.java +++ b/positions/src/main/java/cc/winboll/studio/positions/activities/LocationActivity.java @@ -3,7 +3,7 @@ package cc.winboll.studio.positions.activities; /** * @Author ZhanGSKen&豆包大模型 * @Date 2025/09/29 18:22 - * @Describe 当前位置实时显示 + * @Describe 当前位置实时显示 + 位置列表实时距离计算 */ import android.Manifest; import android.app.AlertDialog; @@ -12,8 +12,6 @@ import android.content.pm.PackageManager; import android.location.Location; import android.os.Bundle; import android.text.InputType; -import android.view.GestureDetector; -import android.view.MotionEvent; import android.view.View; import android.widget.Button; import android.widget.EditText; @@ -25,7 +23,6 @@ import androidx.core.app.ActivityCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import cc.winboll.studio.positions.R; -import cc.winboll.studio.positions.activities.LocationActivity; import cc.winboll.studio.positions.adapters.PositionAdapter; import cc.winboll.studio.positions.models.PositionModel; import com.google.android.gms.location.FusedLocationProviderClient; @@ -35,59 +32,52 @@ import com.google.android.gms.location.LocationResult; import com.google.android.gms.location.LocationServices; import com.google.android.gms.tasks.OnSuccessListener; import java.util.ArrayList; -import android.view.LayoutInflater; /** * 实时定位活动窗口: * 1. 申请定位必需权限(精确定位) * 2. 初始化FusedLocationProviderClient(谷歌官方定位服务,兼容所有安卓版本) - * 3. 实时监听位置变化,更新显示经度、纬度 - * 4. 右下角圆形悬浮按钮(含大写P字母,支持扩展功能) + * 3. 实时监听位置变化,更新显示经度、纬度 + 同步给Adapter计算实时距离 + * 4. 右下角圆形悬浮按钮(含大写P字母,支持添加位置) + * 5. 位置列表支持实时距离显示(按isEnableRealPositionDistance控制) */ public class LocationActivity extends AppCompatActivity { public static final String TAG = "LocationActivity"; - // 1. 核心组件与常量定义(Java 7不支持final修饰后未立即初始化的变量,移除不必要final) + // 1. 核心组件与常量定义(兼容Java 7,移除不必要final) private static final int REQUEST_LOCATION_PERMISSIONS = 1004; // 定位权限请求码 private FusedLocationProviderClient fusedLocationClient; // 定位核心客户端 private LocationCallback locationCallback; // 位置变化监听器 private LocationRequest locationRequest; // 定位请求配置(频率、精度等) + private Location currentLocation; // 存储当前最新位置(用于同步给Adapter) - // UI控件:Java 7不支持变量声明后直接换行注释,调整注释位置 + // UI控件(Java 7显式声明+强制转换) private TextView tvLongitude; // 经度显示 private TextView tvLatitude; // 纬度显示 private Button fabPButton; // 右下角圆形悬浮按钮(P字母) - // UI控件:新增RecyclerView和Adapter private RecyclerView rvPositionList; // 位置列表(RecyclerView) - private PositionAdapter positionAdapter; // 列表Adapter(数据联动) - ArrayList mPositionList = new ArrayList(); + private PositionAdapter positionAdapter; // 列表Adapter(含实时距离逻辑) + ArrayList mPositionList = new ArrayList(); // 位置数据集合 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_location); - // 绑定UI控件(Java 7无语法差异,保持逻辑) - tvLongitude = (TextView) findViewById(R.id.tv_longitude); // Java 7需显式强制转换 + // 绑定UI控件(Java 7显式强制转换) + tvLongitude = (TextView) findViewById(R.id.tv_longitude); tvLatitude = (TextView) findViewById(R.id.tv_latitude); - fabPButton = (Button) findViewById(R.id.fab_p_button); // 绑定悬浮按钮 - - // 初始化定位相关组件 - initLocationConfig(); - // 初始化悬浮按钮(设置点击事件) - initFabPButton(); - - // 新增1:绑定RecyclerView控件(显式强转,适配Java 7) + fabPButton = (Button) findViewById(R.id.fab_p_button); rvPositionList = (RecyclerView) findViewById(R.id.rv_position_list); - // 新增2:初始化Adapter(传入mPositionList,确保数据联动) + // 初始化核心逻辑:定位配置→悬浮按钮→列表Adapter→加载历史数据 + initLocationConfig(); + initFabPButton(); initRecyclerViewAndAdapter(); - - // 新增3:从本地加载历史位置数据(BaseBean的saveBeanList对应加载方法) loadHistoryPositions(); - // 检查并申请定位权限 + // 检查并申请定位权限(权限通过后启动实时定位) if (checkLocationPermissions()) { startRealTimeLocation(); } else { @@ -97,10 +87,54 @@ public class LocationActivity extends AppCompatActivity { /** - * 初始化悬浮按钮(设置点击事件,Java 7不支持Lambda,改用匿名内部类) + * 初始化定位配置(兼容Java 7,用LocationRequest.create()替代Builder) + */ + private void initLocationConfig() { + // 初始化定位客户端 + fusedLocationClient = LocationServices.getFusedLocationProviderClient(this); + + // 定位请求配置(高精度、1秒更新一次,适配旧版Google Play Services) + locationRequest = LocationRequest.create(); + locationRequest.setInterval(1000); // 定位更新间隔(1秒) + locationRequest.setFastestInterval(500); // 最快更新间隔(500毫秒) + locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); // 优先GPS高精度定位 + + // 位置变化监听器(实时更新UI + 同步位置给Adapter计算距离) + locationCallback = new LocationCallback() { + @Override + public void onLocationResult(@NonNull LocationResult locationResult) { + super.onLocationResult(locationResult); + currentLocation = locationResult.getLastLocation(); // 更新当前最新位置 + if (currentLocation != null) { + // 1. 更新页面经度、纬度显示 + double longitude = currentLocation.getLongitude(); + double latitude = currentLocation.getLatitude(); + tvLongitude.setText(String.format("当前经度:%.6f", longitude)); + tvLatitude.setText(String.format("当前纬度:%.6f", latitude)); + + // 2. 同步当前位置给Adapter(用于计算列表项实时距离) + if (positionAdapter != null) { + // 创建仅含经纬度的PositionModel(memo空,isEnable无需关注) + PositionModel currentGpsPos = new PositionModel(); + currentGpsPos.setLongitude(longitude); + currentGpsPos.setLatitude(latitude); + // 调用Adapter方法传入当前GPS位置 + positionAdapter.setCurrentGpsPosition(currentGpsPos); + } + } else { + // 位置为空(如GPS信号弱),显示等待提示 + tvLongitude.setText("当前经度:等待更新..."); + tvLatitude.setText("当前纬度:等待更新..."); + } + } + }; + } + + + /** + * 初始化悬浮按钮(点击弹出备注输入框,添加当前位置到列表) */ private void initFabPButton() { - // 悬浮按钮点击事件:替换Lambda为View.OnClickListener匿名内部类 fabPButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -111,56 +145,30 @@ public class LocationActivity extends AppCompatActivity { /** - * 初始化定位配置:Java 7不支持LocationRequest.Builder(适配旧API),调整定位请求创建方式 + * 初始化RecyclerView和Adapter(绑定实时距离计算逻辑) */ - private void initLocationConfig() { - // 初始化定位客户端(Java 7语法无差异) - fusedLocationClient = LocationServices.getFusedLocationProviderClient(this); + private void initRecyclerViewAndAdapter() { + // 1. 配置RecyclerView布局管理器(垂直列表) + LinearLayoutManager layoutManager = new LinearLayoutManager(this); + layoutManager.setOrientation(LinearLayoutManager.VERTICAL); + rvPositionList.setLayoutManager(layoutManager); - // 关键修改:Java 7环境对应旧版Google Play Services,用LocationRequest.create()替代Builder - locationRequest = LocationRequest.create(); - locationRequest.setInterval(1000); // 定位更新间隔(毫秒)- 替代Builder(1000) - locationRequest.setFastestInterval(500); // 最小更新间隔 - 替代setMinUpdateIntervalMillis - locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); // 高精度定位(优先GPS) + // 2. 初始化Adapter(传入上下文和数据集合,兼容Java 7) + positionAdapter = new PositionAdapter(this, mPositionList); + rvPositionList.setAdapter(positionAdapter); - // 初始化位置变化监听器:替换Lambda为LocationCallback匿名内部类(Java 7核心修改) - locationCallback = new LocationCallback() { - @Override - public void onLocationResult(@NonNull LocationResult locationResult) { - super.onLocationResult(locationResult); - // 获取最新位置信息(逻辑不变,Java 7语法兼容) - Location latestLocation = locationResult.getLastLocation(); - if (latestLocation != null) { - double longitude = latestLocation.getLongitude(); // 经度 - double latitude = latestLocation.getLatitude(); // 纬度 - - // 更新UI:String.format兼容Java 7,逻辑不变 - tvLongitude.setText(String.format("当前经度:%.6f", longitude)); - tvLatitude.setText(String.format("当前纬度:%.6f", latitude)); - } - } - }; - } - - private void initRecyclerViewAndAdapter() { - // 1. 初始化Adapter(传入Context和mPositionList,适配新构造函数) - positionAdapter = new PositionAdapter(this, mPositionList); - rvPositionList.setAdapter(positionAdapter); - - // 2. 设置删除监听(编辑视图点击“删除”时触发) - positionAdapter.setOnDeleteClickListener(new PositionAdapter.OnDeleteClickListener() { + // 3. 设置Adapter删除监听(删除列表项并同步本地数据) + positionAdapter.setOnDeleteClickListener(new PositionAdapter.OnDeleteClickListener() { @Override public void onDeleteClick(int position) { - // 弹删除确认对话框(复用之前的showDeleteConfirmDialog方法) showDeleteConfirmDialog(position); } }); - // 3. 设置保存监听(编辑视图点击“确定”时触发,同步本地数据) - positionAdapter.setOnSaveClickListener(new PositionAdapter.OnSaveClickListener() { + // 4. 设置Adapter保存监听(编辑备注后同步本地数据) + positionAdapter.setOnSaveClickListener(new PositionAdapter.OnSaveClickListener() { @Override public void onSaveClick() { - // 编辑后保存到本地 try { PositionModel.saveBeanList(LocationActivity.this, mPositionList, PositionModel.class); } catch (Exception e) { @@ -169,79 +177,147 @@ public class LocationActivity extends AppCompatActivity { } } }); - - // 4. 原有布局管理器设置(保留不变) - RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this); - ((LinearLayoutManager) layoutManager).setOrientation(LinearLayoutManager.VERTICAL); - rvPositionList.setLayoutManager(layoutManager); - } - -// 保留原删除确认对话框(无修改,编辑视图点击“删除”会触发此方法) - private void showDeleteConfirmDialog(final int position) { - AlertDialog.Builder deleteDialogBuilder = new AlertDialog.Builder(this); - deleteDialogBuilder.setTitle("删除位置记录") - .setMessage("确定要删除这条位置记录吗?(删除后不可恢复)") - .setPositiveButton("确定", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - positionAdapter.removePosition(position); - // 删除后同步本地数据 - try { - PositionModel.saveBeanList(LocationActivity.this, mPositionList, PositionModel.class); - Toast.makeText(LocationActivity.this, "删除成功", Toast.LENGTH_SHORT).show(); - } catch (Exception e) { - e.printStackTrace(); - Toast.makeText(LocationActivity.this, "删除失败,请重试", Toast.LENGTH_SHORT).show(); - } - } - }) - .setNegativeButton("取消", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }) - .setCancelable(false); - deleteDialogBuilder.show(); - } - - /** - * 从本地加载历史位置数据: - * 基于BaseBean的saveBeanList方法,对应加载方法为readBeanList(通用持久化逻辑) - */ - private void loadHistoryPositions() { - try { - // 读取本地保存的PositionModel列表(参数:上下文、数据模型类) - ArrayList historyList = new ArrayList(); - PositionModel.loadBeanList(LocationActivity.this, historyList, PositionModel.class); - if (historyList != null && !historyList.isEmpty()) { - mPositionList.clear(); // 清空原有空列表 - mPositionList.addAll(historyList); // 添加历史数据 - positionAdapter.notifyDataSetChanged(); // 通知Adapter更新列表 - } - } catch (Exception e) { - // 异常处理(如首次启动无历史数据,不提示) - e.printStackTrace(); - } } - /** - * 检查定位必需权限(逻辑不变,Java 7语法兼容) + * 从本地加载历史位置数据(基于BaseBean的持久化逻辑) + */ + private void loadHistoryPositions() { + try { + ArrayList historyList = new ArrayList(); + // 调用PositionModel加载方法(读取本地保存的位置数据) + PositionModel.loadBeanList(LocationActivity.this, historyList, PositionModel.class); + if (historyList != null && !historyList.isEmpty()) { + mPositionList.clear(); + mPositionList.addAll(historyList); + positionAdapter.notifyDataSetChanged(); // 通知列表刷新 + } + } catch (Exception e) { + e.printStackTrace(); // 首次启动无数据时忽略异常 + } + } + + + /** + * 弹出位置备注输入对话框(添加当前位置到列表) + */ + private void showLocationRemarkDialog() { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(LocationActivity.this); + dialogBuilder.setTitle("当前位置备注"); + + // 创建输入框(配置提示文本和内边距) + final EditText remarkInput = new EditText(LocationActivity.this); + remarkInput.setHint("请输入备注(如:公司/家/学校)"); + remarkInput.setInputType(InputType.TYPE_CLASS_TEXT); + remarkInput.setPadding( + dip2px(16), + dip2px(8), + dip2px(16), + dip2px(8) + ); + dialogBuilder.setView(remarkInput); + + // 确定按钮:添加位置到列表 + 保存本地 + dialogBuilder.setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String inputRemark = remarkInput.getText().toString().trim(); + if (inputRemark.isEmpty()) { + Toast.makeText(LocationActivity.this, "未输入备注", Toast.LENGTH_SHORT).show(); + return; + } + + // 校验当前位置是否有效(避免无定位时添加空数据) + if (currentLocation == null) { + Toast.makeText(LocationActivity.this, "未获取到当前位置,请稍后再试", Toast.LENGTH_SHORT).show(); + return; + } + + // 添加位置到列表(isEnableRealPositionDistance默认false,需手动开启) + double longitude = currentLocation.getLongitude(); + double latitude = currentLocation.getLatitude(); + PositionModel newPosition = new PositionModel( + longitude, + latitude, + inputRemark, + false // 默认不启用实时距离,用户可后续通过编辑开启 + ); + mPositionList.add(newPosition); + + // 保存到本地 + 刷新列表 + try { + PositionModel.saveBeanList(LocationActivity.this, mPositionList, PositionModel.class); + positionAdapter.notifyItemInserted(mPositionList.size() - 1); // 局部刷新(性能更优) + Toast.makeText(LocationActivity.this, "位置已添加", Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + e.printStackTrace(); + Toast.makeText(LocationActivity.this, "位置保存失败", Toast.LENGTH_SHORT).show(); + } + dialog.dismiss(); + } + }); + + // 取消按钮:仅关闭对话框 + dialogBuilder.setNegativeButton("取消", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + + // 配置对话框(禁止外部点击关闭) + dialogBuilder.setCancelable(false); + AlertDialog remarkDialog = dialogBuilder.create(); + remarkDialog.show(); + } + + + /** + * 显示删除确认对话框(删除列表项) + */ + private void showDeleteConfirmDialog(final int position) { + AlertDialog.Builder deleteDialogBuilder = new AlertDialog.Builder(this); + deleteDialogBuilder.setTitle("删除位置记录") + .setMessage("确定要删除这条位置吗?(删除后不可恢复)") + .setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // 从列表移除 + 保存本地 + positionAdapter.removePosition(position); + try { + PositionModel.saveBeanList(LocationActivity.this, mPositionList, PositionModel.class); + Toast.makeText(LocationActivity.this, "删除成功", Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + e.printStackTrace(); + Toast.makeText(LocationActivity.this, "删除失败,请重试", Toast.LENGTH_SHORT).show(); + } + } + }) + .setNegativeButton("取消", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .setCancelable(false); + deleteDialogBuilder.show(); + } + + + /** + * 检查定位权限(仅精确定位权限,满足实时距离计算需求) */ private boolean checkLocationPermissions() { - // Java 7不支持多行条件判断的简洁换行,调整格式保持可读性 - return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) + return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED; } /** - * 申请定位必需权限(逻辑不变,Java 7语法兼容) + * 申请定位权限 */ private void requestLocationPermissions() { - // Java 7数组初始化语法无差异,保持原逻辑 String[] permissions = new String[]{Manifest.permission.ACCESS_FINE_LOCATION}; ActivityCompat.requestPermissions( this, @@ -252,23 +328,21 @@ public class LocationActivity extends AppCompatActivity { /** - * 启动实时定位:Java 7不支持Lambda,替换OnSuccessListener为匿名内部类 + * 启动实时定位(获取当前位置 + 监听位置变化) */ private void startRealTimeLocation() { - // 权限兜底检查(逻辑不变) - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, "定位权限未授予,无法启动定位", Toast.LENGTH_SHORT).show(); + if (!checkLocationPermissions()) { + Toast.makeText(this, "定位权限未授予", Toast.LENGTH_SHORT).show(); return; } - // 1. 先获取一次当前位置:替换Lambda为OnSuccessListener匿名内部类(Java 7核心修改) + // 1. 先获取一次当前位置(初始化页面显示) fusedLocationClient.getLastLocation() .addOnSuccessListener(this, new OnSuccessListener() { @Override public void onSuccess(Location location) { - // 成功获取位置后的逻辑(无语法差异) if (location != null) { + currentLocation = location; tvLongitude.setText(String.format("当前经度:%.6f", location.getLongitude())); tvLatitude.setText(String.format("当前纬度:%.6f", location.getLatitude())); } else { @@ -278,35 +352,35 @@ public class LocationActivity extends AppCompatActivity { } }); - // 2. 注册监听器,接收实时位置更新(逻辑不变,Java 7语法兼容) + // 2. 注册位置监听器(实时更新位置) fusedLocationClient.requestLocationUpdates( locationRequest, locationCallback, - getMainLooper() // 在主线程更新UI(避免线程异常) + getMainLooper() // 主线程更新UI,避免线程异常 ); } /** - * 处理权限申请结果(逻辑不变,Java 7语法兼容) + * 处理权限申请结果 */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_LOCATION_PERMISSIONS) { - boolean allGranted = true; - // Java 7增强for循环语法无差异 + boolean isGranted = false; for (int result : grantResults) { - if (result != PackageManager.PERMISSION_GRANTED) { - allGranted = false; + if (result == PackageManager.PERMISSION_GRANTED) { + isGranted = true; break; } } - if (allGranted) { - startRealTimeLocation(); + if (isGranted) { + startRealTimeLocation(); // 权限通过,启动定位 } else { - Toast.makeText(this, "定位权限被拒绝,无法显示位置信息", Toast.LENGTH_SHORT).show(); + // 权限拒绝,提示并显示无权限状态 + Toast.makeText(this, "定位权限被拒绝,无法显示实时位置和距离", Toast.LENGTH_SHORT).show(); tvLongitude.setText("当前经度:无权限"); tvLatitude.setText("当前纬度:无权限"); } @@ -315,18 +389,23 @@ public class LocationActivity extends AppCompatActivity { /** - * 活动销毁时停止定位监听(逻辑不变,Java 7语法兼容) + * 活动销毁:停止定位 + 停止距离刷新定时器(避免内存泄漏) */ @Override protected void onDestroy() { super.onDestroy(); - - // 1. 停止定位监听(原有逻辑,保留) + + // 1. 停止定位监听(释放定位资源) if (fusedLocationClient != null && locationCallback != null) { fusedLocationClient.removeLocationUpdates(locationCallback); } - // 2. 补充:销毁前同步一次数据(确保最后修改的记录被保存) + // 2. 停止Adapter的距离刷新定时器(关键:避免Timer持有Context导致内存泄漏) + if (positionAdapter != null) { + positionAdapter.stopDistanceRefreshTimer(); + } + + // 3. 最后同步一次数据(确保所有修改保存) try { if (mPositionList != null && !mPositionList.isEmpty()) { PositionModel.saveBeanList(this, mPositionList, PositionModel.class); @@ -335,85 +414,14 @@ public class LocationActivity extends AppCompatActivity { e.printStackTrace(); } } - - /** - * 弹出“当前位置备注”输入对话框: - * 1. 显示带文本输入框的对话框(标题固定为“当前位置备注”) - * 2. 点击确定→弹Toast显示输入的备注内容 - * 3. 点击取消/关闭对话框→不执行操作 - */ - private void showLocationRemarkDialog() { - // 1. 初始化对话框构建器(Java 7需显式指定Context为LocationActivity.this) - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(LocationActivity.this); - dialogBuilder.setTitle("当前位置备注"); // 设置对话框标题 - // 2. 创建文本输入框(配置输入类型为普通文本,提示用户输入) - final EditText remarkInput = new EditText(LocationActivity.this); - remarkInput.setHint("请输入当前位置的备注(如:公司/家/学校)"); // 输入提示 - remarkInput.setInputType(InputType.TYPE_CLASS_TEXT); // 普通文本输入(禁止数字/密码等特殊类型) - remarkInput.setPadding( - dip2px(16), // 左内边距(16dp,适配不同屏幕) - dip2px(8), // 上内边距 - dip2px(16), // 右内边距 - dip2px(8) // 下内边距 - ); - dialogBuilder.setView(remarkInput); // 将输入框添加到对话框 - - // 3. 设置“确定”按钮(点击后获取输入内容,弹Toast显示) - dialogBuilder.setPositiveButton("确定", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // 获取输入框内容(trim()去除前后空格,避免空输入) - String inputRemark = remarkInput.getText().toString().trim(); - - // 处理输入:空输入提示“未输入备注”,非空提示输入内容 - if (inputRemark.isEmpty()) { - Toast.makeText(LocationActivity.this, "未输入位置备注", Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(LocationActivity.this, "当前位置备注:" + inputRemark, Toast.LENGTH_SHORT).show(); - - // 扩展建议:获取当前经纬度做操作(Java 7字符串处理逻辑不变) - if (tvLongitude.getText().toString().contains(":")) { - String szLongitude = tvLongitude.getText().toString().split(":")[1]; - double longitude = Double.parseDouble(szLongitude); - String szLatitude = tvLatitude.getText().toString().split(":")[1]; - double latitude = Double.parseDouble(szLatitude); - // saveCurrentLocation(longitude, latitude); // 自定义“保存位置”方法(如需使用需自己实现) - mPositionList.add(new PositionModel(longitude, latitude, inputRemark)); - PositionModel.saveBeanList(LocationActivity.this, mPositionList, PositionModel.class); - } - } - dialog.dismiss(); // 关闭对话框(避免残留) - } - }); - - // 4. 设置“取消”按钮(点击仅关闭对话框,不做其他操作) - dialogBuilder.setNegativeButton("取消", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); // 关闭对话框 - } - }); - - // 5. 配置对话框其他属性(禁止点击外部关闭、禁止按返回键关闭) - dialogBuilder.setCancelable(false); // 点击对话框外部不关闭 - AlertDialog remarkDialog = dialogBuilder.create(); // 创建对话框实例 - remarkDialog.setCanceledOnTouchOutside(true); // 再次确认“外部点击不关闭”(兼容部分机型) - - // 6. 显示对话框 - remarkDialog.show(); - } /** - * 辅助工具:将dp转换为px(解决不同屏幕分辨率下输入框内边距不一致问题) - * @param dpValue 要转换的dp值(如16dp) - * @return 转换后的px值(适配当前设备屏幕密度) + * 辅助工具:dp转px(适配不同屏幕分辨率) */ private int dip2px(float dpValue) { - // 获取设备屏幕密度(density),dp转px公式:px = dp * density + 0.5f(+0.5f用于四舍五入) final float scale = getResources().getDisplayMetrics().density; - return (int) (dpValue * scale + 0.5f); + return (int) (dpValue * scale + 0.5f); // +0.5f用于四舍五入,确保精度 } - } diff --git a/positions/src/main/java/cc/winboll/studio/positions/adapters/PositionAdapter.java b/positions/src/main/java/cc/winboll/studio/positions/adapters/PositionAdapter.java index 04d5865..a09c200 100644 --- a/positions/src/main/java/cc/winboll/studio/positions/adapters/PositionAdapter.java +++ b/positions/src/main/java/cc/winboll/studio/positions/adapters/PositionAdapter.java @@ -3,9 +3,11 @@ package cc.winboll.studio.positions.adapters; /** * @Author ZhanGSKen&豆包大模型 * @Date 2025/09/29 20:25 - * @Describe 位置数据适配器(支持简单视图/编辑视图切换) + * @Describe 位置数据适配器(支持简单视图/编辑视图切换 + 实时距离开关控制) */ import android.content.Context; +import android.os.Handler; +import android.os.Looper; import android.view.LayoutInflater; import android.view.MenuInflater; import android.view.MenuItem; @@ -14,6 +16,8 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; import android.widget.PopupMenu; +import android.widget.RadioButton; +import android.widget.RadioGroup; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; @@ -21,14 +25,20 @@ import androidx.recyclerview.widget.RecyclerView; import cc.winboll.studio.positions.R; import cc.winboll.studio.positions.models.PositionModel; import java.util.ArrayList; +import java.util.Timer; +import java.util.TimerTask; public class PositionAdapter extends RecyclerView.Adapter { public static final String TAG = "PositionAdapter"; private static final int VIEW_TYPE_SIMPLE = 1; // 简单视图 private static final int VIEW_TYPE_EDIT = 2; // 编辑视图 + private static final long REFRESH_INTERVAL = 5000; // 实时刷新间隔:5秒(5000毫秒) private ArrayList mPositionList; private Context mContext; + private PositionModel mCurrentGpsPosition; // 存储当前GPS位置(由Activity传入) + private Timer mDistanceTimer; // 定时计算距离的定时器 + private Handler mMainHandler; // 主线程Handler(更新UI必须在主线程) // 接口定义(不变) public interface OnDeleteClickListener { @@ -42,10 +52,57 @@ public class PositionAdapter extends RecyclerView.Adapter positionList) { this.mContext = context; this.mPositionList = positionList; + this.mMainHandler = new Handler(Looper.getMainLooper()); // 绑定主线程Looper + } + + // ---------------------- 新增:设置当前GPS位置(由Activity调用,传入实时GPS数据) ---------------------- + /** + * 设置当前GPS位置(Activity通过定位API获取后,调用此方法传入Adapter) + * @param currentGpsPosition 包含当前经度、纬度的PositionModel(memo可空,isEnable无需关注) + */ + public void setCurrentGpsPosition(PositionModel currentGpsPosition) { + this.mCurrentGpsPosition = currentGpsPosition; + // 首次设置GPS位置时,启动定时器(避免重复启动) + if (currentGpsPosition != null && mDistanceTimer == null) { + startDistanceRefreshTimer(); + } + } + + // ---------------------- 新增:启动5秒定时刷新距离的定时器 ---------------------- + private void startDistanceRefreshTimer() { + // 先停止原有定时器(防止重复创建) + stopDistanceRefreshTimer(); + + mDistanceTimer = new Timer(); + mDistanceTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + // 定时器在子线程执行,更新UI需切换到主线程 + mMainHandler.post(new Runnable() { + @Override + public void run() { + // 刷新所有列表项的距离显示(仅简单视图生效) + notifyItemRangeChanged(0, getItemCount()); + } + }); + } + }, 0, REFRESH_INTERVAL); // 0延迟启动,每REFRESH_INTERVAL(5秒)执行一次 + } + + // ---------------------- 新增:停止定时器(避免内存泄漏) ---------------------- + /** + * 停止定时器(必须在Activity销毁/Adapter不再使用时调用,如Activity的onDestroy) + */ + public void stopDistanceRefreshTimer() { + if (mDistanceTimer != null) { + mDistanceTimer.cancel(); + mDistanceTimer.purge(); + mDistanceTimer = null; + } } // 设置监听(不变) @@ -64,39 +121,67 @@ public class PositionAdapter extends RecyclerView.Adapter 90 || lat2 < -90 || lat2 > 90 + || lon1 < -180 || lon1 > 180 || lon2 < -180 || lon2 > 180) { + throw new IllegalArgumentException("经纬度值无效(纬度:-90~90,经度:-180~180)"); + } + + // 2. Haversine公式核心计算(将角度转为弧度,地球半径取平均半径6371km) + final double EARTH_RADIUS_KM = 6371; // 地球平均半径(单位:千米) + // 经纬度转为弧度(Math.toRadians:角度→弧度,公式计算需弧度值) + double radLat1 = Math.toRadians(lat1); + double radLat2 = Math.toRadians(lat2); + double radLon1 = Math.toRadians(lon1); + double radLon2 = Math.toRadians(lon2); + + // 计算纬度差、经度差 + double deltaLat = radLat2 - radLat1; + double deltaLon = radLon2 - radLon1; + + // Haversine公式:a = sin²(Δφ/2) + cos φ1 ⋅ cos φ2 ⋅ sin²(Δλ/2) + double a = Math.pow(Math.sin(deltaLat / 2), 2) + + Math.cos(radLat1) * Math.cos(radLat2) + * Math.pow(Math.sin(deltaLon / 2), 2); + // c = 2 ⋅ atan2(√a, √(1−a)) (计算圆心角) + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + // 距离 = 地球半径 × 圆心角(单位:千米) + double distanceKm = EARTH_RADIUS_KM * c; + + // 3. 单位转换+保留2位小数(避免精度冗余,符合日常使用习惯) + double distance; + if (isKilometer) { + distance = Math.round(distanceKm * 100.0) / 100.0; // 千米,保留2位小数 + } else { + distance = Math.round(distanceKm * 1000 * 100.0) / 100.0; // 米,保留2位小数(1km=1000m) + } + + return distance; + } } diff --git a/positions/src/main/res/layout/item_position_edit.xml b/positions/src/main/res/layout/item_position_edit.xml index d701268..b86f4d0 100644 --- a/positions/src/main/res/layout/item_position_edit.xml +++ b/positions/src/main/res/layout/item_position_edit.xml @@ -1,5 +1,5 @@ - + - + + android:layout_marginBottom="8dp"/> - + + + + + + + + + + + + + + + + + + + - +