From 61eb820aabe409597fcd92f93f347d72200109ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=A3=8A?= <913305160@qq.com> Date: Mon, 8 Jun 2026 14:27:37 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E9=85=8D=E7=BD=AE=EF=BC=8C=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E6=8A=A5=E9=94=99=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../org.eclipse.buildship.core.prefs | 2 -- app/build.gradle | 24 +++++++++---- .../java/com/zcshou/gogogo/MainActivity.java | 2 +- build.gradle | 26 ++------------ gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle | 36 +++++++++++++++++++ 7 files changed, 60 insertions(+), 33 deletions(-) delete mode 100644 app/.settings/org.eclipse.buildship.core.prefs diff --git a/.gitignore b/.gitignore index a6b86156..2eb07b85 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ captures/ # Google Services (e.g. APIs or Firebase) google-services.json +/secrets.properties diff --git a/app/.settings/org.eclipse.buildship.core.prefs b/app/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index a7b84d92..00000000 --- a/app/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -connection.project.dir=.. -eclipse.preferences.version=1 diff --git a/app/build.gradle b/app/build.gradle index d1886148..9869f173 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,13 +1,11 @@ plugins { id "com.android.application" // 谷歌官方推荐的用于隐藏地图 KEY 的插件 https://developers.google.com/maps/documentation/places/android-sdk/secrets-gradle-plugin?hl=zh-cn - id "com.google.android.libraries.mapsplatform.secrets-gradle-plugin" + id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' } android { - ndkVersion = '26.2.11394342' compileSdk = 32 - buildToolsVersion = '36.0.0' namespace = 'com.zcshou.gogogo' signingConfigs { @@ -31,7 +29,7 @@ android { //noinspection ExpiredTargetSdkVersion targetSdkVersion 32 versionCode 1123 - versionName '1.12.3' // https://semver.org/lang/zh-CN/ + versionName '1.12.3' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resourceConfigurations += ['zh', 'zh-rCN', 'en', 'en-rUS'] ndk { @@ -54,7 +52,7 @@ android { // Includes the default ProGuard rules files that are packaged with // the Android Gradle plugin. To learn more, go to the section about // R8 configuration files. - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' signingConfig = signingConfigs.release multiDexEnabled = true @@ -64,7 +62,6 @@ android { } } - // 要存取 secret value 必須要使用到一個 BuildScript 的類別,默认会从 local.properties 中获取 buildFeatures { buildConfig = true } @@ -111,6 +108,21 @@ android { } } +secrets { + // Optionally specify a different file name containing your secrets. + // The plugin defaults to "local.properties" + propertiesFileName = "secrets.properties" + + // A properties file containing default secret values. This file can be + // checked in version control. + // defaultPropertiesFileName = "local.defaults.properties" + + // Configure which keys should be ignored by the plugin by providing regular expressions. + // "sdk.dir" is ignored by default. + ignoreList.add("keyToIgnore") // Ignore the key "keyToIgnore" + ignoreList.add("sdk.*") // Ignore all keys matching the regexp "sdk.*" +} + dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') // AppCompat 1.5.1 开始显式依赖于 Lifecycle 2.5.1 和 SavedState 1.2.0 diff --git a/app/src/main/java/com/zcshou/gogogo/MainActivity.java b/app/src/main/java/com/zcshou/gogogo/MainActivity.java index 07641103..641d6bcb 100644 --- a/app/src/main/java/com/zcshou/gogogo/MainActivity.java +++ b/app/src/main/java/com/zcshou/gogogo/MainActivity.java @@ -650,7 +650,7 @@ private static LocationClientOption getLocationClientOption() { //可选,默认false,设置是否需要POI结果,可以在BDLocation.getPoiList里得到 locationOption.setIsNeedLocationPoiList(false); //可选,默认false,设置是否收集CRASH信息,默认收集 - locationOption.setIgnoreCacheException(true); + locationOption.SetIgnoreCacheException(true); //可选,默认false,设置是否开启Gps定位 //locationOption.setOpenGps(true); locationOption.setOpenGnss(true); diff --git a/build.gradle b/build.gradle index 729f5da1..8b6b8ed9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,34 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:8.12.1' // 谷歌官方推荐的用于隐藏地图 KEY 的插件 https://developers.google.com/maps/documentation/places/android-sdk/secrets-gradle-plugin?hl=zh-cn - classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1") - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1" } } -allprojects { - repositories { - google() - mavenCentral() - } - - gradle.projectsEvaluated { - tasks.withType(JavaCompile).tap { - configureEach { // 用于显示过时 API 的详细信息 - options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" - } - } - } +plugins { + id 'com.android.application' version '8.12.1' apply false } tasks.register('clean', Delete) { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6e73f44f..2d128cf4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.13-bin.zip diff --git a/settings.gradle b/settings.gradle index d3db1092..cae051d6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,37 @@ +// 修改项目依赖的仓库地址 +gradle.settingsEvaluated { settings -> + settings.pluginManagement { + repositories { + maven { url 'https://mirrors.cloud.tencent.com/nexus/repository/maven-public/' } // 腾讯镜像 + maven { url 'https://maven.aliyun.com/repository/public' } + maven { url 'https://maven.aliyun.com/repository/google' } + maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } + maven { url 'https://jitpack.io' } + google() // 阿里云对于有些库会有丢失,加入进行兜底 + mavenCentral() + gradlePluginPortal() + } + } + settings.dependencyResolutionManagement { + /** + * FAIL_ON_PROJECT_REPOS: 如果设置此模式,则在项目中直接或通过插件直接声明的任何存储库都将触发构建错误 + * PREFER_PROJECT: 如果设置此模式,则在项目上声明的任何存储库会覆盖设置中声明的存储库 + * PREFER_SETTINGS: 如果设置此模式,则项目中使用的存储库只会以设置中声明的为准 + */ + repositoriesMode.set(RepositoriesMode.PREFER_PROJECT) + + repositories { + mavenLocal() + maven { url 'https://maven.aliyun.com/repository/public' } // central 和 jcenter 的聚合仓 + maven { url 'https://maven.aliyun.com/repository/google' } + maven { url 'https://jitpack.io' } + maven { url 'https://mirrors.cloud.tencent.com/nexus/repository/maven-public/' } // 腾讯镜像 + google() // 阿里云对于有些库会有丢失,加入进行兜底 + mavenCentral() + } + } +} + +rootProject.name = "Shadow" + include ':app' From 70cf05158739aa34efb04a18ab57bc403dc6fb3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=A3=8A?= <913305160@qq.com> Date: Mon, 8 Jun 2026 14:55:29 +0800 Subject: [PATCH 2/8] =?UTF-8?q?fix:=20=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E4=B8=AD=20ListView=20=E6=9B=BF=E6=8D=A2=E4=B8=BA=20RecyclerVi?= =?UTF-8?q?ew=EF=BC=8C=E4=BF=AE=E5=A4=8D=E7=BC=96=E8=BE=91=E5=8D=95?= =?UTF-8?q?=E6=9D=A1=20item=20=E6=A0=87=E9=A2=98=E5=AF=B9=E6=89=80?= =?UTF-8?q?=E6=9C=89=20item=20=E7=94=9F=E6=95=88=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/zcshou/gogogo/HistoryActivity.java | 183 ++++++++---------- .../com/zcshou/gogogo/HistoryAdapter.java | 170 ++++++++++++++++ .../java/com/zcshou/gogogo/HistoryItem.java | 50 +++++ .../java/com/zcshou/joystick/JoyStick.java | 88 ++++----- app/src/main/res/layout/activity_history.xml | 4 +- app/src/main/res/layout/joystick_history.xml | 4 +- 6 files changed, 340 insertions(+), 159 deletions(-) create mode 100644 app/src/main/java/com/zcshou/gogogo/HistoryAdapter.java create mode 100644 app/src/main/java/com/zcshou/gogogo/HistoryItem.java diff --git a/app/src/main/java/com/zcshou/gogogo/HistoryActivity.java b/app/src/main/java/com/zcshou/gogogo/HistoryActivity.java index 72d353db..65eefaf1 100644 --- a/app/src/main/java/com/zcshou/gogogo/HistoryActivity.java +++ b/app/src/main/java/com/zcshou/gogogo/HistoryActivity.java @@ -8,6 +8,8 @@ import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.text.InputType; import android.text.TextUtils; @@ -16,9 +18,7 @@ import android.view.MenuItem; import android.view.View; import android.widget.LinearLayout; -import android.widget.ListView; import android.widget.SearchView; -import android.widget.SimpleAdapter; import android.widget.TextView; import android.widget.EditText; import android.widget.PopupMenu; @@ -27,28 +27,23 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; -import java.util.HashMap; import java.util.Objects; import java.util.Locale; import java.util.List; -import java.util.Map; import com.zcshou.database.DataBaseHistoryLocation; import com.zcshou.utils.GoUtils; public class HistoryActivity extends BaseActivity { - public static final String KEY_ID = "KEY_ID"; - public static final String KEY_LOCATION = "KEY_LOCATION"; - public static final String KEY_TIME = "KEY_TIME"; - public static final String KEY_LNG_LAT_WGS = "KEY_LNG_LAT_WGS"; - public static final String KEY_LNG_LAT_CUSTOM = "KEY_LNG_LAT_CUSTOM"; - private ListView mRecordListView; + private RecyclerView mRecordListView; private TextView noRecordText; private LinearLayout mSearchLayout; private SQLiteDatabase mHistoryLocationDB; - private List> mAllRecord; + private List mAllItems; private SharedPreferences sharedPreferences; + private SearchView mSearchView; + private HistoryAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { @@ -130,8 +125,8 @@ private void initLocationDataBase() { } //sqlite 操作 查询所有记录 - private List> fetchAllRecord() { - List> data = new ArrayList<>(); + private List fetchAllRecord() { + List data = new ArrayList<>(); try { Cursor cursor = mHistoryLocationDB.query(DataBaseHistoryLocation.TABLE_NAME, null, @@ -139,7 +134,6 @@ private List> fetchAllRecord() { null, null, DataBaseHistoryLocation.DB_COLUMN_TIMESTAMP + " DESC", null); while (cursor.moveToNext()) { - Map item = new HashMap<>(); int ID = cursor.getInt(0); String Location = cursor.getString(1); String Longitude = cursor.getString(2); @@ -156,12 +150,13 @@ private List> fetchAllRecord() { double doubleLatitude = bigDecimalLatitude.setScale(11, RoundingMode.HALF_UP).doubleValue(); double doubleBDLongitude = bigDecimalBDLongitude.setScale(11, RoundingMode.HALF_UP).doubleValue(); double doubleBDLatitude = bigDecimalBDLatitude.setScale(11, RoundingMode.HALF_UP).doubleValue(); - item.put(KEY_ID, Integer.toString(ID)); - item.put(KEY_LOCATION, Location); - item.put(KEY_TIME, GoUtils.timeStamp2Date(Long.toString(TimeStamp))); - item.put(KEY_LNG_LAT_WGS, "[经度:" + doubleLongitude + " 纬度:" + doubleLatitude + "]"); - item.put(KEY_LNG_LAT_CUSTOM, "[经度:" + doubleBDLongitude + " 纬度:" + doubleBDLatitude + "]"); - data.add(item); + + String timeStr = GoUtils.timeStamp2Date(Long.toString(TimeStamp)); + String wgsLatLng = "[经度:" + doubleLongitude + " 纬度:" + doubleLatitude + "]"; + String bdLatLng = "[经度:" + doubleBDLongitude + " 纬度:" + doubleBDLatitude + "]"; + + data.add(new HistoryItem(ID, Location, timeStr, + wgsLatLng, bdLatLng, doubleBDLongitude, doubleBDLatitude)); } cursor.close(); } catch (Exception e) { @@ -208,7 +203,7 @@ private boolean deleteRecord(int ID) { } private void initSearchView() { - SearchView mSearchView = findViewById(R.id.searchView); + mSearchView = findViewById(R.id.searchView); mSearchView.onActionViewExpanded();// 当展开无输入内容的时候,没有关闭的图标 mSearchView.setSubmitButtonEnabled(false);//显示提交按钮 mSearchView.setFocusable(false); @@ -221,55 +216,33 @@ public boolean onQueryTextSubmit(String query) {// 当点击搜索按钮时触 @Override public boolean onQueryTextChange(String newText) {// 当搜索内容改变时触发该方法 - if (TextUtils.isEmpty(newText)) { - SimpleAdapter simAdapt = new SimpleAdapter( - HistoryActivity.this.getBaseContext(), - mAllRecord, - R.layout.history_item, - new String[]{KEY_ID, KEY_LOCATION, KEY_TIME, KEY_LNG_LAT_WGS, KEY_LNG_LAT_CUSTOM}, // 与下面数组元素要一一对应 - new int[]{R.id.LocationID, R.id.LocationText, R.id.TimeText, R.id.WGSLatLngText, R.id.BDLatLngText}); - mRecordListView.setAdapter(simAdapt); - } else { - List> searchRet = new ArrayList<>(); - for (int i = 0; i < mAllRecord.size(); i++){ - if (mAllRecord.get(i).toString().indexOf(newText) > 0){ - searchRet.add(mAllRecord.get(i)); - } - } - if (!searchRet.isEmpty()) { - SimpleAdapter simAdapt = new SimpleAdapter( - HistoryActivity.this.getBaseContext(), - searchRet, - R.layout.history_item, - new String[]{KEY_ID, KEY_LOCATION, KEY_TIME, KEY_LNG_LAT_WGS, KEY_LNG_LAT_CUSTOM}, // 与下面数组元素要一一对应 - new int[]{R.id.LocationID, R.id.LocationText, R.id.TimeText, R.id.WGSLatLngText, R.id.BDLatLngText}); - mRecordListView.setAdapter(simAdapt); - } else { + if (mAdapter != null) { + mAdapter.filter(newText); + if (!TextUtils.isEmpty(newText) && mAdapter.isFilteredEmpty()) { GoUtils.DisplayToast(HistoryActivity.this, getResources().getString(R.string.history_error_search)); - SimpleAdapter simAdapt = new SimpleAdapter( - HistoryActivity.this.getBaseContext(), - mAllRecord, - R.layout.history_item, - new String[]{KEY_ID, KEY_LOCATION, KEY_TIME, KEY_LNG_LAT_WGS, KEY_LNG_LAT_CUSTOM}, // 与下面数组元素要一一对应 - new int[]{R.id.LocationID, R.id.LocationText, R.id.TimeText, R.id.WGSLatLngText, R.id.BDLatLngText}); - mRecordListView.setAdapter(simAdapt); + mAdapter.filter(""); // 无结果时回退到全部 } } - return false; } }); } - private void showDeleteDialog(String locID) { + private void showDeleteDialog(int locId) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("警告"); builder.setMessage("确定要删除该项历史记录吗?"); builder.setPositiveButton("确定", (dialog, whichButton) -> { - boolean deleteRet = deleteRecord(Integer.parseInt(locID)); + boolean deleteRet = deleteRecord(locId); if (deleteRet) { GoUtils.DisplayToast(HistoryActivity.this, getResources().getString(R.string.history_delete_ok)); - updateRecordList(); + boolean removed = mAdapter.removeItemById(locId); + if (!removed || mAdapter.isFilteredEmpty()) { + // 如果当前过滤列表为空,重新加载 + updateRecordList(); + } else if (mAdapter.getItemCount() == 0) { + updateEmptyView(); + } } }); builder.setNegativeButton("取消", null); @@ -277,18 +250,20 @@ private void showDeleteDialog(String locID) { builder.show(); } - private void showInputDialog(String locID, String name) { + private void showInputDialog(int locId, String name) { final EditText input = new EditText(this); input.setInputType(InputType.TYPE_CLASS_TEXT); input.setText(name); + input.setSelectAllOnFocus(true); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("名称"); builder.setView(input); builder.setPositiveButton("确认", (dialog, whichButton) -> { String userInput = input.getText().toString(); - DataBaseHistoryLocation.updateHistoryLocation(mHistoryLocationDB, locID, userInput); - updateRecordList(); + DataBaseHistoryLocation.updateHistoryLocation(mHistoryLocationDB, String.valueOf(locId), userInput); + // 精准刷新单条 item,保留搜索状态,不会触发全部 item 刷新 + mAdapter.updateItemTitle(locId, userInput); }); builder.setNegativeButton("取消", null); @@ -318,45 +293,45 @@ private void initRecordListView() { noRecordText = findViewById(R.id.record_no_textview); mSearchLayout = findViewById(R.id.search_linear); mRecordListView = findViewById(R.id.record_list_view); - mRecordListView.setOnItemClickListener((adapterView, view, i, l) -> { - String bd09Longitude; - String bd09Latitude; - String name; - name = (String) ((TextView) view.findViewById(R.id.LocationText)).getText(); - String bd09LatLng = (String) ((TextView) view.findViewById(R.id.BDLatLngText)).getText(); - bd09LatLng = bd09LatLng.substring(bd09LatLng.indexOf('[') + 1, bd09LatLng.indexOf(']')); - String[] latLngStr = bd09LatLng.split(" "); - bd09Longitude = latLngStr[0].substring(latLngStr[0].indexOf(':') + 1); - bd09Latitude = latLngStr[1].substring(latLngStr[1].indexOf(':') + 1); + + // RecyclerView 配置 + mRecordListView.setLayoutManager(new LinearLayoutManager(this)); + + mAdapter = new HistoryAdapter(); + + // 点击 item:导航到该位置 + mAdapter.setOnItemClickListener(item -> { + String bdLongitudeStr = String.valueOf(item.getBdLongitude()); + String bdLatitudeStr = String.valueOf(item.getBdLatitude()); + String name = item.getLocation(); // Random offset - if(sharedPreferences.getBoolean("setting_random_offset", false)) { - String[] offsetResult = randomOffset(bd09Longitude, bd09Latitude); - bd09Longitude = offsetResult[0]; - bd09Latitude = offsetResult[1]; + if (sharedPreferences.getBoolean("setting_random_offset", false)) { + String[] offsetResult = randomOffset(bdLongitudeStr, bdLatitudeStr); + bdLongitudeStr = offsetResult[0]; + bdLatitudeStr = offsetResult[1]; } - if (!MainActivity.showLocation(name, bd09Longitude, bd09Latitude)) { + if (!MainActivity.showLocation(name, bdLongitudeStr, bdLatitudeStr)) { GoUtils.DisplayToast(this, getResources().getString(R.string.history_error_location)); } this.finish(); }); - mRecordListView.setOnItemLongClickListener((parent, view, position, id) -> { - PopupMenu popupMenu = new PopupMenu(HistoryActivity.this, view); + // 长按 item:弹出编辑/删除菜单 + mAdapter.setOnItemLongClickListener((anchorView, item) -> { + PopupMenu popupMenu = new PopupMenu(HistoryActivity.this, anchorView); popupMenu.setGravity(Gravity.END | Gravity.BOTTOM); popupMenu.getMenu().add("编辑"); popupMenu.getMenu().add("删除"); - popupMenu.setOnMenuItemClickListener(item -> { - String locID = ((TextView) view.findViewById(R.id.LocationID)).getText().toString(); - String name = ((TextView) view.findViewById(R.id.LocationText)).getText().toString(); - switch (item.getTitle().toString()) { + popupMenu.setOnMenuItemClickListener(menuItem -> { + switch (menuItem.getTitle().toString()) { case "编辑": - showInputDialog(locID, name); + showInputDialog(item.getId(), item.getLocation()); return true; case "删除": - showDeleteDialog(locID); + showDeleteDialog(item.getId()); return true; default: return false; @@ -364,34 +339,40 @@ private void initRecordListView() { }); popupMenu.show(); - return true; }); + mRecordListView.setAdapter(mAdapter); + updateRecordList(); } - private void updateRecordList() { - mAllRecord = fetchAllRecord(); - - if (mAllRecord.isEmpty()) { - mRecordListView.setVisibility(View.GONE); - mSearchLayout.setVisibility(View.GONE); - noRecordText.setVisibility(View.VISIBLE); - } else { + private void updateEmptyView() { + if (mAdapter != null && !mAdapter.isFilteredEmpty()) { noRecordText.setVisibility(View.GONE); mRecordListView.setVisibility(View.VISIBLE); mSearchLayout.setVisibility(View.VISIBLE); + } else { + mRecordListView.setVisibility(View.GONE); + mSearchLayout.setVisibility(View.GONE); + noRecordText.setVisibility(View.VISIBLE); + } + } + + private void updateRecordList() { + mAllItems = fetchAllRecord(); - try { - SimpleAdapter simAdapt = new SimpleAdapter( - this, - mAllRecord, - R.layout.history_item, - new String[]{KEY_ID, KEY_LOCATION, KEY_TIME, KEY_LNG_LAT_WGS, KEY_LNG_LAT_CUSTOM}, - new int[]{R.id.LocationID, R.id.LocationText, R.id.TimeText, R.id.WGSLatLngText, R.id.BDLatLngText}); - mRecordListView.setAdapter(simAdapt); - } catch (Exception e) { - Log.e("HistoryActivity", "ERROR - updateRecordList"); + if (mAllItems.isEmpty()) { + mAdapter.setData(null); + updateEmptyView(); + } else { + mAdapter.setData(mAllItems); + updateEmptyView(); + // 恢复当前搜索过滤状态 + if (mSearchView != null) { + String currentQuery = mSearchView.getQuery().toString(); + if (!TextUtils.isEmpty(currentQuery)) { + mAdapter.filter(currentQuery); + } } } } diff --git a/app/src/main/java/com/zcshou/gogogo/HistoryAdapter.java b/app/src/main/java/com/zcshou/gogogo/HistoryAdapter.java new file mode 100644 index 00000000..5289bf26 --- /dev/null +++ b/app/src/main/java/com/zcshou/gogogo/HistoryAdapter.java @@ -0,0 +1,170 @@ +package com.zcshou.gogogo; + +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.List; + +public class HistoryAdapter extends RecyclerView.Adapter { + private final List allItems = new ArrayList<>(); + private final List filteredItems = new ArrayList<>(); + private OnItemClickListener onItemClickListener; + private OnItemLongClickListener onItemLongClickListener; + + public interface OnItemClickListener { + void onItemClick(HistoryItem item); + } + + public interface OnItemLongClickListener { + void onItemLongClick(View anchorView, HistoryItem item); + } + + public void setOnItemClickListener(OnItemClickListener listener) { + this.onItemClickListener = listener; + } + + public void setOnItemLongClickListener(OnItemLongClickListener listener) { + this.onItemLongClickListener = listener; + } + + // ==================== 数据操作 ==================== + + /** + * 设置全量数据(首次加载 / 搜索重置 / 删除全部后调用) + */ + public void setData(List items) { + allItems.clear(); + if (items != null) { + allItems.addAll(items); + } + // 默认显示全部 + filteredItems.clear(); + filteredItems.addAll(allItems); + notifyDataSetChanged(); + } + + /** + * 更新单条 item 的标题(仅刷新该 item,不重建列表) + */ + public void updateItemTitle(int id, String newTitle) { + // 同步更新 allItems + for (HistoryItem item : allItems) { + if (item.getId() == id) { + item.setLocation(newTitle); + break; + } + } + // 同步更新 filteredItems 并精准刷新 + for (int i = 0; i < filteredItems.size(); i++) { + if (filteredItems.get(i).getId() == id) { + filteredItems.get(i).setLocation(newTitle); + notifyItemChanged(i); + break; + } + } + } + + /** + * 删除单条 item(仅刷新删除项,不重建列表) + */ + public boolean removeItemById(int id) { + // 从 allItems 中移除 + for (int i = 0; i < allItems.size(); i++) { + if (allItems.get(i).getId() == id) { + allItems.remove(i); + break; + } + } + // 从 filteredItems 中移除 + for (int i = 0; i < filteredItems.size(); i++) { + if (filteredItems.get(i).getId() == id) { + filteredItems.remove(i); + notifyItemRemoved(i); + return true; + } + } + return false; + } + + /** + * 搜索过滤 + */ + public void filter(String query) { + filteredItems.clear(); + if (TextUtils.isEmpty(query)) { + filteredItems.addAll(allItems); + } else { + for (HistoryItem item : allItems) { + if (item.matches(query)) { + filteredItems.add(item); + } + } + } + notifyDataSetChanged(); + } + + /** + * 当前过滤后的数据是否为空 + */ + public boolean isFilteredEmpty() { + return filteredItems.isEmpty(); + } + + // ==================== RecyclerView.Adapter ==================== + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.history_item, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + HistoryItem item = filteredItems.get(position); + holder.locationText.setText(item.getLocation()); + holder.timeText.setText(item.getTime()); + holder.wgsLatLngText.setText(item.getWgsLatLngText()); + + holder.itemView.setOnClickListener(v -> { + if (onItemClickListener != null) { + onItemClickListener.onItemClick(item); + } + }); + + holder.itemView.setOnLongClickListener(v -> { + if (onItemLongClickListener != null) { + onItemLongClickListener.onItemLongClick(v, item); + } + return true; // 消费长按事件 + }); + } + + @Override + public int getItemCount() { + return filteredItems.size(); + } + + // ==================== ViewHolder ==================== + + static class ViewHolder extends RecyclerView.ViewHolder { + TextView locationText; + TextView timeText; + TextView wgsLatLngText; + + ViewHolder(@NonNull View itemView) { + super(itemView); + locationText = itemView.findViewById(R.id.LocationText); + timeText = itemView.findViewById(R.id.TimeText); + wgsLatLngText = itemView.findViewById(R.id.WGSLatLngText); + } + } +} diff --git a/app/src/main/java/com/zcshou/gogogo/HistoryItem.java b/app/src/main/java/com/zcshou/gogogo/HistoryItem.java new file mode 100644 index 00000000..2e2da6d5 --- /dev/null +++ b/app/src/main/java/com/zcshou/gogogo/HistoryItem.java @@ -0,0 +1,50 @@ +package com.zcshou.gogogo; + +public class HistoryItem { + private final int id; + private String location; + private final String time; + private final String wgsLatLngText; + private final String bdLatLngText; + private final double bdLongitude; + private final double bdLatitude; + + public HistoryItem(int id, String location, String time, + String wgsLatLngText, String bdLatLngText, + double bdLongitude, double bdLatitude) { + this.id = id; + this.location = location; + this.time = time; + this.wgsLatLngText = wgsLatLngText; + this.bdLatLngText = bdLatLngText; + this.bdLongitude = bdLongitude; + this.bdLatitude = bdLatitude; + } + + public int getId() { return id; } + + public String getLocation() { return location; } + + public void setLocation(String location) { this.location = location; } + + public String getTime() { return time; } + + public String getWgsLatLngText() { return wgsLatLngText; } + + public String getBdLatLngText() { return bdLatLngText; } + + public double getBdLongitude() { return bdLongitude; } + + public double getBdLatitude() { return bdLatitude; } + + /** + * 用于搜索匹配,检查所有可搜索字段 + */ + public boolean matches(String query) { + if (query == null || query.isEmpty()) return true; + String lowerQuery = query.toLowerCase(); + return location.toLowerCase().contains(lowerQuery) + || time.toLowerCase().contains(lowerQuery) + || wgsLatLngText.toLowerCase().contains(lowerQuery); + } +} diff --git a/app/src/main/java/com/zcshou/joystick/JoyStick.java b/app/src/main/java/com/zcshou/joystick/JoyStick.java index 859b9efc..927f48c2 100644 --- a/app/src/main/java/com/zcshou/joystick/JoyStick.java +++ b/app/src/main/java/com/zcshou/joystick/JoyStick.java @@ -23,6 +23,8 @@ import android.widget.SearchView; import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import com.baidu.mapapi.map.BaiduMap; import com.baidu.mapapi.map.MapPoi; @@ -35,7 +37,8 @@ import com.baidu.mapapi.search.sug.SuggestionSearch; import com.baidu.mapapi.search.sug.SuggestionSearchOption; import com.zcshou.database.DataBaseHistoryLocation; -import com.zcshou.gogogo.HistoryActivity; +import com.zcshou.gogogo.HistoryAdapter; +import com.zcshou.gogogo.HistoryItem; import com.zcshou.gogogo.MainActivity; import com.zcshou.gogogo.R; import com.zcshou.utils.GoUtils; @@ -80,10 +83,10 @@ public class JoyStick extends View { private final SharedPreferences sharedPreferences; /* 历史记录悬浮窗相关 */ private FrameLayout mHistoryLayout; - private final List> mAllRecord = new ArrayList<> (); + private final List mAllItems = new ArrayList<>(); private TextView noRecordText; - private ListView mRecordListView; - /* 地图悬浮窗相关 */ + private RecyclerView mRecordListView; + private HistoryAdapter mHistoryAdapter; private FrameLayout mMapLayout; private MapView mMapView; private BaiduMap mBaiduMap; @@ -687,31 +690,23 @@ public boolean onQueryTextSubmit(String query) {// 当点击搜索按钮时触 @Override public boolean onQueryTextChange(String newText) {// 当搜索内容改变时触发该方法 - if (TextUtils.isEmpty(newText)) { - showHistory(mAllRecord); - } else { - List> searchRet = new ArrayList<>(); - for (int i = 0; i < mAllRecord.size(); i++){ - if (mAllRecord.get(i).toString().indexOf(newText) > 0){ - searchRet.add(mAllRecord.get(i)); - } - } - - if (searchRet.size() > 0) { - showHistory(searchRet); - } else { + if (mHistoryAdapter != null) { + mHistoryAdapter.filter(newText); + if (!TextUtils.isEmpty(newText) && mHistoryAdapter.isFilteredEmpty()) { GoUtils.DisplayToast(mContext, getResources().getString(R.string.app_search_null)); - showHistory(mAllRecord); + mHistoryAdapter.filter(""); // 无结果时回退到全部 } } - return false; } }); noRecordText = mHistoryLayout.findViewById(R.id.joystick_his_record_no_textview); mRecordListView = mHistoryLayout.findViewById(R.id.joystick_his_record_list_view); - mRecordListView.setOnItemClickListener((adapterView, view, i, l) -> { + mRecordListView.setLayoutManager(new LinearLayoutManager(mContext)); + + mHistoryAdapter = new HistoryAdapter(); + mHistoryAdapter.setOnItemClickListener(item -> { // 关闭时清除焦点 mWindowParamCurrent.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL @@ -722,29 +717,25 @@ public boolean onQueryTextChange(String newText) {// 当搜索内容改变时触 mSearchView.onActionViewCollapsed(); tips.setVisibility(VISIBLE); - // wgs84坐标 - String wgs84LatLng = (String) ((TextView) view.findViewById(R.id.WGSLatLngText)).getText(); - wgs84LatLng = wgs84LatLng.substring(wgs84LatLng.indexOf('[') + 1, wgs84LatLng.indexOf(']')); - String[] wgs84latLngStr = wgs84LatLng.split(" "); + // 从 HistoryItem 直接获取坐标,无需从 TextView 解析 + String wgs84LatLngText = item.getWgsLatLngText(); + wgs84LatLngText = wgs84LatLngText.substring(wgs84LatLngText.indexOf('[') + 1, wgs84LatLngText.indexOf(']')); + String[] wgs84latLngStr = wgs84LatLngText.split(" "); String wgs84Longitude = wgs84latLngStr[0].substring(wgs84latLngStr[0].indexOf(':') + 1); String wgs84Latitude = wgs84latLngStr[1].substring(wgs84latLngStr[1].indexOf(':') + 1); mListener.onPositionInfo(Double.parseDouble(wgs84Longitude), Double.parseDouble(wgs84Latitude), mAltitude); - // 注意这里在选择位置之后需要刷新地图 - String bdLatLng = (String) ((TextView) view.findViewById(R.id.BDLatLngText)).getText(); - bdLatLng = bdLatLng.substring(bdLatLng.indexOf('[') + 1, bdLatLng.indexOf(']')); - String[] bdLatLngStr = bdLatLng.split(" "); - String bdLongitude = bdLatLngStr[0].substring(bdLatLngStr[0].indexOf(':') + 1); - String bdLatitude = bdLatLngStr[1].substring(bdLatLngStr[1].indexOf(':') + 1); - mCurMapLngLat = new LatLng(Double.parseDouble(bdLatitude), Double.parseDouble(bdLongitude)); + // 更新地图中心 + mCurMapLngLat = new LatLng(item.getBdLatitude(), item.getBdLongitude()); GoUtils.DisplayToast(mContext, getResources().getString(R.string.app_location_ok)); }); - fetchAllRecord(); + mRecordListView.setAdapter(mHistoryAdapter); - showHistory(mAllRecord); + fetchAllRecord(); + updateHistoryView(); ImageButton btnClose = mHistoryLayout.findViewById(R.id.joystick_his_close); btnClose.setOnClickListener(v -> { @@ -774,7 +765,6 @@ private void fetchAllRecord() { null, null, DataBaseHistoryLocation.DB_COLUMN_TIMESTAMP + " DESC", null); while (cursor.moveToNext()) { - Map item = new HashMap<>(); int ID = cursor.getInt(0); String Location = cursor.getString(1); String Longitude = cursor.getString(2); @@ -791,12 +781,13 @@ private void fetchAllRecord() { double doubleLatitude = bigDecimalLatitude.setScale(11, RoundingMode.HALF_UP).doubleValue(); double doubleBDLongitude = bigDecimalBDLongitude.setScale(11, RoundingMode.HALF_UP).doubleValue(); double doubleBDLatitude = bigDecimalBDLatitude.setScale(11, RoundingMode.HALF_UP).doubleValue(); - item.put(HistoryActivity.KEY_ID, Integer.toString(ID)); - item.put(HistoryActivity.KEY_LOCATION, Location); - item.put(HistoryActivity.KEY_TIME, GoUtils.timeStamp2Date(Long.toString(TimeStamp))); - item.put(HistoryActivity.KEY_LNG_LAT_WGS, "[经度:" + doubleLongitude + " 纬度:" + doubleLatitude + "]"); - item.put(HistoryActivity.KEY_LNG_LAT_CUSTOM, "[经度:" + doubleBDLongitude + " 纬度:" + doubleBDLatitude + "]"); - mAllRecord.add(item); + + String timeStr = GoUtils.timeStamp2Date(Long.toString(TimeStamp)); + String wgsLatLng = "[经度:" + doubleLongitude + " 纬度:" + doubleLatitude + "]"; + String bdLatLng = "[经度:" + doubleBDLongitude + " 纬度:" + doubleBDLatitude + "]"; + + mAllItems.add(new HistoryItem(ID, Location, timeStr, + wgsLatLng, bdLatLng, doubleBDLongitude, doubleBDLatitude)); } cursor.close(); mHistoryLocationDB.close(); @@ -805,25 +796,14 @@ private void fetchAllRecord() { } } - private void showHistory(List> list) { - if (list.size() == 0) { + private void updateHistoryView() { + if (mAllItems.isEmpty()) { mRecordListView.setVisibility(View.GONE); noRecordText.setVisibility(View.VISIBLE); } else { noRecordText.setVisibility(View.GONE); mRecordListView.setVisibility(View.VISIBLE); - - try { - SimpleAdapter simAdapt = new SimpleAdapter( - mContext, - list, - R.layout.history_item, - new String[]{HistoryActivity.KEY_ID, HistoryActivity.KEY_LOCATION, HistoryActivity.KEY_TIME, HistoryActivity.KEY_LNG_LAT_WGS, HistoryActivity.KEY_LNG_LAT_CUSTOM}, // 与下面数组元素要一一对应 - new int[]{R.id.LocationID, R.id.LocationText, R.id.TimeText, R.id.WGSLatLngText, R.id.BDLatLngText}); - mRecordListView.setAdapter(simAdapt); - } catch (Exception e) { - Log.e("JOYSTICK", "ERROR - showHistory"); - } + mHistoryAdapter.setData(mAllItems); } } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_history.xml b/app/src/main/res/layout/activity_history.xml index 50af4fdd..fb6c8fa0 100644 --- a/app/src/main/res/layout/activity_history.xml +++ b/app/src/main/res/layout/activity_history.xml @@ -34,11 +34,11 @@ android:textAlignment="center" android:layout_gravity="center"/> - - + \ No newline at end of file diff --git a/app/src/main/res/layout/joystick_history.xml b/app/src/main/res/layout/joystick_history.xml index 0ac9aa1a..db6c7404 100644 --- a/app/src/main/res/layout/joystick_history.xml +++ b/app/src/main/res/layout/joystick_history.xml @@ -57,11 +57,11 @@ android:textAlignment="center" android:layout_gravity="center"/> - - + From 99e87383cd2774937a3b2b7423222212a939dcd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=A3=8A?= <913305160@qq.com> Date: Mon, 8 Jun 2026 15:12:40 +0800 Subject: [PATCH 3/8] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95=E4=B8=AD=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E5=AE=9A=E4=BD=8D=EF=BC=8C=E4=BC=9A=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E6=8F=92=E5=85=A5=E6=96=B0=E8=AE=B0=E5=BD=95=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../database/DataBaseHistoryLocation.java | 12 +++++++ .../com/zcshou/gogogo/HistoryActivity.java | 2 +- .../java/com/zcshou/gogogo/MainActivity.java | 36 ++++++++++++++++--- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/zcshou/database/DataBaseHistoryLocation.java b/app/src/main/java/com/zcshou/database/DataBaseHistoryLocation.java index 4e0b7c53..ae822c97 100644 --- a/app/src/main/java/com/zcshou/database/DataBaseHistoryLocation.java +++ b/app/src/main/java/com/zcshou/database/DataBaseHistoryLocation.java @@ -66,4 +66,16 @@ public static void updateHistoryLocation(SQLiteDatabase sqLiteDatabase, String l XLog.e("DATABASE: update error"); } } + + // 更新历史记录的时间戳和名称(用于从历史记录中重新选择位置时,仅更新时间) + public static void updateHistoryTimestamp(SQLiteDatabase sqLiteDatabase, String locID, String location, long timestamp) { + try { + ContentValues contentValues = new ContentValues(); + contentValues.put(DB_COLUMN_LOCATION, location); + contentValues.put(DB_COLUMN_TIMESTAMP, timestamp); + sqLiteDatabase.update(TABLE_NAME, contentValues, DB_COLUMN_ID + " = ?", new String[]{locID}); + } catch (Exception e) { + XLog.e("DATABASE: update timestamp error"); + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/zcshou/gogogo/HistoryActivity.java b/app/src/main/java/com/zcshou/gogogo/HistoryActivity.java index 65eefaf1..cdc83791 100644 --- a/app/src/main/java/com/zcshou/gogogo/HistoryActivity.java +++ b/app/src/main/java/com/zcshou/gogogo/HistoryActivity.java @@ -312,7 +312,7 @@ private void initRecordListView() { bdLatitudeStr = offsetResult[1]; } - if (!MainActivity.showLocation(name, bdLongitudeStr, bdLatitudeStr)) { + if (!MainActivity.showLocation(item.getId(), name, bdLongitudeStr, bdLatitudeStr)) { GoUtils.DisplayToast(this, getResources().getString(R.string.history_error_location)); } this.finish(); diff --git a/app/src/main/java/com/zcshou/gogogo/MainActivity.java b/app/src/main/java/com/zcshou/gogogo/MainActivity.java index 641d6bcb..1ca3d41d 100644 --- a/app/src/main/java/com/zcshou/gogogo/MainActivity.java +++ b/app/src/main/java/com/zcshou/gogogo/MainActivity.java @@ -128,7 +128,8 @@ public class MainActivity extends BaseActivity implements SensorEventListener { private MapView mMapView; private static BaiduMap mBaiduMap = null; private static LatLng mMarkLatLngMap = new LatLng(36.547743718042415, 117.07018449827267); // 当前标记的地图点 - private static String mMarkName = null; + private static String mMarkName = null; + private static int mMarkHistoryId = -1; // 从历史记录中选择的位置 ID,-1 表示非历史记录 private GeoCoder mGeoCoder; private SensorManager mSensorManager; private Sensor mSensorAccelerometer; @@ -762,6 +763,17 @@ private void resetMap() { // 在地图上显示位置 public static boolean showLocation(String name, String bd09Longitude, String bd09Latitude) { + mMarkHistoryId = -1; // 非历史记录来源,重置 + return showLocationInternal(name, bd09Longitude, bd09Latitude); + } + + // 从历史记录中选择位置(携带历史记录 ID,点击 faBtnStart 时将更新该条记录的时间戳而非新增) + public static boolean showLocation(int historyId, String name, String bd09Longitude, String bd09Latitude) { + mMarkHistoryId = historyId; + return showLocationInternal(name, bd09Longitude, bd09Latitude); + } + + private static boolean showLocationInternal(String name, String bd09Longitude, String bd09Latitude) { boolean ret = true; try { @@ -940,7 +952,7 @@ public void onFailure(@NonNull Call call, @NonNull IOException e) { contentValues.put(DataBaseHistoryLocation.DB_COLUMN_LONGITUDE_CUSTOM, Double.toString(lng)); contentValues.put(DataBaseHistoryLocation.DB_COLUMN_LATITUDE_CUSTOM, Double.toString(lat)); - DataBaseHistoryLocation.saveHistoryLocation(mLocationHistoryDB, contentValues); + saveOrUpdateHistoryLocation(contentValues); } @Override @@ -961,7 +973,7 @@ public void onResponse(@NonNull Call call, @NonNull Response response) throws IO contentValues.put(DataBaseHistoryLocation.DB_COLUMN_TIMESTAMP, System.currentTimeMillis() / 1000); contentValues.put(DataBaseHistoryLocation.DB_COLUMN_LONGITUDE_CUSTOM, Double.toString(lng)); contentValues.put(DataBaseHistoryLocation.DB_COLUMN_LATITUDE_CUSTOM, Double.toString(lat)); - DataBaseHistoryLocation.saveHistoryLocation(mLocationHistoryDB, contentValues); + saveOrUpdateHistoryLocation(contentValues); } else { ContentValues contentValues = new ContentValues(); contentValues.put(DataBaseHistoryLocation.DB_COLUMN_LOCATION, mMarkName == null ? getRetJson.getString("message"): mMarkName); @@ -970,7 +982,7 @@ public void onResponse(@NonNull Call call, @NonNull Response response) throws IO contentValues.put(DataBaseHistoryLocation.DB_COLUMN_TIMESTAMP, System.currentTimeMillis() / 1000); contentValues.put(DataBaseHistoryLocation.DB_COLUMN_LONGITUDE_CUSTOM, Double.toString(lng)); contentValues.put(DataBaseHistoryLocation.DB_COLUMN_LATITUDE_CUSTOM, Double.toString(lat)); - DataBaseHistoryLocation.saveHistoryLocation(mLocationHistoryDB, contentValues); + saveOrUpdateHistoryLocation(contentValues); } } catch (JSONException e) { XLog.e("JSON: resolve json error"); @@ -982,13 +994,27 @@ public void onResponse(@NonNull Call call, @NonNull Response response) throws IO contentValues.put(DataBaseHistoryLocation.DB_COLUMN_TIMESTAMP, System.currentTimeMillis() / 1000); contentValues.put(DataBaseHistoryLocation.DB_COLUMN_LONGITUDE_CUSTOM, Double.toString(lng)); contentValues.put(DataBaseHistoryLocation.DB_COLUMN_LATITUDE_CUSTOM, Double.toString(lat)); - DataBaseHistoryLocation.saveHistoryLocation(mLocationHistoryDB, contentValues); + saveOrUpdateHistoryLocation(contentValues); } } } }); } + // 保存或更新历史记录:如果来源是历史记录(mMarkHistoryId > 0),则更新时间戳;否则新增 + private void saveOrUpdateHistoryLocation(ContentValues contentValues) { + if (mMarkHistoryId > 0) { + String name = contentValues.getAsString(DataBaseHistoryLocation.DB_COLUMN_LOCATION); + Long timestamp = contentValues.getAsLong(DataBaseHistoryLocation.DB_COLUMN_TIMESTAMP); + DataBaseHistoryLocation.updateHistoryTimestamp(mLocationHistoryDB, + String.valueOf(mMarkHistoryId), name != null ? name : "", + timestamp != null ? timestamp : System.currentTimeMillis() / 1000); + mMarkHistoryId = -1; + } else { + DataBaseHistoryLocation.saveHistoryLocation(mLocationHistoryDB, contentValues); + } + } + /*============================== SearchView 相关 ==============================*/ private void initSearchView() { mSearchLayout = findViewById(R.id.search_linear); From 16b78e87bb6f840f58967f0dca69fb64b4b717ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=A3=8A?= <913305160@qq.com> Date: Mon, 8 Jun 2026 15:30:24 +0800 Subject: [PATCH 4/8] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20MainActivity=20?= =?UTF-8?q?=E4=B8=AD=20mMarkName=20=E5=A4=9A=E4=B8=AA=E5=9C=BA=E6=99=AF?= =?UTF-8?q?=E4=B8=8B=E6=9C=AA=E5=8F=8A=E6=97=B6=E6=9B=B4=E6=96=B0=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95=E6=A0=87=E9=A2=98?= =?UTF-8?q?=EF=BC=88=E4=BD=8D=E7=BD=AE=E4=BF=A1=E6=81=AF=EF=BC=89=E6=80=BB?= =?UTF-8?q?=E6=98=AF=E6=AE=8B=E7=95=99=E4=B8=8A=E6=AC=A1=E7=9A=84=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/zcshou/gogogo/MainActivity.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/zcshou/gogogo/MainActivity.java b/app/src/main/java/com/zcshou/gogogo/MainActivity.java index 1ca3d41d..4f20d8cf 100644 --- a/app/src/main/java/com/zcshou/gogogo/MainActivity.java +++ b/app/src/main/java/com/zcshou/gogogo/MainActivity.java @@ -471,16 +471,20 @@ private void initMap() { */ @Override public void onMapClick(LatLng point) { + mMarkHistoryId = -1; mMarkLatLngMap = point; markMap(); + mGeoCoder.reverseGeoCode(new ReverseGeoCodeOption().location(point)); } /** * 单击地图中的POI点 */ @Override public void onMapPoiClick(MapPoi poi) { + mMarkHistoryId = -1; mMarkLatLngMap = poi.getPosition(); markMap(); + mGeoCoder.reverseGeoCode(new ReverseGeoCodeOption().location(mMarkLatLngMap)); } }); mBaiduMap.setOnMapLongClickListener(new BaiduMap.OnMapLongClickListener() { @@ -489,6 +493,7 @@ public void onMapPoiClick(MapPoi poi) { */ @Override public void onMapLongClick(LatLng point) { + mMarkHistoryId = -1; mMarkLatLngMap = point; markMap(); mGeoCoder.reverseGeoCode(new ReverseGeoCodeOption().location(point)); @@ -712,6 +717,7 @@ private void initMapButton() { if (dialog_lat_double > 90.0 || dialog_lat_double < -90.0) { GoUtils.DisplayToast(MainActivity.this, getResources().getString(R.string.app_error_latitude)); } else { + mMarkHistoryId = -1; if (rbBD.isChecked()) { mMarkLatLngMap = new LatLng(dialog_lat_double, dialog_lng_double); } else { @@ -1024,6 +1030,7 @@ private void initSearchView() { mSearchList.setOnItemClickListener((parent, view, position, id) -> { String lng = ((TextView) view.findViewById(R.id.poi_longitude)).getText().toString(); String lat = ((TextView) view.findViewById(R.id.poi_latitude)).getText().toString(); + mMarkHistoryId = -1; mMarkName = ((TextView) view.findViewById(R.id.poi_name)).getText().toString(); mMarkLatLngMap = new LatLng(Double.parseDouble(lat), Double.parseDouble(lng)); MapStatusUpdate mapstatusupdate = MapStatusUpdateFactory.newLatLng(mMarkLatLngMap); @@ -1060,7 +1067,8 @@ private void initSearchView() { if (searchIsLoc.equals("1")) { String lng = ((TextView) view.findViewById(R.id.search_longitude)).getText().toString(); String lat = ((TextView) view.findViewById(R.id.search_latitude)).getText().toString(); - // mMarkName = ((TextView) view.findViewById(R.id.poi_name)).getText().toString(); + mMarkHistoryId = -1; + mMarkName = searchKey; mMarkLatLngMap = new LatLng(Double.parseDouble(lat), Double.parseDouble(lng)); MapStatusUpdate mapstatusupdate = MapStatusUpdateFactory.newLatLng(mMarkLatLngMap); mBaiduMap.setMapStatus(mapstatusupdate); From 637a69aa200851d3d1cebbd5ddc6953257954fcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=A3=8A?= <913305160@qq.com> Date: Mon, 8 Jun 2026 16:03:45 +0800 Subject: [PATCH 5/8] =?UTF-8?q?fix:=201.=20=E4=BF=AE=E5=A4=8D=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E4=B8=AD=E4=BF=AE=E6=94=B9=E7=9A=84=E6=9C=89=E6=95=88?= =?UTF-8?q?=E6=9C=9F=E6=B0=B8=E8=BF=9C=E4=B8=8D=E7=94=9F=E6=95=88=E7=9A=84?= =?UTF-8?q?=20bug=20=20=20=20=20=202.=20=E5=8E=86=E5=8F=B2=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E6=9C=89=E6=95=88=E6=9C=9F=E9=BB=98=E8=AE=A4=E5=80=BC?= =?UTF-8?q?=E4=BB=8E=207=20=E5=A4=A9=E6=8F=90=E5=8D=87=E8=87=B3=2030=20?= =?UTF-8?q?=E5=A4=A9=20=20=20=20=20=203.=20=E5=8E=86=E5=8F=B2=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E6=9C=89=E6=95=88=E6=9C=9F=E8=AE=BE=E7=BD=AE=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=B0=B8=E4=B8=8D=E8=BF=87=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/zcshou/gogogo/FragmentSettings.java | 24 ++++++++++++++++- .../com/zcshou/gogogo/HistoryActivity.java | 14 +++++++--- .../java/com/zcshou/gogogo/MainActivity.java | 27 +++++++++++++++++++ app/src/main/res/values/strings.xml | 4 ++- app/src/main/res/xml/preferences_main.xml | 1 + 5 files changed, 64 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/zcshou/gogogo/FragmentSettings.java b/app/src/main/java/com/zcshou/gogogo/FragmentSettings.java index 1e98d042..0499baa6 100644 --- a/app/src/main/java/com/zcshou/gogogo/FragmentSettings.java +++ b/app/src/main/java/com/zcshou/gogogo/FragmentSettings.java @@ -84,7 +84,29 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { } EditTextPreference pfPosHisValid = findPreference("setting_history_expiration"); - setupDecimalEditTextPreference(pfPosHisValid); + if (pfPosHisValid != null) { + pfPosHisValid.setSummaryProvider((Preference.SummaryProvider) EditTextPreference::getText); + pfPosHisValid.setOnBindEditTextListener(editText -> { + editText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED); + editText.setSelection(editText.length()); + }); + // 当用户设置为永不过期(≤ 0)时弹出警告 + pfPosHisValid.setOnPreferenceChangeListener((pref, newValue) -> { + if (newValue.toString().trim().isEmpty()) { + GoUtils.DisplayToast(this.getContext(), getResources().getString(R.string.app_error_input_null)); + return false; + } + try { + if (Double.parseDouble(newValue.toString()) <= 0) { + GoUtils.DisplayToast(this.getContext(), getResources().getString(R.string.history_expiration_never_warn)); + } + } catch (NumberFormatException e) { + GoUtils.DisplayToast(this.getContext(), getResources().getString(R.string.app_error_input)); + return false; + } + return true; + }); + } // 设置版本号 String verName; diff --git a/app/src/main/java/com/zcshou/gogogo/HistoryActivity.java b/app/src/main/java/com/zcshou/gogogo/HistoryActivity.java index cdc83791..39e9a08a 100644 --- a/app/src/main/java/com/zcshou/gogogo/HistoryActivity.java +++ b/app/src/main/java/com/zcshou/gogogo/HistoryActivity.java @@ -170,15 +170,21 @@ private List fetchAllRecord() { private void recordArchive() { double limits; try { - limits = Double.parseDouble(sharedPreferences.getString("setting_pos_history", getResources().getString(R.string.history_expiration))); + limits = Double.parseDouble(sharedPreferences.getString("setting_history_expiration", getResources().getString(R.string.history_expiration))); } catch (NumberFormatException e) { // GOOD: The exception is caught. - limits = 7; + limits = 30; } - final long weekSecond = (long) (limits * 24 * 60 * 60); + + // limits <= 0 表示永不过期 + if (limits <= 0) { + return; + } + + final long expireSecond = (long) (limits * 24 * 60 * 60); try { mHistoryLocationDB.delete(DataBaseHistoryLocation.TABLE_NAME, - DataBaseHistoryLocation.DB_COLUMN_TIMESTAMP + " < ?", new String[] {Long.toString(System.currentTimeMillis() / 1000 - weekSecond)}); + DataBaseHistoryLocation.DB_COLUMN_TIMESTAMP + " < ?", new String[] {Long.toString(System.currentTimeMillis() / 1000 - expireSecond)}); } catch (Exception e) { Log.e("HistoryActivity", "ERROR - recordArchive"); } diff --git a/app/src/main/java/com/zcshou/gogogo/MainActivity.java b/app/src/main/java/com/zcshou/gogogo/MainActivity.java index 4f20d8cf..7e43423f 100644 --- a/app/src/main/java/com/zcshou/gogogo/MainActivity.java +++ b/app/src/main/java/com/zcshou/gogogo/MainActivity.java @@ -901,11 +901,38 @@ private void initStoreHistory() { // 搜索历史 DataBaseHistorySearch dbHistory = new DataBaseHistorySearch(getApplicationContext()); mSearchHistoryDB = dbHistory.getWritableDatabase(); + + // 清理过期历史记录(与 HistoryActivity.recordArchive 保持一致) + archiveHistoryLocation(); } catch (Exception e) { XLog.e("ERROR: sqlite init error"); } } + // 清理超过有效期的历史定位记录 + private void archiveHistoryLocation() { + double limits; + try { + limits = Double.parseDouble(sharedPreferences.getString("setting_history_expiration", getResources().getString(R.string.history_expiration))); + } catch (NumberFormatException e) { + limits = 30; + } + + // limits <= 0 表示永不过期,不做清理 + if (limits <= 0) { + return; + } + + final long expireSecond = (long) (limits * 24 * 60 * 60); + try { + mLocationHistoryDB.delete(DataBaseHistoryLocation.TABLE_NAME, + DataBaseHistoryLocation.DB_COLUMN_TIMESTAMP + " < ?", + new String[]{Long.toString(System.currentTimeMillis() / 1000 - expireSecond)}); + } catch (Exception e) { + XLog.e("ERROR: archiveHistoryLocation"); + } + } + //获取查询历史 private List> getSearchHistory() { List> data = new ArrayList<>(); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9aad9af8..6acd8c2e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -81,7 +81,8 @@ 开发者 - 7 + 30 + 已设置为永不过期,历史记录将不会被自动清理,请注意数据积累! 地图位置 暂无历史记录 删除成功! @@ -137,4 +138,5 @@ 忽略一次 更新时间: 提交信息: + 永不过期(≤ 0) diff --git a/app/src/main/res/xml/preferences_main.xml b/app/src/main/res/xml/preferences_main.xml index 9d629415..8c5abd13 100644 --- a/app/src/main/res/xml/preferences_main.xml +++ b/app/src/main/res/xml/preferences_main.xml @@ -79,6 +79,7 @@ app:defaultValue="false" app:iconSpaceReserved="false"/> Date: Mon, 8 Jun 2026 16:18:23 +0800 Subject: [PATCH 6/8] =?UTF-8?q?fix:=201.=20=E4=BC=98=E5=8C=96=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E9=A1=B5=E9=80=BB=E8=BE=91=EF=BC=8C=E6=8F=90=E5=8D=87?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C=EF=BC=9A=E6=9D=83=E9=99=90?= =?UTF-8?q?=E5=85=A8=E9=83=A8=E6=8E=88=E4=BA=88=E5=AE=8C=E6=88=90=E5=90=8E?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E8=BF=9B=E5=85=A5=E4=B8=BB=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=EF=BC=8C=E6=97=A0=E9=9C=80=E7=94=A8=E6=88=B7=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E8=BF=9B=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/zcshou/gogogo/WelcomeActivity.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/java/com/zcshou/gogogo/WelcomeActivity.java b/app/src/main/java/com/zcshou/gogogo/WelcomeActivity.java index 8d822a57..9638c4b2 100644 --- a/app/src/main/java/com/zcshou/gogogo/WelcomeActivity.java +++ b/app/src/main/java/com/zcshou/gogogo/WelcomeActivity.java @@ -55,6 +55,11 @@ protected void onCreate(Bundle savedInstanceState) { startBtn.setOnClickListener(v -> startMainActivity()); checkAgreementAndPrivacy(); + + // 已经同意过协议和隐私政策,直接进入主界面 + if (mAgreement && mPrivacy) { + startMainActivity(); + } } @Override @@ -87,6 +92,10 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis } } isPermission = true; + // 已同意协议且所有权限已授予,自动进入主界面 + if (mAgreement && mPrivacy) { + startMainActivity(); + } } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } From 9bba50133957fbd8ab2e5ba060b4e653d8bfccc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=A3=8A?= <913305160@qq.com> Date: Mon, 8 Jun 2026 16:30:04 +0800 Subject: [PATCH 7/8] =?UTF-8?q?fix:=201.=20=E4=BF=AE=E5=A4=8D=20GPS=20?= =?UTF-8?q?=E5=BC=80=E5=85=B3=E5=88=A4=E6=96=AD=E4=B8=8D=E7=B2=BE=E5=87=86?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/zcshou/utils/GoUtils.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/zcshou/utils/GoUtils.java b/app/src/main/java/com/zcshou/utils/GoUtils.java index 623518a9..b0bd34a4 100644 --- a/app/src/main/java/com/zcshou/utils/GoUtils.java +++ b/app/src/main/java/com/zcshou/utils/GoUtils.java @@ -81,9 +81,18 @@ public static boolean isNetworkAvailable(Context context) { } //判断GPS是否打开 + @SuppressWarnings("deprecation") public static boolean isGpsOpened(Context context) { LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + // API 28+ 使用 isLocationEnabled,检查系统级定位开关,避免 Provider 状态缓存问题 + return locationManager.isLocationEnabled(); + } else { + // API 27 回退:直接读取系统设置中的定位模式 + int locationMode = Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); + return locationMode != Settings.Secure.LOCATION_MODE_OFF; + } } // 判断是否已在开发者选项中开启模拟位置权限(注意下面临时添加 @SuppressLint("wrongconstant") 以处理 addTestProvider 参数值的 lint 错误) From 1e983317a7d0f629e5100a99061421c395414489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=A3=8A?= <913305160@qq.com> Date: Mon, 8 Jun 2026 17:47:36 +0800 Subject: [PATCH 8/8] =?UTF-8?q?fix:=201.=20=E4=BF=AE=E5=A4=8D=E5=81=9C?= =?UTF-8?q?=E6=AD=A2=E4=BD=8D=E7=BD=AE=E6=A8=A1=E6=8B=9F=EF=BC=8C=E5=BD=93?= =?UTF-8?q?=E5=89=8D=E4=BD=8D=E7=BD=AE=E6=9C=AA=E7=BD=AE=E4=B8=BA=E7=9C=9F?= =?UTF-8?q?=E5=AE=9E=E4=BD=8D=E7=BD=AE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/zcshou/gogogo/MainActivity.java | 54 +++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/zcshou/gogogo/MainActivity.java b/app/src/main/java/com/zcshou/gogogo/MainActivity.java index 7e43423f..af842600 100644 --- a/app/src/main/java/com/zcshou/gogogo/MainActivity.java +++ b/app/src/main/java/com/zcshou/gogogo/MainActivity.java @@ -128,7 +128,7 @@ public class MainActivity extends BaseActivity implements SensorEventListener { private MapView mMapView; private static BaiduMap mBaiduMap = null; private static LatLng mMarkLatLngMap = new LatLng(36.547743718042415, 117.07018449827267); // 当前标记的地图点 - private static String mMarkName = null; + private static String mMarkName = null; private static int mMarkHistoryId = -1; // 从历史记录中选择的位置 ID,-1 表示非历史记录 private GeoCoder mGeoCoder; private SensorManager mSensorManager; @@ -145,6 +145,11 @@ public class MainActivity extends BaseActivity implements SensorEventListener { private float mCurrentDirection = 0.0f; private boolean isFirstLoc = true; // 是否首次定位 private boolean isMockServStart = false; + private boolean mRestoringPosition = false; // 是否正在恢复真实位置,阻止回调覆盖指示图标 + private double mLastRealLat = 0.0; // 进入模拟前缓存的真实位置,用于停止模拟后恢复地图 + private double mLastRealLon = 0.0; + private double mMockLat = 0.0; // 停止模拟时保存的模拟位置,用于判断回调是否仍为模拟数据 + private double mMockLon = 0.0; private ServiceGo.ServiceGoBinder mServiceBinder; private ServiceConnection mConnection; private FloatingActionButton mButtonStart; @@ -581,8 +586,31 @@ public void onReceiveLocation(BDLocation bdLocation) { } mCurrentCity = bdLocation.getCity(); + + // 正在恢复真实位置时,需要判断回调数据是否仍为模拟残留 + if (mRestoringPosition) { + double cbLat = bdLocation.getLatitude(); + double cbLon = bdLocation.getLongitude(); + // 计算回调位置与模拟位置的距离(粗略判断,1度纬度 ≈ 111km) + double dLat = cbLat - mMockLat; + double dLon = cbLon - mMockLon; + double dist = Math.sqrt(dLat * dLat + dLon * dLon) * 111000; // 米 + if (dist < 100) { + // 仍在模拟位置附近,说明是残留数据,跳过 + return; + } + // 已远离模拟位置,说明 GPS 已回到真实定位,解除恢复模式 + mRestoringPosition = false; + } + mCurrentLat = bdLocation.getLatitude(); mCurrentLon = bdLocation.getLongitude(); + // 非模拟状态下缓存真实位置,用于停止模拟后恢复地图 + if (!isMockServStart) { + mLastRealLat = mCurrentLat; + mLastRealLon = mCurrentLon; + } + MyLocationData locData = new MyLocationData.Builder() .accuracy(bdLocation.getRadius()) .direction(mCurrentDirection)// 此处设置开发者获取到的方向信息,顺时针0-360 @@ -755,15 +783,26 @@ private void resetMap() { mBaiduMap.clear(); mMarkLatLngMap = null; + // 优先使用缓存的真实位置,避免使用模拟期间被污染的 mCurrentLat/Lon + double lat = (mLastRealLat != 0.0) ? mLastRealLat : mCurrentLat; + double lon = (mLastRealLon != 0.0) ? mLastRealLon : mCurrentLon; + + // 保存当前模拟位置,用于后续回调判断数据是否仍为模拟残留 + mMockLat = mCurrentLat; + mMockLon = mCurrentLon; + + // 进入恢复模式,阻止后续回调中的 setMyLocationData 覆盖指示图标 + mRestoringPosition = true; + MyLocationData locData = new MyLocationData.Builder() - .latitude(mCurrentLat) - .longitude(mCurrentLon) + .latitude(lat) + .longitude(lon) .direction(mCurrentDirection) .build(); mBaiduMap.setMyLocationData(locData); MapStatus.Builder builder = new MapStatus.Builder(); - builder.target(new LatLng(mCurrentLat, mCurrentLon)).zoom(18.0f); + builder.target(new LatLng(lat, lon)).zoom(18.0f); mBaiduMap.animateMapStatus(MapStatusUpdateFactory.newMapStatus(builder.build())); } @@ -806,6 +845,9 @@ private void initGoBtn() { } private void startGoLocation() { + // 新一轮模拟开始,清除上次停止模拟时的恢复标志,否则回调会跳过新的模拟位置数据 + mRestoringPosition = false; + Intent serviceGoIntent = new Intent(MainActivity.this, ServiceGo.class); bindService(serviceGoIntent, mConnection, BIND_AUTO_CREATE); // 绑定服务和活动,之后活动就可以去调服务的方法了 double[] latLng = MapUtils.bd2wgs(mMarkLatLngMap.longitude, mMarkLatLngMap.latitude); @@ -847,6 +889,10 @@ private void doGoLocation(View v) { if (isMockServStart) { if (mMarkLatLngMap == null) { stopGoLocation(); + resetMap(); + if (mLocClient != null) { + mLocClient.requestLocation(); // 触发 SDK 获取真实位置,回调中会更新 mLastRealLat/Lon + } Snackbar.make(v, "模拟位置已终止", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); mButtonStart.setImageResource(R.drawable.ic_position);