From 72ba518c6d2f34d935438c8c551505d39add1ada Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Mon, 29 Sep 2025 20:03:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=AE=80=E5=8D=95=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=E4=BF=A1=E6=81=AF=E4=BF=9D=E5=AD=98=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 | 395 +++++++++++------- .../positions/models/PositionModel.java | 102 +++++ .../main/res/drawable/circle_button_bg.xml | 21 + .../src/main/res/layout/activity_location.xml | 70 +++- 5 files changed, 413 insertions(+), 179 deletions(-) create mode 100644 positions/src/main/java/cc/winboll/studio/positions/models/PositionModel.java create mode 100644 positions/src/main/res/drawable/circle_button_bg.xml diff --git a/positions/build.properties b/positions/build.properties index 238b633..b5d68b6 100644 --- a/positions/build.properties +++ b/positions/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Mon Sep 29 18:46:38 HKT 2025 +#Mon Sep 29 12:02:00 GMT 2025 stageCount=3 libraryProject= baseVersion=15.0 publishVersion=15.0.2 -buildCount=0 +buildCount=4 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 ba8830c..a328e8b 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 @@ -1,219 +1,306 @@ package cc.winboll.studio.positions.activities; - /** * @Author ZhanGSKen&豆包大模型 * @Date 2025/09/29 18:22 * @Describe 当前位置实时显示 */ - - import android.Manifest; import android.content.pm.PackageManager; import android.location.Location; import android.os.Bundle; +import android.view.View; +import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; +import cc.winboll.studio.positions.R; import com.google.android.gms.location.FusedLocationProviderClient; import com.google.android.gms.location.LocationCallback; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationResult; import com.google.android.gms.location.LocationServices; import com.google.android.gms.tasks.OnSuccessListener; -import cc.winboll.studio.positions.R; - +import java.util.ArrayList; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.widget.EditText; +import android.text.InputType; +import cc.winboll.studio.positions.models.PositionModel; /** * 实时定位活动窗口: - * 1. 申请定位必需权限(精确定位+粗略定位) + * 1. 申请定位必需权限(精确定位) * 2. 初始化FusedLocationProviderClient(谷歌官方定位服务,兼容所有安卓版本) * 3. 实时监听位置变化,更新显示经度、纬度 + * 4. 右下角圆形悬浮按钮(含大写P字母,支持扩展功能) */ public class LocationActivity extends AppCompatActivity { - - public static final String TAG = "LocationActivity"; - - // 1. 核心组件与常量定义 - private static final int REQUEST_LOCATION_PERMISSIONS = 1004; // 定位权限请求码 - private FusedLocationProviderClient fusedLocationClient; // 定位核心客户端 - private LocationCallback locationCallback; // 位置变化监听器 - private LocationRequest locationRequest; // 定位请求配置(频率、精度等) - // UI控件:用于显示经纬度(需在布局中定义对应ID) - private TextView tvLongitude; // 经度显示 - private TextView tvLatitude; // 纬度显示 + public static final String TAG = "LocationActivity"; + + // 1. 核心组件与常量定义(Java 7不支持final修饰后未立即初始化的变量,移除不必要final) + private static final int REQUEST_LOCATION_PERMISSIONS = 1004; // 定位权限请求码 + private FusedLocationProviderClient fusedLocationClient; // 定位核心客户端 + private LocationCallback locationCallback; // 位置变化监听器 + private LocationRequest locationRequest; // 定位请求配置(频率、精度等) + + // UI控件:Java 7不支持变量声明后直接换行注释,调整注释位置 + private TextView tvLongitude; // 经度显示 + private TextView tvLatitude; // 纬度显示 + private Button fabPButton; // 右下角圆形悬浮按钮(P字母) + 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需显式强制转换 + tvLatitude = (TextView) findViewById(R.id.tv_latitude); + fabPButton = (Button) findViewById(R.id.fab_p_button); // 绑定悬浮按钮 + + // 初始化定位相关组件 + initLocationConfig(); + // 初始化悬浮按钮(设置点击事件) + initFabPButton(); + + // 检查并申请定位权限 + if (checkLocationPermissions()) { + startRealTimeLocation(); + } else { + requestLocationPermissions(); + } + } - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // 2. 加载布局(需手动创建对应布局文件,见下方说明) - setContentView(R.layout.activity_location); - - // 3. 绑定UI控件(与布局文件中的TextView ID对应) - tvLongitude = findViewById(R.id.tv_longitude); - tvLatitude = findViewById(R.id.tv_latitude); - - // 4. 初始化定位相关组件 - initLocationConfig(); - - // 5. 检查并申请定位权限(无权限则申请,有权限则直接启动定位) - if (checkLocationPermissions()) { - startRealTimeLocation(); // 权限已授予 → 启动实时定位 - } else { - requestLocationPermissions(); // 权限未授予 → 申请权限 - } - } - - - /** - * 初始化定位配置:设置定位精度、更新频率等 - */ - private void initLocationConfig() { - // 初始化定位客户端(谷歌官方推荐,替代旧的LocationManager) - fusedLocationClient = LocationServices.getFusedLocationProviderClient(this); - - // 配置定位请求:实时更新(1秒请求一次,最小间隔500毫秒,最高精度) - locationRequest = new LocationRequest.Builder(1000) // 定位更新间隔(毫秒) - .setMinUpdateIntervalMillis(500) // 最小更新间隔(避免频繁更新耗电) - .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) // 高精度定位(优先GPS) - .build(); - - // 初始化位置变化监听器:位置更新时触发(实时更新UI) - locationCallback = new LocationCallback() { - @Override - public void onLocationResult(@NonNull LocationResult locationResult) { - super.onLocationResult(locationResult); - // 获取最新位置信息(locationResult包含最近一次或多次位置) - Location latestLocation = locationResult.getLastLocation(); - if (latestLocation != null) { - // 6. 提取经纬度并更新UI(实时显示) - double longitude = latestLocation.getLongitude(); // 经度(东经为正,西经为负) - double latitude = latestLocation.getLatitude(); // 纬度(北纬为正,南纬为负) - - // 更新TextView显示(保留6位小数,精度足够日常使用) - tvLongitude.setText(String.format("当前经度:%.6f", longitude)); - tvLatitude.setText(String.format("当前纬度:%.6f", latitude)); + /** + * 初始化悬浮按钮(设置点击事件,Java 7不支持Lambda,改用匿名内部类) + */ + private void initFabPButton() { + // 悬浮按钮点击事件:替换Lambda为View.OnClickListener匿名内部类 + fabPButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showLocationRemarkDialog(); } - } - }; - } + }); + } - /** - * 检查定位必需权限是否已授予(精确定位+粗略定位,覆盖所有安卓版本) - */ - private boolean checkLocationPermissions() { - // 安卓12+(API31+)新增精确定位权限,需同时检查2个权限;低版本只需检查1个 - /*if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { - return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED - && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED; - } else {*/ - return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED; - //} - } + /** + * 初始化定位配置:Java 7不支持LocationRequest.Builder(适配旧API),调整定位请求创建方式 + */ + 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 requestLocationPermissions() { - /*if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { - // 安卓12+:同时申请精确定位+粗略定位 - ActivityCompat.requestPermissions( - this, - new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, - REQUEST_LOCATION_PERMISSIONS - ); - } else {*/ - // 安卓12以下:仅申请精确定位(包含粗略定位能力) - ActivityCompat.requestPermissions( - this, - new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, - REQUEST_LOCATION_PERMISSIONS - ); - //} - } + /** + * 检查定位必需权限(逻辑不变,Java 7语法兼容) + */ + private boolean checkLocationPermissions() { + // Java 7不支持多行条件判断的简洁换行,调整格式保持可读性 + return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED; + } - /** - * 启动实时定位:注册位置监听器,开始接收位置更新 - */ - private void startRealTimeLocation() { - // 权限兜底检查(避免异常) - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, "定位权限未授予,无法启动定位", Toast.LENGTH_SHORT).show(); - return; - } + /** + * 申请定位必需权限(逻辑不变,Java 7语法兼容) + */ + private void requestLocationPermissions() { + // Java 7数组初始化语法无差异,保持原逻辑 + String[] permissions = new String[]{Manifest.permission.ACCESS_FINE_LOCATION}; + ActivityCompat.requestPermissions( + this, + permissions, + REQUEST_LOCATION_PERMISSIONS + ); + } - // 1. 先获取一次当前位置(快速显示初始经纬度) - fusedLocationClient.getLastLocation() + + /** + * 启动实时定位: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(); + return; + } + + // 1. 先获取一次当前位置:替换Lambda为OnSuccessListener匿名内部类(Java 7核心修改) + fusedLocationClient.getLastLocation() .addOnSuccessListener(this, new OnSuccessListener() { @Override public void onSuccess(Location location) { + // 成功获取位置后的逻辑(无语法差异) if (location != null) { - // 显示初始经纬度 tvLongitude.setText(String.format("当前经度:%.6f", location.getLongitude())); tvLatitude.setText(String.format("当前纬度:%.6f", location.getLatitude())); } else { - // 无历史位置(如首次启动),提示“等待定位更新” tvLongitude.setText("当前经度:等待更新..."); tvLatitude.setText("当前纬度:等待更新..."); } } }); - // 2. 注册监听器,接收实时位置更新(持续监听) - fusedLocationClient.requestLocationUpdates( + // 2. 注册监听器,接收实时位置更新(逻辑不变,Java 7语法兼容) + fusedLocationClient.requestLocationUpdates( locationRequest, locationCallback, 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循环语法无差异 + for (int result : grantResults) { + if (result != PackageManager.PERMISSION_GRANTED) { + allGranted = false; + break; + } + } + + if (allGranted) { + startRealTimeLocation(); + } else { + Toast.makeText(this, "定位权限被拒绝,无法显示位置信息", Toast.LENGTH_SHORT).show(); + tvLongitude.setText("当前经度:无权限"); + tvLatitude.setText("当前纬度:无权限"); + } + } + } + + + /** + * 活动销毁时停止定位监听(逻辑不变,Java 7语法兼容) + */ + @Override + protected void onDestroy() { + super.onDestroy(); + // Java 7条件判断格式无差异,保持空指针防护 + if (fusedLocationClient != null && locationCallback != null) { + fusedLocationClient.removeLocationUpdates(locationCallback); + } + } + /** - * 处理权限申请结果(用户同意/拒绝后触发) - */ - @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; - for (int result : grantResults) { - if (result != PackageManager.PERMISSION_GRANTED) { - allGranted = false; - break; + * 弹出“当前位置备注”输入对话框: + * 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(); // 关闭对话框(避免残留) } - } + }); - if (allGranted) { - // 权限同意 → 启动实时定位 - startRealTimeLocation(); - } else { - // 权限拒绝 → 提示用户(无法定位) - Toast.makeText(this, "定位权限被拒绝,无法显示位置信息", Toast.LENGTH_SHORT).show(); - tvLongitude.setText("当前经度:无权限"); - tvLatitude.setText("当前纬度:无权限"); - } - } - } + // 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(false); // 再次确认“外部点击不关闭”(兼容部分机型) - /** - * 活动销毁时:停止定位监听(避免内存泄漏、减少耗电) - */ - @Override - protected void onDestroy() { - super.onDestroy(); - // 移除定位监听器(核心:防止Activity销毁后仍在监听,导致内存泄漏) - if (fusedLocationClient != null && locationCallback != null) { - fusedLocationClient.removeLocationUpdates(locationCallback); - } - } + // 6. 显示对话框 + remarkDialog.show(); + } + + /** + * 辅助工具:将dp转换为px(解决不同屏幕分辨率下输入框内边距不一致问题) + * @param dpValue 要转换的dp值(如16dp) + * @return 转换后的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); + } + } diff --git a/positions/src/main/java/cc/winboll/studio/positions/models/PositionModel.java b/positions/src/main/java/cc/winboll/studio/positions/models/PositionModel.java new file mode 100644 index 0000000..1b6b024 --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/models/PositionModel.java @@ -0,0 +1,102 @@ +package cc.winboll.studio.positions.models; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/09/29 18:57 + * @Describe 位置数据模型 + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; + +public class PositionModel extends BaseBean { + + public static final String TAG = "PositionModel"; + // 经度 + double longitude; + // 纬度 + double latitude; + // 位置信息备注 + String memo; + + public PositionModel(double longitude, double latitude, String memo) { + this.longitude = longitude; + this.latitude = latitude; + this.memo = memo; + } + + public PositionModel() { + this.longitude = 0.0f; + this.latitude = 0.0f; + this.memo = ""; + } + + public void setMemo(String memo) { + this.memo = memo; + } + + public String getMemo() { + return memo; + } + + public void setLongitude(double longitude) { + this.longitude = longitude; + } + + public double getLongitude() { + return longitude; + } + + public void setLatitude(double latitude) { + this.latitude = latitude; + } + + public double getLatitude() { + return latitude; + } + + @Override + public String getName() { + return PositionModel.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + jsonWriter.name("longitude").value(getLongitude()); + jsonWriter.name("latitude").value(getLatitude()); + jsonWriter.name("memo").value(getMemo()); + } + + @Override + public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { + if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { + if (name.equals("longitude")) { + setLongitude(jsonReader.nextDouble()); + } else if (name.equals("latitude")) { + setLatitude(jsonReader.nextDouble()); + } else if (name.equals("memo")) { + setMemo(jsonReader.nextString()); + } else { + return false; + } + } + return true; + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (!initObjectsFromJsonReader(jsonReader, name)) { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return this; + } + +} diff --git a/positions/src/main/res/drawable/circle_button_bg.xml b/positions/src/main/res/drawable/circle_button_bg.xml new file mode 100644 index 0000000..5ed6038 --- /dev/null +++ b/positions/src/main/res/drawable/circle_button_bg.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/positions/src/main/res/layout/activity_location.xml b/positions/src/main/res/layout/activity_location.xml index 2d9895d..8303f07 100644 --- a/positions/src/main/res/layout/activity_location.xml +++ b/positions/src/main/res/layout/activity_location.xml @@ -1,34 +1,58 @@ - + - - + + android:layout_centerInParent="true" + android:orientation="vertical" + android:gravity="center_horizontal"> - - + + - - + + - + + + + + + +