添加实时距离计算功能
This commit is contained in:
		| @@ -1,8 +1,8 @@ | |||||||
| #Created by .winboll/winboll_app_build.gradle | #Created by .winboll/winboll_app_build.gradle | ||||||
| #Mon Sep 29 17:49:24 GMT 2025 | #Mon Sep 29 18:33:47 GMT 2025 | ||||||
| stageCount=3 | stageCount=3 | ||||||
| libraryProject= | libraryProject= | ||||||
| baseVersion=15.0 | baseVersion=15.0 | ||||||
| publishVersion=15.0.2 | publishVersion=15.0.2 | ||||||
| buildCount=12 | buildCount=15 | ||||||
| baseBetaVersion=15.0.3 | baseBetaVersion=15.0.3 | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ 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 当前位置实时显示 + 位置列表实时距离计算 | ||||||
|  */ |  */ | ||||||
| import android.Manifest; | import android.Manifest; | ||||||
| import android.app.AlertDialog; | import android.app.AlertDialog; | ||||||
| @@ -12,8 +12,6 @@ import android.content.pm.PackageManager; | |||||||
| import android.location.Location; | import android.location.Location; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.text.InputType; | import android.text.InputType; | ||||||
| import android.view.GestureDetector; |  | ||||||
| import android.view.MotionEvent; |  | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.widget.Button; | import android.widget.Button; | ||||||
| import android.widget.EditText; | import android.widget.EditText; | ||||||
| @@ -25,7 +23,6 @@ import androidx.core.app.ActivityCompat; | |||||||
| import androidx.recyclerview.widget.LinearLayoutManager; | import androidx.recyclerview.widget.LinearLayoutManager; | ||||||
| import androidx.recyclerview.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| import cc.winboll.studio.positions.R; | import cc.winboll.studio.positions.R; | ||||||
| import cc.winboll.studio.positions.activities.LocationActivity; |  | ||||||
| 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 com.google.android.gms.location.FusedLocationProviderClient; | 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.location.LocationServices; | ||||||
| import com.google.android.gms.tasks.OnSuccessListener; | import com.google.android.gms.tasks.OnSuccessListener; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import android.view.LayoutInflater; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 实时定位活动窗口: |  * 实时定位活动窗口: | ||||||
|  * 1. 申请定位必需权限(精确定位) |  * 1. 申请定位必需权限(精确定位) | ||||||
|  * 2. 初始化FusedLocationProviderClient(谷歌官方定位服务,兼容所有安卓版本) |  * 2. 初始化FusedLocationProviderClient(谷歌官方定位服务,兼容所有安卓版本) | ||||||
|  * 3. 实时监听位置变化,更新显示经度、纬度 |  * 3. 实时监听位置变化,更新显示经度、纬度 + 同步给Adapter计算实时距离 | ||||||
|  * 4. 右下角圆形悬浮按钮(含大写P字母,支持扩展功能) |  * 4. 右下角圆形悬浮按钮(含大写P字母,支持添加位置) | ||||||
|  |  * 5. 位置列表支持实时距离显示(按isEnableRealPositionDistance控制) | ||||||
|  */ |  */ | ||||||
| public class LocationActivity extends AppCompatActivity { | public class LocationActivity extends AppCompatActivity { | ||||||
|  |  | ||||||
|     public static final String TAG = "LocationActivity"; |     public static final String TAG = "LocationActivity"; | ||||||
|  |  | ||||||
|     // 1. 核心组件与常量定义(Java 7不支持final修饰后未立即初始化的变量,移除不必要final) |     // 1. 核心组件与常量定义(兼容Java 7,移除不必要final) | ||||||
|     private static final int REQUEST_LOCATION_PERMISSIONS = 1004; // 定位权限请求码 |     private static final int REQUEST_LOCATION_PERMISSIONS = 1004; // 定位权限请求码 | ||||||
|     private FusedLocationProviderClient fusedLocationClient; // 定位核心客户端 |     private FusedLocationProviderClient fusedLocationClient; // 定位核心客户端 | ||||||
|     private LocationCallback locationCallback; // 位置变化监听器 |     private LocationCallback locationCallback; // 位置变化监听器 | ||||||
|     private LocationRequest locationRequest; // 定位请求配置(频率、精度等) |     private LocationRequest locationRequest; // 定位请求配置(频率、精度等) | ||||||
|  |     private Location currentLocation; // 存储当前最新位置(用于同步给Adapter) | ||||||
|  |  | ||||||
|     // UI控件:Java 7不支持变量声明后直接换行注释,调整注释位置 |     // UI控件(Java 7显式声明+强制转换) | ||||||
|     private TextView tvLongitude; // 经度显示 |     private TextView tvLongitude; // 经度显示 | ||||||
|     private TextView tvLatitude;  // 纬度显示 |     private TextView tvLatitude;  // 纬度显示 | ||||||
|     private Button fabPButton;    // 右下角圆形悬浮按钮(P字母) |     private Button fabPButton;    // 右下角圆形悬浮按钮(P字母) | ||||||
| 	// UI控件:新增RecyclerView和Adapter |  | ||||||
|     private RecyclerView rvPositionList; // 位置列表(RecyclerView) |     private RecyclerView rvPositionList; // 位置列表(RecyclerView) | ||||||
|     private PositionAdapter positionAdapter; // 列表Adapter(数据联动) |     private PositionAdapter positionAdapter; // 列表Adapter(含实时距离逻辑) | ||||||
| 	ArrayList<PositionModel> mPositionList = new ArrayList<PositionModel>(); |     ArrayList<PositionModel> mPositionList = new ArrayList<PositionModel>(); // 位置数据集合 | ||||||
|  |  | ||||||
|     @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); | ||||||
|  |  | ||||||
|         // 绑定UI控件(Java 7无语法差异,保持逻辑) |         // 绑定UI控件(Java 7显式强制转换) | ||||||
|         tvLongitude = (TextView) findViewById(R.id.tv_longitude); // Java 7需显式强制转换 |         tvLongitude = (TextView) findViewById(R.id.tv_longitude); | ||||||
|         tvLatitude = (TextView) findViewById(R.id.tv_latitude); |         tvLatitude = (TextView) findViewById(R.id.tv_latitude); | ||||||
|         fabPButton = (Button) findViewById(R.id.fab_p_button); // 绑定悬浮按钮 |         fabPButton = (Button) findViewById(R.id.fab_p_button); | ||||||
|  |  | ||||||
|         // 初始化定位相关组件 |  | ||||||
|         initLocationConfig(); |  | ||||||
|         // 初始化悬浮按钮(设置点击事件) |  | ||||||
|         initFabPButton(); |  | ||||||
| 		 |  | ||||||
| 		// 新增1:绑定RecyclerView控件(显式强转,适配Java 7) |  | ||||||
|         rvPositionList = (RecyclerView) findViewById(R.id.rv_position_list); |         rvPositionList = (RecyclerView) findViewById(R.id.rv_position_list); | ||||||
|  |  | ||||||
|         // 新增2:初始化Adapter(传入mPositionList,确保数据联动) |         // 初始化核心逻辑:定位配置→悬浮按钮→列表Adapter→加载历史数据 | ||||||
|  |         initLocationConfig(); | ||||||
|  |         initFabPButton(); | ||||||
|         initRecyclerViewAndAdapter(); |         initRecyclerViewAndAdapter(); | ||||||
|  |  | ||||||
|         // 新增3:从本地加载历史位置数据(BaseBean的saveBeanList对应加载方法) |  | ||||||
|         loadHistoryPositions(); |         loadHistoryPositions(); | ||||||
|  |  | ||||||
|         // 检查并申请定位权限 |         // 检查并申请定位权限(权限通过后启动实时定位) | ||||||
|         if (checkLocationPermissions()) { |         if (checkLocationPermissions()) { | ||||||
|             startRealTimeLocation(); |             startRealTimeLocation(); | ||||||
|         } else { |         } 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() { |     private void initFabPButton() { | ||||||
|         // 悬浮按钮点击事件:替换Lambda为View.OnClickListener匿名内部类 |  | ||||||
|         fabPButton.setOnClickListener(new View.OnClickListener() { |         fabPButton.setOnClickListener(new View.OnClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public void onClick(View v) { | 				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); |  | ||||||
|  |  | ||||||
|         // 关键修改: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) |  | ||||||
|  |  | ||||||
|         // 初始化位置变化监听器:替换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() { |     private void initRecyclerViewAndAdapter() { | ||||||
| 		// 1. 初始化Adapter(传入Context和mPositionList,适配新构造函数) |         // 1. 配置RecyclerView布局管理器(垂直列表) | ||||||
|  |         LinearLayoutManager layoutManager = new LinearLayoutManager(this); | ||||||
|  |         layoutManager.setOrientation(LinearLayoutManager.VERTICAL); | ||||||
|  |         rvPositionList.setLayoutManager(layoutManager); | ||||||
|  |  | ||||||
|  |         // 2. 初始化Adapter(传入上下文和数据集合,兼容Java 7) | ||||||
|         positionAdapter = new PositionAdapter(this, mPositionList); |         positionAdapter = new PositionAdapter(this, mPositionList); | ||||||
|         rvPositionList.setAdapter(positionAdapter); |         rvPositionList.setAdapter(positionAdapter); | ||||||
|  |  | ||||||
| 		// 2. 设置删除监听(编辑视图点击“删除”时触发) |         // 3. 设置Adapter删除监听(删除列表项并同步本地数据) | ||||||
|         positionAdapter.setOnDeleteClickListener(new PositionAdapter.OnDeleteClickListener() { |         positionAdapter.setOnDeleteClickListener(new PositionAdapter.OnDeleteClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public void onDeleteClick(int position) { | 				public void onDeleteClick(int position) { | ||||||
| 					// 弹删除确认对话框(复用之前的showDeleteConfirmDialog方法) |  | ||||||
| 					showDeleteConfirmDialog(position); | 					showDeleteConfirmDialog(position); | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| 		// 3. 设置保存监听(编辑视图点击“确定”时触发,同步本地数据) |         // 4. 设置Adapter保存监听(编辑备注后同步本地数据) | ||||||
|         positionAdapter.setOnSaveClickListener(new PositionAdapter.OnSaveClickListener() { |         positionAdapter.setOnSaveClickListener(new PositionAdapter.OnSaveClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public void onSaveClick() { | 				public void onSaveClick() { | ||||||
| 					// 编辑后保存到本地 |  | ||||||
| 					try { | 					try { | ||||||
| 						PositionModel.saveBeanList(LocationActivity.this, mPositionList, PositionModel.class); | 						PositionModel.saveBeanList(LocationActivity.this, mPositionList, PositionModel.class); | ||||||
| 					} catch (Exception e) { | 					} catch (Exception e) { | ||||||
| @@ -169,23 +177,114 @@ public class LocationActivity extends AppCompatActivity { | |||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| 		// 4. 原有布局管理器设置(保留不变) |  | ||||||
| 		RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this); |  | ||||||
| 		((LinearLayoutManager) layoutManager).setOrientation(LinearLayoutManager.VERTICAL); |  | ||||||
| 		rvPositionList.setLayoutManager(layoutManager); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
| // 保留原删除确认对话框(无修改,编辑视图点击“删除”会触发此方法) |  | ||||||
|  |     /** | ||||||
|  |      * 从本地加载历史位置数据(基于BaseBean的持久化逻辑) | ||||||
|  |      */ | ||||||
|  |     private void loadHistoryPositions() { | ||||||
|  |         try { | ||||||
|  |             ArrayList<PositionModel> historyList = new ArrayList<PositionModel>(); | ||||||
|  |             // 调用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) { |     private void showDeleteConfirmDialog(final int position) { | ||||||
|         AlertDialog.Builder deleteDialogBuilder = new AlertDialog.Builder(this); |         AlertDialog.Builder deleteDialogBuilder = new AlertDialog.Builder(this); | ||||||
|         deleteDialogBuilder.setTitle("删除位置记录") |         deleteDialogBuilder.setTitle("删除位置记录") | ||||||
|             .setMessage("确定要删除这条位置记录吗?(删除后不可恢复)") | 			.setMessage("确定要删除这条位置吗?(删除后不可恢复)") | ||||||
| 			.setPositiveButton("确定", new DialogInterface.OnClickListener() { | 			.setPositiveButton("确定", new DialogInterface.OnClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public void onClick(DialogInterface dialog, int which) { | 				public void onClick(DialogInterface dialog, int which) { | ||||||
|  | 					// 从列表移除 + 保存本地 | ||||||
| 					positionAdapter.removePosition(position); | 					positionAdapter.removePosition(position); | ||||||
|                     // 删除后同步本地数据 |  | ||||||
| 					try { | 					try { | ||||||
| 						PositionModel.saveBeanList(LocationActivity.this, mPositionList, PositionModel.class); | 						PositionModel.saveBeanList(LocationActivity.this, mPositionList, PositionModel.class); | ||||||
| 						Toast.makeText(LocationActivity.this, "删除成功", Toast.LENGTH_SHORT).show(); | 						Toast.makeText(LocationActivity.this, "删除成功", Toast.LENGTH_SHORT).show(); | ||||||
| @@ -205,43 +304,20 @@ public class LocationActivity extends AppCompatActivity { | |||||||
|         deleteDialogBuilder.show(); |         deleteDialogBuilder.show(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| 	/** |  | ||||||
|      * 从本地加载历史位置数据: |  | ||||||
|      * 基于BaseBean的saveBeanList方法,对应加载方法为readBeanList(通用持久化逻辑) |  | ||||||
|      */ |  | ||||||
|     private void loadHistoryPositions() { |  | ||||||
|         try { |  | ||||||
|             // 读取本地保存的PositionModel列表(参数:上下文、数据模型类) |  | ||||||
|             ArrayList<PositionModel> historyList = new ArrayList<PositionModel>(); |  | ||||||
| 			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语法兼容) |      * 检查定位权限(仅精确定位权限,满足实时距离计算需求) | ||||||
|      */ |      */ | ||||||
|     private boolean checkLocationPermissions() { |     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; | 			== PackageManager.PERMISSION_GRANTED; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 申请定位必需权限(逻辑不变,Java 7语法兼容) |      * 申请定位权限 | ||||||
|      */ |      */ | ||||||
|     private void requestLocationPermissions() { |     private void requestLocationPermissions() { | ||||||
|         // Java 7数组初始化语法无差异,保持原逻辑 |  | ||||||
|         String[] permissions = new String[]{Manifest.permission.ACCESS_FINE_LOCATION}; |         String[] permissions = new String[]{Manifest.permission.ACCESS_FINE_LOCATION}; | ||||||
|         ActivityCompat.requestPermissions( |         ActivityCompat.requestPermissions( | ||||||
| 			this, | 			this, | ||||||
| @@ -252,23 +328,21 @@ public class LocationActivity extends AppCompatActivity { | |||||||
|  |  | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 启动实时定位:Java 7不支持Lambda,替换OnSuccessListener为匿名内部类 |      * 启动实时定位(获取当前位置 + 监听位置变化) | ||||||
|      */ |      */ | ||||||
|     private void startRealTimeLocation() { |     private void startRealTimeLocation() { | ||||||
|         // 权限兜底检查(逻辑不变) |         if (!checkLocationPermissions()) { | ||||||
|         if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)  |             Toast.makeText(this, "定位权限未授予", Toast.LENGTH_SHORT).show(); | ||||||
| 			!= PackageManager.PERMISSION_GRANTED) { |  | ||||||
|             Toast.makeText(this, "定位权限未授予,无法启动定位", Toast.LENGTH_SHORT).show(); |  | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 1. 先获取一次当前位置:替换Lambda为OnSuccessListener匿名内部类(Java 7核心修改) |         // 1. 先获取一次当前位置(初始化页面显示) | ||||||
|         fusedLocationClient.getLastLocation() |         fusedLocationClient.getLastLocation() | ||||||
| 			.addOnSuccessListener(this, new OnSuccessListener<Location>() { | 			.addOnSuccessListener(this, new OnSuccessListener<Location>() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public void onSuccess(Location location) { | 				public void onSuccess(Location location) { | ||||||
| 					// 成功获取位置后的逻辑(无语法差异) |  | ||||||
| 					if (location != null) { | 					if (location != null) { | ||||||
|  | 						currentLocation = location; | ||||||
| 						tvLongitude.setText(String.format("当前经度:%.6f", location.getLongitude())); | 						tvLongitude.setText(String.format("当前经度:%.6f", location.getLongitude())); | ||||||
| 						tvLatitude.setText(String.format("当前纬度:%.6f", location.getLatitude())); | 						tvLatitude.setText(String.format("当前纬度:%.6f", location.getLatitude())); | ||||||
| 					} else { | 					} else { | ||||||
| @@ -278,35 +352,35 @@ public class LocationActivity extends AppCompatActivity { | |||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
|         // 2. 注册监听器,接收实时位置更新(逻辑不变,Java 7语法兼容) |         // 2. 注册位置监听器(实时更新位置) | ||||||
|         fusedLocationClient.requestLocationUpdates( |         fusedLocationClient.requestLocationUpdates( | ||||||
| 			locationRequest, | 			locationRequest, | ||||||
| 			locationCallback, | 			locationCallback, | ||||||
| 			getMainLooper() // 在主线程更新UI(避免线程异常) | 			getMainLooper() // 主线程更新UI,避免线程异常 | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 处理权限申请结果(逻辑不变,Java 7语法兼容) |      * 处理权限申请结果 | ||||||
|      */ |      */ | ||||||
|     @Override |     @Override | ||||||
|     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { |     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { | ||||||
|         super.onRequestPermissionsResult(requestCode, permissions, grantResults); |         super.onRequestPermissionsResult(requestCode, permissions, grantResults); | ||||||
|         if (requestCode == REQUEST_LOCATION_PERMISSIONS) { |         if (requestCode == REQUEST_LOCATION_PERMISSIONS) { | ||||||
|             boolean allGranted = true; |             boolean isGranted = false; | ||||||
|             // Java 7增强for循环语法无差异 |  | ||||||
|             for (int result : grantResults) { |             for (int result : grantResults) { | ||||||
|                 if (result != PackageManager.PERMISSION_GRANTED) { |                 if (result == PackageManager.PERMISSION_GRANTED) { | ||||||
|                     allGranted = false; |                     isGranted = true; | ||||||
|                     break; |                     break; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (allGranted) { |             if (isGranted) { | ||||||
|                 startRealTimeLocation(); |                 startRealTimeLocation(); // 权限通过,启动定位 | ||||||
|             } else { |             } else { | ||||||
|                 Toast.makeText(this, "定位权限被拒绝,无法显示位置信息", Toast.LENGTH_SHORT).show(); |                 // 权限拒绝,提示并显示无权限状态 | ||||||
|  |                 Toast.makeText(this, "定位权限被拒绝,无法显示实时位置和距离", Toast.LENGTH_SHORT).show(); | ||||||
|                 tvLongitude.setText("当前经度:无权限"); |                 tvLongitude.setText("当前经度:无权限"); | ||||||
|                 tvLatitude.setText("当前纬度:无权限"); |                 tvLatitude.setText("当前纬度:无权限"); | ||||||
|             } |             } | ||||||
| @@ -315,18 +389,23 @@ public class LocationActivity extends AppCompatActivity { | |||||||
|  |  | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 活动销毁时停止定位监听(逻辑不变,Java 7语法兼容) |      * 活动销毁:停止定位 + 停止距离刷新定时器(避免内存泄漏) | ||||||
|      */ |      */ | ||||||
|     @Override |     @Override | ||||||
|     protected void onDestroy() { |     protected void onDestroy() { | ||||||
|         super.onDestroy(); |         super.onDestroy(); | ||||||
|  |  | ||||||
| 		// 1. 停止定位监听(原有逻辑,保留) |         // 1. 停止定位监听(释放定位资源) | ||||||
|         if (fusedLocationClient != null && locationCallback != null) { |         if (fusedLocationClient != null && locationCallback != null) { | ||||||
|             fusedLocationClient.removeLocationUpdates(locationCallback); |             fusedLocationClient.removeLocationUpdates(locationCallback); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 2. 补充:销毁前同步一次数据(确保最后修改的记录被保存) |         // 2. 停止Adapter的距离刷新定时器(关键:避免Timer持有Context导致内存泄漏) | ||||||
|  |         if (positionAdapter != null) { | ||||||
|  |             positionAdapter.stopDistanceRefreshTimer(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 3. 最后同步一次数据(确保所有修改保存) | ||||||
|         try { |         try { | ||||||
|             if (mPositionList != null && !mPositionList.isEmpty()) { |             if (mPositionList != null && !mPositionList.isEmpty()) { | ||||||
|                 PositionModel.saveBeanList(this, mPositionList, PositionModel.class); |                 PositionModel.saveBeanList(this, mPositionList, PositionModel.class); | ||||||
| @@ -336,84 +415,13 @@ public class LocationActivity extends AppCompatActivity { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| 	/** |  | ||||||
|      * 弹出“当前位置备注”输入对话框: |  | ||||||
|      * 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(解决不同屏幕分辨率下输入框内边距不一致问题) |      * 辅助工具:dp转px(适配不同屏幕分辨率) | ||||||
|      * @param dpValue 要转换的dp值(如16dp) |  | ||||||
|      * @return 转换后的px值(适配当前设备屏幕密度) |  | ||||||
|      */ |      */ | ||||||
|     private int dip2px(float dpValue) { |     private int dip2px(float dpValue) { | ||||||
|         // 获取设备屏幕密度(density),dp转px公式:px = dp * density + 0.5f(+0.5f用于四舍五入) |  | ||||||
|         final float scale = getResources().getDisplayMetrics().density; |         final float scale = getResources().getDisplayMetrics().density; | ||||||
|         return (int) (dpValue * scale + 0.5f); |         return (int) (dpValue * scale + 0.5f); // +0.5f用于四舍五入,确保精度 | ||||||
|     } |     } | ||||||
| 	 |  | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,9 +3,11 @@ package cc.winboll.studio.positions.adapters; | |||||||
| /** | /** | ||||||
|  * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> |  * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> | ||||||
|  * @Date 2025/09/29 20:25 |  * @Date 2025/09/29 20:25 | ||||||
|  * @Describe 位置数据适配器(支持简单视图/编辑视图切换) |  * @Describe 位置数据适配器(支持简单视图/编辑视图切换 + 实时距离开关控制) | ||||||
|  */ |  */ | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
|  | import android.os.Handler; | ||||||
|  | import android.os.Looper; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.MenuInflater; | import android.view.MenuInflater; | ||||||
| import android.view.MenuItem; | import android.view.MenuItem; | ||||||
| @@ -14,6 +16,8 @@ import android.view.ViewGroup; | |||||||
| import android.widget.Button; | import android.widget.Button; | ||||||
| import android.widget.EditText; | import android.widget.EditText; | ||||||
| import android.widget.PopupMenu; | import android.widget.PopupMenu; | ||||||
|  | import android.widget.RadioButton; | ||||||
|  | import android.widget.RadioGroup; | ||||||
| import android.widget.TextView; | import android.widget.TextView; | ||||||
| import android.widget.Toast; | import android.widget.Toast; | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| @@ -21,14 +25,20 @@ import androidx.recyclerview.widget.RecyclerView; | |||||||
| import cc.winboll.studio.positions.R; | import cc.winboll.studio.positions.R; | ||||||
| import cc.winboll.studio.positions.models.PositionModel; | import cc.winboll.studio.positions.models.PositionModel; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
|  | import java.util.Timer; | ||||||
|  | import java.util.TimerTask; | ||||||
|  |  | ||||||
| public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { | public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { | ||||||
|     public static final String TAG = "PositionAdapter"; |     public static final String TAG = "PositionAdapter"; | ||||||
|     private static final int VIEW_TYPE_SIMPLE = 1;  // 简单视图 |     private static final int VIEW_TYPE_SIMPLE = 1;  // 简单视图 | ||||||
|     private static final int VIEW_TYPE_EDIT = 2;    // 编辑视图 |     private static final int VIEW_TYPE_EDIT = 2;    // 编辑视图 | ||||||
|  |     private static final long REFRESH_INTERVAL = 5000; // 实时刷新间隔:5秒(5000毫秒) | ||||||
|  |  | ||||||
|     private ArrayList<PositionModel> mPositionList; |     private ArrayList<PositionModel> mPositionList; | ||||||
|     private Context mContext; |     private Context mContext; | ||||||
|  |     private PositionModel mCurrentGpsPosition; // 存储当前GPS位置(由Activity传入) | ||||||
|  |     private Timer mDistanceTimer; // 定时计算距离的定时器 | ||||||
|  |     private Handler mMainHandler; // 主线程Handler(更新UI必须在主线程) | ||||||
|  |  | ||||||
|     // 接口定义(不变) |     // 接口定义(不变) | ||||||
|     public interface OnDeleteClickListener { |     public interface OnDeleteClickListener { | ||||||
| @@ -42,10 +52,57 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
|     private OnDeleteClickListener mOnDeleteClickListener; |     private OnDeleteClickListener mOnDeleteClickListener; | ||||||
|     private OnSaveClickListener mOnSaveClickListener; |     private OnSaveClickListener mOnSaveClickListener; | ||||||
|  |  | ||||||
|     // 构造函数(不变) |     // 构造函数(新增:初始化主线程Handler) | ||||||
|     public PositionAdapter(Context context, ArrayList<PositionModel> positionList) { |     public PositionAdapter(Context context, ArrayList<PositionModel> positionList) { | ||||||
|         this.mContext = context; |         this.mContext = context; | ||||||
|         this.mPositionList = positionList; |         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<RecyclerView.ViewHolde | |||||||
|         return model.isSimpleView() ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT; |         return model.isSimpleView() ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // 创建ViewHolder(不变,布局已在xml中修改) |     // 创建ViewHolder(修改:EditViewHolder新增单选框组引用;SimpleViewHolder不变) | ||||||
|     @NonNull |     @NonNull | ||||||
|     @Override |     @Override | ||||||
|     public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { |     public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { | ||||||
|         LayoutInflater inflater = LayoutInflater.from(mContext); |         LayoutInflater inflater = LayoutInflater.from(mContext); | ||||||
|         if (viewType == VIEW_TYPE_SIMPLE) { |         if (viewType == VIEW_TYPE_SIMPLE) { | ||||||
|  |             // 简单视图:绑定距离显示控件(不变) | ||||||
|             View view = inflater.inflate(R.layout.item_position_simple, parent, false); |             View view = inflater.inflate(R.layout.item_position_simple, parent, false); | ||||||
|             return new SimpleViewHolder(view); |             return new SimpleViewHolder(view); | ||||||
|         } else { |         } else { | ||||||
|  |             // 编辑视图:绑定新增的单选框组(关键修改) | ||||||
|             View view = inflater.inflate(R.layout.item_position_edit, parent, false); |             View view = inflater.inflate(R.layout.item_position_edit, parent, false); | ||||||
|             return new EditViewHolder(view); |             return new EditViewHolder(view); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // 绑定数据(核心修改:编辑视图添加取消按钮逻辑) |     // 绑定数据(核心修改:编辑视图添加单选框状态绑定) | ||||||
|     @Override |     @Override | ||||||
|     public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { |     public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { | ||||||
|         PositionModel model = mPositionList.get(position); |         PositionModel model = mPositionList.get(position); | ||||||
|         if (holder instanceof SimpleViewHolder) { |         if (holder instanceof SimpleViewHolder) { | ||||||
|             bindSimpleView((SimpleViewHolder) holder, model, position); |             bindSimpleView((SimpleViewHolder) holder, model, position); | ||||||
|         } else if (holder instanceof EditViewHolder) { |         } else if (holder instanceof EditViewHolder) { | ||||||
|             // 编辑视图绑定:新增取消按钮逻辑 |  | ||||||
|             bindEditView((EditViewHolder) holder, model, position); |             bindEditView((EditViewHolder) holder, model, position); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // 简单视图绑定(不变) |     // ---------------------- 简单视图绑定(不变:实时距离显示逻辑保持原样) ---------------------- | ||||||
|     private void bindSimpleView(SimpleViewHolder holder, final PositionModel model, final int position) { |     private void bindSimpleView(SimpleViewHolder holder, final PositionModel model, final int position) { | ||||||
|  |         // 原有:绑定经纬度、备注 | ||||||
|         holder.tvSimpleLongitude.setText(String.format("经度:%.6f", model.getLongitude())); |         holder.tvSimpleLongitude.setText(String.format("经度:%.6f", model.getLongitude())); | ||||||
|         holder.tvSimpleLatitude.setText(String.format("纬度:%.6f", model.getLatitude())); |         holder.tvSimpleLatitude.setText(String.format("纬度:%.6f", model.getLatitude())); | ||||||
|         String memo = model.getMemo().trim().isEmpty() ? "无备注" : model.getMemo(); |         String memo = model.getMemo().trim().isEmpty() ? "无备注" : model.getMemo(); | ||||||
|         holder.tvSimpleMemo.setText(String.format("备注:%s", memo)); |         holder.tvSimpleMemo.setText(String.format("备注:%s", memo)); | ||||||
|  |  | ||||||
|  |         // 实时距离显示逻辑(根据isEnableRealPositionDistance判断) | ||||||
|  |         if (model.isEnableRealPositionDistance()) { | ||||||
|  |             if (mCurrentGpsPosition != null) { | ||||||
|  |                 try { | ||||||
|  |                     // 调用PositionModel静态方法计算距离(米/千米自适应) | ||||||
|  |                     double distanceM = PositionModel.calculatePositionDistance( | ||||||
|  | 						mCurrentGpsPosition, model, false); | ||||||
|  |                     String distanceText; | ||||||
|  |                     if (distanceM < 1000) { | ||||||
|  |                         distanceText = String.format("实时距离:%.1f 米", distanceM); | ||||||
|  |                     } else { | ||||||
|  |                         double distanceKm = distanceM / 1000; | ||||||
|  |                         distanceText = String.format("实时距离:%.1f 千米", distanceKm); | ||||||
|  |                     } | ||||||
|  |                     holder.tvSimpleRealDistance.setText(distanceText); | ||||||
|  |                 } catch (IllegalArgumentException e) { | ||||||
|  |                     holder.tvSimpleRealDistance.setText("实时距离:数据无效"); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 holder.tvSimpleRealDistance.setText("实时距离:等待GPS定位"); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             holder.tvSimpleRealDistance.setText("实时距离未启用"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 长按弹出编辑菜单(不变) | ||||||
|         holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { |         holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public boolean onLongClick(View v) { | 				public boolean onLongClick(View v) { | ||||||
| @@ -121,15 +206,24 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
| 			}); | 			}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // 编辑视图绑定(核心修改:新增取消按钮+调整按钮顺序) |     // ---------------------- 核心修改:编辑视图绑定(新增单选框状态同步+保存) ---------------------- | ||||||
|     private void bindEditView(final EditViewHolder holder, final PositionModel model, final int position) { |     private void bindEditView(final EditViewHolder holder, final PositionModel model, final int position) { | ||||||
|         // 1. 绑定数据(不变) |         // 1. 原有:绑定经纬度、备注(不变) | ||||||
|         holder.tvEditLongitude.setText(String.format("经度:%.6f", model.getLongitude())); |         holder.tvEditLongitude.setText(String.format("经度:%.6f", model.getLongitude())); | ||||||
|         holder.tvEditLatitude.setText(String.format("纬度:%.6f", model.getLatitude())); |         holder.tvEditLatitude.setText(String.format("纬度:%.6f", model.getLatitude())); | ||||||
|         holder.etEditMemo.setText(model.getMemo()); |         holder.etEditMemo.setText(model.getMemo()); | ||||||
|         holder.etEditMemo.setSelection(model.getMemo().length()); |         holder.etEditMemo.setSelection(model.getMemo().length()); | ||||||
|  |  | ||||||
|         // 2. 按钮逻辑(顺序:①删除→②取消→③确定) |         // 2. 新增:绑定实时距离开关状态(与model.isEnableRealPositionDistance同步) | ||||||
|  |         if (model.isEnableRealPositionDistance()) { | ||||||
|  |             // 启用实时距离→选中“启用”单选框 | ||||||
|  |             holder.rgRealDistanceSwitch.check(R.id.rb_enable); | ||||||
|  |         } else { | ||||||
|  |             // 禁用实时距离→选中“禁用”单选框(默认状态) | ||||||
|  |             holder.rgRealDistanceSwitch.check(R.id.rb_disable); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 3. 按钮逻辑(不变+新增单选框保存) | ||||||
|         // ① 删除按钮(最左侧,逻辑不变) |         // ① 删除按钮(最左侧,逻辑不变) | ||||||
|         holder.btnEditDelete.setOnClickListener(new View.OnClickListener() { |         holder.btnEditDelete.setOnClickListener(new View.OnClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| @@ -140,73 +234,95 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
|         // ② 新增:取消按钮(中间,不保存→切回简单视图) |         // ② 取消按钮(中间,逻辑不变:不保存修改,切回简单视图) | ||||||
|         holder.btnEditCancel.setOnClickListener(new View.OnClickListener() { |         holder.btnEditCancel.setOnClickListener(new View.OnClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public void onClick(View v) { | 				public void onClick(View v) { | ||||||
| 					// 关键:不保存任何修改,直接设置为简单视图 |  | ||||||
| 					model.setIsSimpleView(true); | 					model.setIsSimpleView(true); | ||||||
| 					// 局部刷新当前项→切换回简单视图 |  | ||||||
| 					notifyItemChanged(position); | 					notifyItemChanged(position); | ||||||
| 					// (可选)清空输入框焦点,避免残留光标 |  | ||||||
| 					holder.etEditMemo.clearFocus(); | 					holder.etEditMemo.clearFocus(); | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
|         // ③ 确定按钮(最右侧,逻辑不变) |         // ③ 确定按钮(最右侧,核心修改:保存备注+保存单选框状态) | ||||||
|         holder.btnEditConfirm.setOnClickListener(new View.OnClickListener() { |         holder.btnEditConfirm.setOnClickListener(new View.OnClickListener() { | ||||||
| 				@Override | 				@Override | ||||||
| 				public void onClick(View v) { | 				public void onClick(View v) { | ||||||
|  | 					// 原有:保存备注内容 | ||||||
| 					String newMemo = holder.etEditMemo.getText().toString().trim(); | 					String newMemo = holder.etEditMemo.getText().toString().trim(); | ||||||
| 					model.setMemo(newMemo.isEmpty() ? "无备注" : newMemo); | 					model.setMemo(newMemo.isEmpty() ? "无备注" : newMemo); | ||||||
|  |  | ||||||
|  | 					// 新增:保存实时距离开关状态(根据单选框选中项更新model属性) | ||||||
|  | 					int checkedRadioId = holder.rgRealDistanceSwitch.getCheckedRadioButtonId(); | ||||||
|  | 					if (checkedRadioId == R.id.rb_enable) { | ||||||
|  | 						// 选中“启用”→设置isEnableRealPositionDistance为true | ||||||
|  | 						model.setIsEnableRealPositionDistance(true); | ||||||
|  | 					} else { | ||||||
|  | 						// 选中“禁用”(含默认)→设置isEnableRealPositionDistance为false | ||||||
|  | 						model.setIsEnableRealPositionDistance(false); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// 原有:切回简单视图+刷新列表+通知保存 | ||||||
| 					model.setIsSimpleView(true); | 					model.setIsSimpleView(true); | ||||||
| 					notifyItemChanged(position); | 					notifyItemChanged(position); // 刷新当前项,实时更新距离显示状态 | ||||||
|  |  | ||||||
| 					if (mOnSaveClickListener != null) { | 					if (mOnSaveClickListener != null) { | ||||||
| 						mOnSaveClickListener.onSaveClick(); | 						mOnSaveClickListener.onSaveClick(); // 通知Activity保存到本地 | ||||||
| 					} | 					} | ||||||
| 					Toast.makeText(mContext, "备注已保存", Toast.LENGTH_SHORT).show(); | 					Toast.makeText(mContext, "备注及实时距离设置已保存", Toast.LENGTH_SHORT).show(); | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // ---------------------- ViewHolder 定义(核心修改:新增取消按钮引用) ---------------------- |     // ---------------------- ViewHolder 定义(核心修改:EditViewHolder新增单选框组引用) ---------------------- | ||||||
|     /** |     /** | ||||||
|      * 简单视图Holder(不变) |      * 简单视图Holder(不变:含实时距离显示控件) | ||||||
|      */ |      */ | ||||||
|     public static class SimpleViewHolder extends RecyclerView.ViewHolder { |     public static class SimpleViewHolder extends RecyclerView.ViewHolder { | ||||||
|         TextView tvSimpleLongitude; |         TextView tvSimpleLongitude; | ||||||
|         TextView tvSimpleLatitude; |         TextView tvSimpleLatitude; | ||||||
|         TextView tvSimpleMemo; |         TextView tvSimpleMemo; | ||||||
|  |         TextView tvSimpleRealDistance; // 实时距离显示控件 | ||||||
|  |  | ||||||
|         public SimpleViewHolder(@NonNull View itemView) { |         public SimpleViewHolder(@NonNull View itemView) { | ||||||
|             super(itemView); |             super(itemView); | ||||||
|  |             // 绑定原有控件(不变) | ||||||
|             tvSimpleLongitude = (TextView) itemView.findViewById(R.id.tv_simple_longitude); |             tvSimpleLongitude = (TextView) itemView.findViewById(R.id.tv_simple_longitude); | ||||||
|             tvSimpleLatitude = (TextView) itemView.findViewById(R.id.tv_simple_latitude); |             tvSimpleLatitude = (TextView) itemView.findViewById(R.id.tv_simple_latitude); | ||||||
|             tvSimpleMemo = (TextView) itemView.findViewById(R.id.tv_simple_memo); |             tvSimpleMemo = (TextView) itemView.findViewById(R.id.tv_simple_memo); | ||||||
|  |             // 绑定实时距离显示控件(不变) | ||||||
|  |             tvSimpleRealDistance = (TextView) itemView.findViewById(R.id.tv_simple_real_distance); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 编辑视图Holder(核心修改:新增btnEditCancel引用) |      * 编辑视图Holder(核心修改:新增实时距离开关单选框组及子项引用) | ||||||
|      */ |      */ | ||||||
|     public static class EditViewHolder extends RecyclerView.ViewHolder { |     public static class EditViewHolder extends RecyclerView.ViewHolder { | ||||||
|         TextView tvEditLongitude; |         TextView tvEditLongitude; | ||||||
|         TextView tvEditLatitude; |         TextView tvEditLatitude; | ||||||
|         EditText etEditMemo; |         EditText etEditMemo; | ||||||
|         Button btnEditDelete;    // 删除按钮(已调整到最左) |         Button btnEditDelete; | ||||||
|         Button btnEditCancel;    // 新增:取消按钮(中间) |         Button btnEditCancel; | ||||||
|         Button btnEditConfirm;   // 确定按钮(最右) |         Button btnEditConfirm; | ||||||
|  |         // 新增:实时距离开关单选框组及子项(与item_position_edit.xml对应) | ||||||
|  |         RadioGroup rgRealDistanceSwitch; // 单选框组(控制二选一) | ||||||
|  |         RadioButton rbDisable;          // “禁用”单选框(对应isEnable=false) | ||||||
|  |         RadioButton rbEnable;           // “启用”单选框(对应isEnable=true) | ||||||
|  |  | ||||||
|         public EditViewHolder(@NonNull View itemView) { |         public EditViewHolder(@NonNull View itemView) { | ||||||
|             super(itemView); |             super(itemView); | ||||||
|             // 绑定控件(顺序与xml一致:删除→取消→确定) |             // 绑定原有控件(不变,显式强转适配Java 7) | ||||||
|             tvEditLongitude = (TextView) itemView.findViewById(R.id.tv_edit_longitude); |             tvEditLongitude = (TextView) itemView.findViewById(R.id.tv_edit_longitude); | ||||||
|             tvEditLatitude = (TextView) itemView.findViewById(R.id.tv_edit_latitude); |             tvEditLatitude = (TextView) itemView.findViewById(R.id.tv_edit_latitude); | ||||||
|             etEditMemo = (EditText) itemView.findViewById(R.id.et_edit_memo); |             etEditMemo = (EditText) itemView.findViewById(R.id.et_edit_memo); | ||||||
|             btnEditDelete = (Button) itemView.findViewById(R.id.btn_edit_delete);  // 最左 |             btnEditDelete = (Button) itemView.findViewById(R.id.btn_edit_delete); | ||||||
|             btnEditCancel = (Button) itemView.findViewById(R.id.btn_edit_cancel);  // 中间(新增) |             btnEditCancel = (Button) itemView.findViewById(R.id.btn_edit_cancel); | ||||||
|             btnEditConfirm = (Button) itemView.findViewById(R.id.btn_edit_confirm); // 最右 |             btnEditConfirm = (Button) itemView.findViewById(R.id.btn_edit_confirm); | ||||||
|  |             // 新增:绑定单选框组及子项(显式强转适配Java 7,与布局ID严格对应) | ||||||
|  |             rgRealDistanceSwitch = (RadioGroup) itemView.findViewById(R.id.rg_real_distance_switch); | ||||||
|  |             rbDisable = (RadioButton) itemView.findViewById(R.id.rb_disable); | ||||||
|  |             rbEnable = (RadioButton) itemView.findViewById(R.id.rb_enable); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -19,19 +19,31 @@ public class PositionModel extends BaseBean { | |||||||
| 	double latitude; | 	double latitude; | ||||||
| 	// 位置信息备注 | 	// 位置信息备注 | ||||||
| 	String memo; | 	String memo; | ||||||
|  | 	// 是否启用实时距离计算 | ||||||
|  | 	boolean isEnableRealPositionDistance; | ||||||
| 	// 是否是简单视图 | 	// 是否是简单视图 | ||||||
| 	boolean isSimpleView = true; | 	boolean isSimpleView = true; | ||||||
|  |  | ||||||
| 	public PositionModel(double longitude, double latitude, String memo) { | 	public PositionModel(double longitude, double latitude, String memo, boolean isEnableRealPositionDistance) { | ||||||
| 		this.longitude = longitude; | 		this.longitude = longitude; | ||||||
| 		this.latitude = latitude; | 		this.latitude = latitude; | ||||||
| 		this.memo = memo; | 		this.memo = memo; | ||||||
|  | 		this.isEnableRealPositionDistance = isEnableRealPositionDistance; | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	public PositionModel() { | 	public PositionModel() { | ||||||
| 		this.longitude = 0.0f; | 		this.longitude = 0.0f; | ||||||
| 		this.latitude = 0.0f; | 		this.latitude = 0.0f; | ||||||
| 		this.memo = ""; | 		this.memo = ""; | ||||||
|  | 		this.isEnableRealPositionDistance = false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public void setIsEnableRealPositionDistance(boolean isEnableRealPositionDistance) { | ||||||
|  | 		this.isEnableRealPositionDistance = isEnableRealPositionDistance; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public boolean isEnableRealPositionDistance() { | ||||||
|  | 		return isEnableRealPositionDistance; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public void setIsSimpleView(boolean isSimpleView) { | 	public void setIsSimpleView(boolean isSimpleView) { | ||||||
| @@ -77,6 +89,7 @@ public class PositionModel extends BaseBean { | |||||||
|         jsonWriter.name("longitude").value(getLongitude()); |         jsonWriter.name("longitude").value(getLongitude()); | ||||||
|         jsonWriter.name("latitude").value(getLatitude()); |         jsonWriter.name("latitude").value(getLatitude()); | ||||||
|         jsonWriter.name("memo").value(getMemo()); |         jsonWriter.name("memo").value(getMemo()); | ||||||
|  |         jsonWriter.name("isEnableRealPositionDistance").value(isEnableRealPositionDistance()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -88,6 +101,8 @@ public class PositionModel extends BaseBean { | |||||||
|                 setLatitude(jsonReader.nextDouble()); |                 setLatitude(jsonReader.nextDouble()); | ||||||
|             } else if (name.equals("memo")) { |             } else if (name.equals("memo")) { | ||||||
|                 setMemo(jsonReader.nextString()); |                 setMemo(jsonReader.nextString()); | ||||||
|  |             } else if (name.equals("isEnableRealPositionDistance")) { | ||||||
|  |                 setIsEnableRealPositionDistance(jsonReader.nextBoolean()); | ||||||
|             } else { |             } else { | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
| @@ -109,4 +124,57 @@ public class PositionModel extends BaseBean { | |||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  | 	/** | ||||||
|  | 	 * 计算两个位置之间的直线距离(地球表面最短距离,基于Haversine公式) | ||||||
|  | 	 * @param position1 第一个位置(PositionModel对象,含longitude/latitude) | ||||||
|  | 	 * @param position2 第二个位置(PositionModel对象,含longitude/latitude) | ||||||
|  | 	 * @param isKilometer 是否返回千米单位:true→千米(km),false→米(m) | ||||||
|  | 	 * @return 两个位置间的距离(保留2位小数,单位由isKilometer决定) | ||||||
|  | 	 */ | ||||||
|  | 	public static double calculatePositionDistance(PositionModel position1, PositionModel position2, boolean isKilometer) { | ||||||
|  | 		// 1. 校验参数(避免空指针/无效经纬度,防止计算异常) | ||||||
|  | 		if (position1 == null || position2 == null) { | ||||||
|  | 			throw new IllegalArgumentException("位置对象不能为null"); | ||||||
|  | 		} | ||||||
|  | 		double lon1 = position1.getLongitude(); | ||||||
|  | 		double lat1 = position1.getLatitude(); | ||||||
|  | 		double lon2 = position2.getLongitude(); | ||||||
|  | 		double lat2 = position2.getLatitude(); | ||||||
|  | 		// 经纬度范围校验(纬度:-90~90,经度:-180~180,超出则视为无效值) | ||||||
|  | 		if (lat1 < -90 || lat1 > 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; | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <!-- 编辑视图:按钮顺序改为【删除(左)→取消→确定(右)】,新增取消按钮 --> | <!-- 编辑视图:新增实时距离开关(单选框组),位于备注输入框与按钮区之间 --> | ||||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     android:layout_width="match_parent" |     android:layout_width="match_parent" | ||||||
|     android:layout_height="wrap_content" |     android:layout_height="wrap_content" | ||||||
| @@ -32,7 +32,7 @@ | |||||||
|  |  | ||||||
|     </LinearLayout> |     </LinearLayout> | ||||||
|  |  | ||||||
|     <!-- 2. 备注输入框(不变) --> |     <!-- 2. 备注输入框(不变,调整下方间距为8dp,与新增单选框区过渡更自然) --> | ||||||
|     <EditText |     <EditText | ||||||
|         android:id="@+id/et_edit_memo" |         android:id="@+id/et_edit_memo" | ||||||
|         android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
| @@ -44,9 +44,56 @@ | |||||||
|         android:maxLines="4" |         android:maxLines="4" | ||||||
|         android:padding="10dp" |         android:padding="10dp" | ||||||
|         android:background="@drawable/edittext_bg" |         android:background="@drawable/edittext_bg" | ||||||
|         android:layout_marginBottom="12dp"/> |         android:layout_marginBottom="8dp"/> <!-- 原12dp→8dp,优化与单选框的间距 --> | ||||||
|  |  | ||||||
|     <!-- 3. 按钮区(核心修改:顺序→删除(左)→取消→确定(右),新增取消按钮) --> |     <!-- 新增3:实时距离开关(单选框组)- 控制isEnableRealPositionDistance属性 --> | ||||||
|  |     <LinearLayout | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:orientation="horizontal" | ||||||
|  |         android:gravity="center_vertical" | ||||||
|  |         android:layout_marginBottom="12dp"> <!-- 与下方按钮区保持12dp间距,符合原有布局规范 --> | ||||||
|  |  | ||||||
|  |         <!-- 单选框标题(说明用途,文字颜色与经纬度一致,保持风格统一) --> | ||||||
|  |         <TextView | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:text="实时距离计算:" | ||||||
|  |             android:textSize="14sp" | ||||||
|  |             android:textColor="#666666" | ||||||
|  |             android:layout_marginRight="12dp"/> <!-- 与单选框保持12dp间距,避免拥挤 --> | ||||||
|  |  | ||||||
|  |         <!-- 单选框组(RadioGroup):确保“启用/禁用”二选一 --> | ||||||
|  |         <RadioGroup | ||||||
|  |             android:id="@+id/rg_real_distance_switch" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:orientation="horizontal" | ||||||
|  |             android:checkedButton="@+id/rb_disable"> <!-- 默认选中“禁用”,匹配isEnable默认值false --> | ||||||
|  |  | ||||||
|  |             <!-- 单选框1:禁用实时距离(对应isEnableRealPositionDistance=false) --> | ||||||
|  |             <RadioButton | ||||||
|  |                 android:id="@+id/rb_disable" | ||||||
|  |                 android:layout_width="wrap_content" | ||||||
|  |                 android:layout_height="wrap_content" | ||||||
|  |                 android:text="禁用" | ||||||
|  |                 android:textSize="13sp" | ||||||
|  |                 android:textColor="#333333" | ||||||
|  |                 android:layout_marginRight="16dp"/> <!-- 与“启用”单选框保持16dp间距 --> | ||||||
|  |  | ||||||
|  |             <!-- 单选框2:启用实时距离(对应isEnableRealPositionDistance=true) --> | ||||||
|  |             <RadioButton | ||||||
|  |                 android:id="@+id/rb_enable" | ||||||
|  |                 android:layout_width="wrap_content" | ||||||
|  |                 android:layout_height="wrap_content" | ||||||
|  |                 android:text="启用" | ||||||
|  |                 android:textSize="13sp" | ||||||
|  |                 android:textColor="#333333"/> | ||||||
|  |  | ||||||
|  |         </RadioGroup> | ||||||
|  |     </LinearLayout> | ||||||
|  |  | ||||||
|  |     <!-- 4. 按钮区(不变:顺序→删除(左)→取消→确定(右)) --> | ||||||
|     <LinearLayout |     <LinearLayout | ||||||
|         android:layout_width="wrap_content" |         android:layout_width="wrap_content" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
| @@ -70,7 +117,7 @@ | |||||||
|             android:layout_width="10dp" |             android:layout_width="10dp" | ||||||
|             android:layout_height="match_parent"/> |             android:layout_height="match_parent"/> | ||||||
|  |  | ||||||
|         <!-- ② 新增:取消按钮(中间,灰色,不保存返回简单视图) --> |         <!-- ② 取消按钮(中间,灰色,不保存返回简单视图) --> | ||||||
|         <Button |         <Button | ||||||
|             android:id="@+id/btn_edit_cancel" |             android:id="@+id/btn_edit_cancel" | ||||||
|             android:layout_width="wrap_content" |             android:layout_width="wrap_content" | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <!-- 简单视图:仅显示经纬度+备注,无任何按钮 --> | <!-- 简单视图:新增实时距离显示控件(备注下方) --> | ||||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     android:layout_width="match_parent" |     android:layout_width="match_parent" | ||||||
|     android:layout_height="wrap_content" |     android:layout_height="wrap_content" | ||||||
| @@ -35,5 +35,15 @@ | |||||||
|         android:textStyle="bold" |         android:textStyle="bold" | ||||||
|         android:layout_marginTop="8dp"/> |         android:layout_marginTop="8dp"/> | ||||||
|  |  | ||||||
|  |     <!-- 新增:实时距离显示(距离计算结果/未启用提示) --> | ||||||
|  |     <TextView | ||||||
|  |         android:id="@+id/tv_simple_real_distance" | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:textSize="14sp" | ||||||
|  |         android:textColor="#2196F3" | ||||||
|  |         android:layout_marginTop="6dp" | ||||||
|  |         android:text="实时距离未启用"/>  <!-- 默认提示(isEnable=false时显示) --> | ||||||
|  |  | ||||||
| </LinearLayout> | </LinearLayout> | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 ZhanGSKen
					ZhanGSKen