添加简单位置信息保存功能

This commit is contained in:
ZhanGSKen
2025-09-29 20:03:13 +08:00
parent eccf9b9bde
commit 72ba518c6d
5 changed files with 413 additions and 179 deletions

View File

@@ -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

View File

@@ -1,100 +1,121 @@
package cc.winboll.studio.positions.activities;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @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. 核心组件与常量定义
// 1. 核心组件与常量定义Java 7不支持final修饰后未立即初始化的变量移除不必要final
private static final int REQUEST_LOCATION_PERMISSIONS = 1004; // 定位权限请求码
private FusedLocationProviderClient fusedLocationClient; // 定位核心客户端
private LocationCallback locationCallback; // 位置变化监听器
private LocationRequest locationRequest; // 定位请求配置(频率、精度等)
// UI控件用于显示经纬度需在布局中定义对应ID
// UI控件Java 7不支持变量声明后直接换行注释调整注释位置
private TextView tvLongitude; // 经度显示
private TextView tvLatitude; // 纬度显示
private Button fabPButton; // 右下角圆形悬浮按钮P字母
ArrayList<PositionModel> mPositionList = new ArrayList<PositionModel>();
@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);
// 绑定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); // 绑定悬浮按钮
// 4. 初始化定位相关组件
// 初始化定位相关组件
initLocationConfig();
// 初始化悬浮按钮(设置点击事件)
initFabPButton();
// 5. 检查并申请定位权限(无权限则申请,有权限则直接启动定位)
// 检查并申请定位权限
if (checkLocationPermissions()) {
startRealTimeLocation(); // 权限已授予 → 启动实时定位
startRealTimeLocation();
} else {
requestLocationPermissions(); // 权限未授予 → 申请权限
requestLocationPermissions();
}
}
/**
* 初始化定位配置:设置定位精度、更新频率等
* 初始化悬浮按钮设置点击事件Java 7不支持Lambda改用匿名内部类
*/
private void initFabPButton() {
// 悬浮按钮点击事件替换Lambda为View.OnClickListener匿名内部类
fabPButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showLocationRemarkDialog();
}
});
}
/**
* 初始化定位配置Java 7不支持LocationRequest.Builder适配旧API调整定位请求创建方式
*/
private void initLocationConfig() {
// 初始化定位客户端(谷歌官方推荐替代旧的LocationManager
// 初始化定位客户端(Java 7语法无差异
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
// 配置定位请求实时更新1秒请求一次最小间隔500毫秒最高精度
locationRequest = new LocationRequest.Builder(1000) // 定位更新间隔(毫秒)
.setMinUpdateIntervalMillis(500) // 最小更新间隔(避免频繁更新耗电)
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) // 高精度定位优先GPS
.build();
// 关键修改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
// 初始化位置变化监听器:位置更新时触发实时更新UI
// 初始化位置变化监听器:替换Lambda为LocationCallback匿名内部类Java 7核心修改
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(@NonNull LocationResult locationResult) {
super.onLocationResult(locationResult);
// 获取最新位置信息locationResult包含最近一次或多次位置
// 获取最新位置信息逻辑不变Java 7语法兼容
Location latestLocation = locationResult.getLastLocation();
if (latestLocation != null) {
// 6. 提取经纬度并更新UI实时显示
double longitude = latestLocation.getLongitude(); // 经度(东经为正,西经为负)
double latitude = latestLocation.getLatitude(); // 纬度(北纬为正,南纬为负)
double longitude = latestLocation.getLongitude(); // 经度
double latitude = latestLocation.getLatitude(); // 纬度
// 更新TextView显示保留6位小数精度足够日常使用
// 更新UIString.format兼容Java 7逻辑不变
tvLongitude.setText(String.format("当前经度:%.6f", longitude));
tvLatitude.setText(String.format("当前纬度:%.6f", latitude));
}
@@ -104,69 +125,57 @@ public class LocationActivity extends AppCompatActivity {
/**
* 检查定位必需权限是否已授予(精确定位+粗略定位,覆盖所有安卓版本
* 检查定位必需权限逻辑不变Java 7语法兼容
*/
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不支持多行条件判断的简洁换行调整格式保持可读性
return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED;
}
/**
* 申请定位必需权限(弹系统授权弹窗
* 申请定位必需权限(逻辑不变Java 7语法兼容
*/
private void requestLocationPermissions() {
/*if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
// 安卓12+:同时申请精确定位+粗略定位
// Java 7数组初始化语法无差异保持原逻辑
String[] permissions = new String[]{Manifest.permission.ACCESS_FINE_LOCATION};
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION},
permissions,
REQUEST_LOCATION_PERMISSIONS
);
} else {*/
// 安卓12以下仅申请精确定位包含粗略定位能力
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_LOCATION_PERMISSIONS
);
//}
}
/**
* 启动实时定位:注册位置监听器,开始接收位置更新
* 启动实时定位:Java 7不支持Lambda替换OnSuccessListener为匿名内部类
*/
private void startRealTimeLocation() {
// 权限兜底检查(避免异常
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// 权限兜底检查(逻辑不变
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "定位权限未授予,无法启动定位", Toast.LENGTH_SHORT).show();
return;
}
// 1. 先获取一次当前位置(快速显示初始经纬度
// 1. 先获取一次当前位置替换Lambda为OnSuccessListener匿名内部类Java 7核心修改
fusedLocationClient.getLastLocation()
.addOnSuccessListener(this, new OnSuccessListener<Location>() {
@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. 注册监听器,接收实时位置更新(持续监听
// 2. 注册监听器,接收实时位置更新(逻辑不变Java 7语法兼容
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
@@ -176,14 +185,14 @@ public class LocationActivity extends AppCompatActivity {
/**
* 处理权限申请结果(用户同意/拒绝后触发
* 处理权限申请结果(逻辑不变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;
@@ -192,10 +201,8 @@ public class LocationActivity extends AppCompatActivity {
}
if (allGranted) {
// 权限同意 → 启动实时定位
startRealTimeLocation();
} else {
// 权限拒绝 → 提示用户(无法定位)
Toast.makeText(this, "定位权限被拒绝,无法显示位置信息", Toast.LENGTH_SHORT).show();
tvLongitude.setText("当前经度:无权限");
tvLatitude.setText("当前纬度:无权限");
@@ -205,15 +212,95 @@ public class LocationActivity extends AppCompatActivity {
/**
* 活动销毁时停止定位监听(避免内存泄漏、减少耗电
* 活动销毁时停止定位监听(逻辑不变Java 7语法兼容
*/
@Override
protected void onDestroy() {
super.onDestroy();
// 移除定位监听器核心防止Activity销毁后仍在监听导致内存泄漏
// Java 7条件判断格式无差异保持空指针防护
if (fusedLocationClient != null && locationCallback != null) {
fusedLocationClient.removeLocationUpdates(locationCallback);
}
}
/**
* 弹出“当前位置备注”输入对话框:
* 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(false); // 再次确认“外部点击不关闭”(兼容部分机型)
// 6. 显示对话框
remarkDialog.show();
}
/**
* 辅助工具将dp转换为px解决不同屏幕分辨率下输入框内边距不一致问题
* @param dpValue 要转换的dp值如16dp
* @return 转换后的px值适配当前设备屏幕密度
*/
private int dip2px(float dpValue) {
// 获取设备屏幕密度densitydp转px公式px = dp * density + 0.5f+0.5f用于四舍五入)
final float scale = getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}

View File

@@ -0,0 +1,102 @@
package cc.winboll.studio.positions.models;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @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;
}
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 圆形按钮背景:默认青色,按压时深色 -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 按钮按压状态(深色,增强交互反馈) -->
<item android:state_pressed="true">
<shape android:shape="oval"> <!-- oval=圆形 -->
<solid android:color="#0F9D58"/> <!-- 按压时颜色深青色接近Google绿美观且醒目 -->
<stroke android:width="1dp" android:color="#0F9D58"/> <!-- 边框颜色与背景一致,避免白边 -->
</shape>
</item>
<!-- 按钮默认状态(青色) -->
<item>
<shape android:shape="oval">
<solid android:color="#14C38E"/> <!-- 默认颜色:亮青色(视觉舒适,符合悬浮按钮风格) -->
<stroke android:width="1dp" android:color="#14C38E"/>
</shape>
</item>
</selector>

View File

@@ -1,11 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<!-- 根布局改为RelativeLayout支持悬浮按钮绝对定位右下角 -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal|center_vertical"
android:orientation="vertical"
android:padding="20dp">
<!-- 原有内容:经纬度显示区域(保持居中) -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical"
android:gravity="center_horizontal">
<!-- 标题 -->
<TextView
android:layout_width="wrap_content"
@@ -14,7 +21,7 @@
android:textSize="22sp"
android:textStyle="bold"/>
<!-- 经度显示(大字体,清晰可见) -->
<!-- 经度显示 -->
<TextView
android:id="@+id/tv_longitude"
android:layout_width="wrap_content"
@@ -22,7 +29,7 @@
android:text="当前经度:等待更新..."
android:textSize="18sp"/>
<!-- 纬度显示(大字体,清晰可见) -->
<!-- 纬度显示 -->
<TextView
android:id="@+id/tv_latitude"
android:layout_width="wrap_content"
@@ -30,5 +37,22 @@
android:text="当前纬度:等待更新..."
android:textSize="18sp"/>
</LinearLayout>
</LinearLayout>
<!-- 新增右下角圆形悬浮按钮中间显示大写P字母 -->
<Button
android:id="@+id/fab_p_button"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="20dp"
android:background="@drawable/circle_button_bg"
android:text="P"
android:textColor="@android:color/white"
android:textSize="24sp"
android:elevation="6dp"
android:padding="0dp"/>
</RelativeLayout>