├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── key ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── huolala │ │ └── mockgps │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── guide_map.json │ │ ├── marker_end.png │ │ ├── marker_start.png │ │ ├── marker_way.png │ │ ├── navi_lbs_texture.png │ │ └── navi_lbs_texture_unselected.png │ ├── java │ │ └── com │ │ │ └── huolala │ │ │ └── mockgps │ │ │ ├── MockReceiver.kt │ │ │ ├── MyApp.kt │ │ │ ├── adaper │ │ │ ├── ExpandAdapter.kt │ │ │ ├── MainAdapter.kt │ │ │ ├── MultiplePoiAdapter.kt │ │ │ ├── PoiListAdapter.kt │ │ │ ├── SettingAdapter.kt │ │ │ └── SimpleDividerDecoration.kt │ │ │ ├── http │ │ │ └── Api.kt │ │ │ ├── listener │ │ │ └── FloatingTouchListener.kt │ │ │ ├── manager │ │ │ ├── FloatingViewManger.kt │ │ │ ├── MapLocationManager.kt │ │ │ ├── SearchManager.kt │ │ │ └── utils │ │ │ │ ├── MapConvertUtils.kt │ │ │ │ └── MapDrawUtils.kt │ │ │ ├── model │ │ │ ├── AppUpdateModel.kt │ │ │ ├── ExpandModel.kt │ │ │ ├── MockMessageModel.kt │ │ │ ├── PoiInfoModel.kt │ │ │ └── SettingModel.kt │ │ │ ├── server │ │ │ └── GpsService.kt │ │ │ ├── ui │ │ │ ├── CalculateRouteActivity.kt │ │ │ ├── ExpandActivity.kt │ │ │ ├── FileMockActivity.kt │ │ │ ├── GuideActivity.kt │ │ │ ├── MainActivity.kt │ │ │ ├── MockLocationActivity.kt │ │ │ ├── PickMapPoiActivity.kt │ │ │ └── SettingActivity.kt │ │ │ ├── utils │ │ │ ├── CalculationLogLatDistance.java │ │ │ ├── HandlerUtils.kt │ │ │ ├── LocationUtils.java │ │ │ ├── MMKVUtils.kt │ │ │ ├── MapSensorManager.kt │ │ │ ├── ReflectionUtil.kt │ │ │ ├── RouteBindingUtils.java │ │ │ ├── Utils.kt │ │ │ └── WarnDialogUtils.kt │ │ │ ├── viewmodel │ │ │ └── HomeViewModel.kt │ │ │ └── widget │ │ │ ├── FloatingCardView.kt │ │ │ ├── GuideView.kt │ │ │ ├── HintDialog.kt │ │ │ ├── InputLatLngDialog.kt │ │ │ ├── InputLocationVibrationDialog.kt │ │ │ ├── InputSpeed.kt │ │ │ ├── LongClickImageButton.kt │ │ │ ├── MapSelectDialog.kt │ │ │ ├── NaviPathDialog.kt │ │ │ ├── NaviPopupWindow.kt │ │ │ ├── PointTypeDialog.kt │ │ │ ├── RegulateView.kt │ │ │ ├── RockerView.kt │ │ │ └── VerticalRecyclerView.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xxhdpi │ │ ├── ic_add.png │ │ ├── ic_adjust_order.png │ │ ├── ic_app_update.png │ │ ├── ic_back.png │ │ ├── ic_change.png │ │ ├── ic_cur_location.png │ │ ├── ic_floating_adjust_close.png │ │ ├── ic_floating_setting.png │ │ ├── ic_guide_arrows.png │ │ ├── ic_guide_button.png │ │ ├── ic_location.png │ │ ├── ic_location_adjust.png │ │ ├── ic_navi_remove.png │ │ ├── ic_navi_setting.png │ │ ├── ic_pause.png │ │ ├── ic_play.png │ │ ├── ic_refresh.png │ │ ├── ic_search.png │ │ ├── ic_select_arrow.png │ │ ├── ic_setting.png │ │ ├── ic_speed_setting_left.png │ │ ├── ic_speed_setting_right.png │ │ └── img.png │ │ ├── drawable │ │ ├── floating_seek_bar_bg.xml │ │ ├── ic_launcher_background.xml │ │ ├── seek_bar_bg.xml │ │ ├── selector_floating_play.xml │ │ ├── shape_circle_color_master.xml │ │ ├── shape_round_10_color_black_30_left.xml │ │ ├── shape_round_10_color_black_30_right.xml │ │ ├── shape_round_10_color_black_30_right_circle_left.xml │ │ ├── shape_round_10_color_red_stroke.xml │ │ ├── shape_round_10_color_white_stroke.xml │ │ ├── shape_round_15_color_grey_50.xml │ │ ├── shape_round_15_color_white.xml │ │ ├── shape_round_15_color_white_top.xml │ │ ├── shape_round_30_color_master.xml │ │ ├── shape_round_5_color_black_30.xml │ │ ├── shape_round_5_color_black_50_stroke.xml │ │ ├── shape_round_5_color_master_50_stroke.xml │ │ └── shape_round_5_color_white.xml │ │ ├── layout │ │ ├── activity_calculate_route.xml │ │ ├── activity_expand.xml │ │ ├── activity_file.xml │ │ ├── activity_guide.xml │ │ ├── activity_main.xml │ │ ├── activity_navi.xml │ │ ├── activity_pick.xml │ │ ├── activity_pick_location.xml │ │ ├── activity_setting.xml │ │ ├── dialog_file_mock_hint.xml │ │ ├── dialog_hint.xml │ │ ├── dialog_input_latlng.xml │ │ ├── dialog_input_location_vibration.xml │ │ ├── dialog_input_speed.xml │ │ ├── dialog_navi_path.xml │ │ ├── dialog_point_type.xml │ │ ├── dialog_select_navi_map.xml │ │ ├── item_history.xml │ │ ├── item_navi_poi_card.xml │ │ ├── item_poiinfo.xml │ │ ├── item_setting.xml │ │ ├── item_title.xml │ │ ├── layout_floating.xml │ │ ├── layout_floating_location_adjust.xml │ │ ├── layout_floating_navi_adjust.xml │ │ ├── layout_location_always_allow.xml │ │ ├── layout_location_card.xml │ │ ├── layout_main_card_header.xml │ │ ├── layout_main_guide.xml │ │ ├── layout_navi_card.xml │ │ ├── layout_regulate_value.xml │ │ └── popupwindow_navi_setting.xml │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.jpeg │ │ ├── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ │ └── xml │ │ └── backup_descriptor.xml │ └── test │ └── java │ └── com │ └── huolala │ └── mockgps │ └── ExampleUnitTest.kt ├── build.gradle ├── common ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── castiel │ │ └── common │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── astronaout_header.json │ │ ├── butterfly_footer.json │ │ ├── heart.json │ │ └── loading.json │ ├── java │ │ └── com │ │ │ └── castiel │ │ │ └── common │ │ │ ├── AppManager.kt │ │ │ ├── BaseApp.kt │ │ │ ├── base │ │ │ ├── BaseActivity.kt │ │ │ ├── BaseFragment.kt │ │ │ ├── BaseListAdapter.kt │ │ │ ├── BaseResponse.kt │ │ │ ├── BaseViewModel.kt │ │ │ └── IFragmentProvider.kt │ │ │ ├── dialog │ │ │ ├── BaseDialog.kt │ │ │ └── LoadingDialog.kt │ │ │ ├── http │ │ │ └── RetrofitClient.kt │ │ │ ├── recycler │ │ │ └── decoration │ │ │ │ ├── HorizontalItemDecoration.kt │ │ │ │ ├── RecyclerItemDecoration.kt │ │ │ │ └── VerticalItemDecoration.kt │ │ │ ├── utils │ │ │ ├── DataBindingAdapter.kt │ │ │ ├── GlideUtils.kt │ │ │ ├── MMKVWrap.kt │ │ │ └── StatusBarUtil.java │ │ │ └── widget │ │ │ ├── BoldTextView.kt │ │ │ ├── MixedTextView.kt │ │ │ ├── MsbRefreshFooter.kt │ │ │ ├── MsbRefreshHeader.kt │ │ │ ├── MultiStateView.kt │ │ │ └── StrokeTextView.java │ └── res │ │ ├── drawable-xxxhdpi │ │ └── common_ic_appbar_back.png │ │ ├── drawable │ │ ├── button_select.xml │ │ ├── ic_launcher_background.xml │ │ ├── shape_item_line_horizontall.xml │ │ ├── shape_item_line_verticall.xml │ │ ├── shape_round_1_red_50_line.xml │ │ ├── shape_round_20_color_accent.xml │ │ ├── shape_round_20_color_accent_50.xml │ │ ├── shape_round_20_white_50.xml │ │ ├── shape_round_20_white_80.xml │ │ ├── shape_round_8_color_black_50.xml │ │ └── text_color_select.xml │ │ ├── layout │ │ ├── dialog_loading.xml │ │ ├── empty_view.xml │ │ ├── error_view.xml │ │ ├── layout_appbar.xml │ │ ├── layout_footer_view.xml │ │ └── layout_header_view.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── ids.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── castiel │ └── common │ └── ExampleUnitTest.kt ├── config.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── download.jpeg ├── floating_window.jpeg ├── floating_window_location.png ├── floating_window_navi.png ├── home_location.jpeg ├── home_navi.jpeg ├── home_navi_multiple.png ├── location_control_panel.jpeg ├── navi_control_panel.jpeg ├── v2.0.jpeg └── wx.jpeg ├── pack.sh └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | /local.properties 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | local.properties 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MockGps # 2 | 3 | # Apk DownLoad 4 | 5 |  6 | 7 | 8 | 9 | # 功能v2.0 10 | 11 |  12 | 13 | **功能截图** 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | # 注意 35 | 36 | 腾讯类的应用无法模拟定位,腾讯定位默认有防模拟定位功能。该软件实现是基于系统API实现,定位信息里会标识为模拟数据标识,如果第三方软件想判断是很简单的,软件仅供日常开发使用。 37 | 38 | 39 | 40 | # 更新 41 | 42 | * 2022.2.18 适配部分手机出现IllegalArgumentException异常,手里机型有限。 43 | * 2022.9.5 v1.1 优化选址模块、新增广播启动模拟导航功能、支持文件数据模拟&文件生成模块(多路线选择指定模块) 44 | * 2023.11.23 v2.0 增加微调功能,以及优化功能。 45 | * 2024.1.24 v2.0 测试完成 46 | 47 | 48 | # 支持 49 | 近期看到下载量暴增,这款软件开发初期只是为我的工作以及团队提供一个调试的小软件,也是在工作中慢慢迭代。没想到有这么多朋友关注,对喜欢这款软件的朋友表示感谢,如果这款软件对你的学习或者工作有所帮助,不妨支持一下,您的支持就是我最大的动力。感谢大家! 50 | 51 | 52 | 53 | # 免责声明 54 | 此应用仅限开发学习和开发使用,软件的发布和使用均不收取任何费用。拒绝任何人或任何实体进行出售、重新修改后分发,严禁用于商业谋利用途。项目维护者对软件的滥用不承担任何责任。 55 | 56 | 57 | 58 | # 功能预警 59 | 由于百度sdk需要收取api流量费用(政策修改,个人或者企业都不会提供免费额度),后续百度sdk相关功能会无法使用。 60 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-android-extensions' 5 | id 'kotlin-kapt' 6 | } 7 | 8 | android { 9 | compileSdkVersion rootProject.ext.android.compileSdkVersion 10 | 11 | dataBinding { 12 | enabled true 13 | } 14 | 15 | defaultConfig { 16 | applicationId rootProject.ext.android.applicationId 17 | minSdkVersion rootProject.ext.android.minSdkVersion 18 | targetSdkVersion rootProject.ext.android.targetSdkVersion 19 | versionCode rootProject.ext.android.versionCode 20 | versionName rootProject.ext.android.versionName 21 | 22 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 23 | 24 | ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "x86" } 25 | } 26 | 27 | signingConfigs {// 自动化打包配置 28 | release {// 线上环境 29 | keyAlias 'huolala' 30 | keyPassword 'huolala123' 31 | storeFile file('key') 32 | storePassword 'huolala123' 33 | } 34 | } 35 | 36 | buildTypes { 37 | debug { 38 | signingConfig signingConfigs.release 39 | minifyEnabled false 40 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 41 | manifestPlaceholders = [MAP_KEY: rootProject.ext.android.map_key] 42 | } 43 | release { 44 | signingConfig signingConfigs.release 45 | minifyEnabled false 46 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 47 | manifestPlaceholders = [MAP_KEY: rootProject.ext.android.map_key] 48 | } 49 | } 50 | compileOptions { 51 | sourceCompatibility JavaVersion.VERSION_11 52 | targetCompatibility JavaVersion.VERSION_11 53 | } 54 | kotlinOptions { 55 | jvmTarget = '1.8' 56 | } 57 | sourceSets { 58 | main { 59 | jniLibs.srcDirs = ['libs'] 60 | } 61 | } 62 | } 63 | 64 | dependencies { 65 | implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs') 66 | implementation project(':common') 67 | implementation 'com.baidu.lbsyun:BaiduMapSDK_Location:9.6.0' 68 | implementation 'com.baidu.lbsyun:BaiduMapSDK_Map:7.6.1' 69 | implementation 'com.baidu.lbsyun:BaiduMapSDK_Search:7.6.1' 70 | implementation 'com.github.tiann:FreeReflection:3.1.0' 71 | } -------------------------------------------------------------------------------- /app/key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/key -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/huolala/mockgps/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.huolala.mockgps", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/assets/marker_end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/assets/marker_end.png -------------------------------------------------------------------------------- /app/src/main/assets/marker_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/assets/marker_start.png -------------------------------------------------------------------------------- /app/src/main/assets/marker_way.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/assets/marker_way.png -------------------------------------------------------------------------------- /app/src/main/assets/navi_lbs_texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/assets/navi_lbs_texture.png -------------------------------------------------------------------------------- /app/src/main/assets/navi_lbs_texture_unselected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/assets/navi_lbs_texture_unselected.png -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/MyApp.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps 2 | 3 | import android.animation.ValueAnimator 4 | import android.annotation.SuppressLint 5 | import android.content.Context 6 | import android.content.IntentFilter 7 | import android.os.Build 8 | import com.baidu.location.LocationClient 9 | import com.baidu.mapapi.CoordType 10 | import com.baidu.mapapi.SDKInitializer 11 | import com.baidu.mapapi.map.OverlayUtil 12 | import com.blankj.utilcode.util.ClickUtils 13 | import com.blankj.utilcode.util.Utils 14 | import com.castiel.common.BaseApp 15 | import me.weishu.reflection.Reflection 16 | import java.lang.reflect.Field 17 | 18 | 19 | /** 20 | * @author jiayu.liu 21 | */ 22 | class MyApp : BaseApp() { 23 | private lateinit var mMockReceiver: MockReceiver 24 | override fun attachBaseContext(base: Context?) { 25 | super.attachBaseContext(base) 26 | Reflection.unseal(base) 27 | reflectionValueAnimator() 28 | } 29 | 30 | @SuppressLint("SoonBlockedPrivateApi") 31 | private fun reflectionValueAnimator() { 32 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 33 | try { 34 | if (!ValueAnimator.areAnimatorsEnabled()) { 35 | val field: Field = ValueAnimator::class.java.getDeclaredField("sDurationScale") 36 | field.isAccessible = true 37 | field.set(null, 1) 38 | } 39 | } catch (e: Exception) { 40 | e.printStackTrace() 41 | } 42 | } 43 | } 44 | 45 | 46 | override fun onCreate() { 47 | super.onCreate() 48 | 49 | Utils.init(this) 50 | SDKInitializer.setAgreePrivacy(this, true) 51 | LocationClient.setAgreePrivacy(true); 52 | try { 53 | SDKInitializer.initialize(this) 54 | OverlayUtil.setOverlayUpgrade(false) 55 | } catch (e: Exception) { 56 | e.printStackTrace() 57 | } 58 | SDKInitializer.setCoordType(CoordType.GCJ02); 59 | initReceiver() 60 | } 61 | 62 | 63 | private fun initReceiver() { 64 | mMockReceiver = MockReceiver() 65 | val intentFilter = IntentFilter() 66 | intentFilter.addAction("com.huolala.mockgps.navi") 67 | registerReceiver(mMockReceiver, intentFilter) 68 | // mMockReceiver is missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag for unprotected broadcasts registered for com.huolala.mockgps.navi 69 | } 70 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/adaper/ExpandAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.adaper 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.DiffUtil 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.castiel.common.base.BaseListAdapter 9 | import com.huolala.mockgps.R 10 | import com.huolala.mockgps.databinding.ItemTitleBinding 11 | import com.huolala.mockgps.model.ExpandModel 12 | 13 | /** 14 | * @author jiayu.liu 15 | */ 16 | class ExpandAdapter : 17 | BaseListAdapter(object : 18 | DiffUtil.ItemCallback() { 19 | override fun areItemsTheSame(oldItem: ExpandModel, newItem: ExpandModel): Boolean { 20 | return oldItem == newItem 21 | } 22 | 23 | override fun areContentsTheSame(oldItem: ExpandModel, newItem: ExpandModel): Boolean { 24 | return oldItem.msg == newItem.msg 25 | } 26 | 27 | }) { 28 | 29 | class ViewHolder(val binding: ItemTitleBinding) : RecyclerView.ViewHolder(binding.root) 30 | 31 | 32 | override fun onBindViewHolderModel(holder: ViewHolder, position: Int) { 33 | getItem(position)?.run { 34 | holder.binding.title = title 35 | holder.binding.msg = msg 36 | } 37 | 38 | } 39 | 40 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 41 | val binding: ItemTitleBinding? = DataBindingUtil.bind( 42 | LayoutInflater.from(parent.context).inflate(R.layout.item_title, parent, false) 43 | ) 44 | return ViewHolder(binding!!) 45 | } 46 | 47 | 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/adaper/MultiplePoiAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.adaper 2 | 3 | import android.annotation.SuppressLint 4 | import android.view.LayoutInflater 5 | import android.view.MotionEvent 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.databinding.DataBindingUtil 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.huolala.mockgps.R 11 | import com.huolala.mockgps.databinding.ItemNaviPoiCardBinding 12 | import com.huolala.mockgps.model.PoiInfoModel 13 | 14 | /** 15 | * @author jiayu.liu 16 | */ 17 | class MultiplePoiAdapter : RecyclerView.Adapter() { 18 | var list: MutableList = arrayListOf() 19 | var clickListener: OnItemClickListener? = null 20 | 21 | override fun onCreateViewHolder(parent: ViewGroup, position: Int): ViewHolder { 22 | val view = 23 | LayoutInflater.from(parent.context).inflate(R.layout.item_navi_poi_card, parent, false) 24 | return ViewHolder(DataBindingUtil.bind(view)!!) 25 | } 26 | 27 | @SuppressLint("ClickableViewAccessibility") 28 | override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { 29 | val item = list[position] 30 | val str = when (position) { 31 | 0 -> { 32 | viewHolder.binding.isWay = false 33 | "起点" 34 | } 35 | 36 | list.size - 1 -> { 37 | viewHolder.binding.isWay = false 38 | "终点" 39 | } 40 | 41 | else -> { 42 | viewHolder.binding.isWay = true 43 | "途经" 44 | } 45 | } 46 | 47 | viewHolder.binding.ivRemove.setOnClickListener { 48 | if (viewHolder.binding.getIsWay() == true) { 49 | list.removeAt(position) 50 | notifyDataSetChanged() 51 | } else { 52 | clickListener?.onItemClick(viewHolder.binding.root, position) 53 | } 54 | } 55 | 56 | viewHolder.binding.poiName = "$str:${item.name}" 57 | 58 | 59 | clickListener?.let { listener -> 60 | viewHolder.binding.root.setOnClickListener { 61 | listener.onItemClick(viewHolder.binding.root, position) 62 | } 63 | viewHolder.binding.ivMove.setOnTouchListener { _, event -> 64 | if (event?.action == MotionEvent.ACTION_DOWN) { 65 | listener.onItemMove() 66 | true 67 | } else { 68 | false 69 | } 70 | } 71 | 72 | } 73 | } 74 | 75 | fun submitList(list: List) { 76 | this.list.clear() 77 | this.list.addAll(list) 78 | notifyDataSetChanged() 79 | } 80 | 81 | fun currentList(): MutableList { 82 | return list 83 | } 84 | 85 | override fun getItemCount(): Int { 86 | return list.size 87 | } 88 | 89 | class ViewHolder(val binding: ItemNaviPoiCardBinding) : RecyclerView.ViewHolder(binding.root) 90 | 91 | 92 | interface OnItemClickListener { 93 | fun onItemClick(view: View, position: Int) 94 | 95 | fun onItemMove() 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/adaper/PoiListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.adaper 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.DiffUtil 7 | import androidx.recyclerview.widget.ListAdapter 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.baidu.mapapi.search.sug.SuggestionResult 10 | import com.huolala.mockgps.R 11 | import kotlinx.android.synthetic.main.item_poiinfo.view.* 12 | 13 | /** 14 | * @author jiayu.liu 15 | */ 16 | class PoiListAdapter : 17 | ListAdapter(object : 18 | DiffUtil.ItemCallback() { 19 | override fun areItemsTheSame( 20 | oldItem: SuggestionResult.SuggestionInfo, 21 | newItem: SuggestionResult.SuggestionInfo 22 | ): Boolean { 23 | return oldItem == newItem 24 | } 25 | 26 | override fun areContentsTheSame( 27 | oldItem: SuggestionResult.SuggestionInfo, 28 | newItem: SuggestionResult.SuggestionInfo 29 | ): Boolean { 30 | return oldItem.uid == newItem.uid 31 | } 32 | }) { 33 | private var mOnItemClickListener: OnItemClickListener? = null 34 | 35 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 36 | val view = 37 | LayoutInflater.from(parent.context).inflate(R.layout.item_poiinfo, parent, false) 38 | return ViewHolder(view) 39 | } 40 | 41 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 42 | val poiInfo = getItem(position) 43 | holder.itemView.tv_item_poi_name.text = 44 | String.format("city: ${poiInfo.city} name: ${poiInfo.key}") 45 | holder.itemView.tv_item_poi_address.text = poiInfo.address 46 | if (mOnItemClickListener != null) { 47 | holder.itemView.setOnClickListener { 48 | mOnItemClickListener!!.onItemClick(poiInfo) 49 | } 50 | } 51 | } 52 | 53 | fun setData(list: MutableList?) { 54 | submitList(list) 55 | } 56 | 57 | class ViewHolder(view: View) : RecyclerView.ViewHolder(view) 58 | 59 | fun setOnItemClickListener(mOnItemClickListener: OnItemClickListener?) { 60 | this.mOnItemClickListener = mOnItemClickListener 61 | } 62 | 63 | interface OnItemClickListener { 64 | fun onItemClick(poiInfo: SuggestionResult.SuggestionInfo) 65 | } 66 | 67 | 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/adaper/SettingAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.adaper 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.databinding.DataBindingUtil 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.huolala.mockgps.R 9 | import com.huolala.mockgps.databinding.ItemSettingBinding 10 | import com.huolala.mockgps.model.SettingMsgModel 11 | 12 | /** 13 | * @author jiayu.liu 14 | */ 15 | class SettingAdapter : RecyclerView.Adapter() { 16 | private val list: MutableList = mutableListOf() 17 | var listener: OnItemListener? = null 18 | 19 | override fun onCreateViewHolder(viewGroup: ViewGroup, position: Int): ViewHolder { 20 | val dataBinding = DataBindingUtil.inflate( 21 | LayoutInflater.from(viewGroup.context), 22 | R.layout.item_setting, 23 | viewGroup, 24 | false 25 | ) 26 | return ViewHolder(dataBinding!!) 27 | } 28 | 29 | override fun getItemCount(): Int { 30 | return this.list.size 31 | } 32 | 33 | override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { 34 | val model = list[position] 35 | viewHolder.dataBinding.isSwitch = model.isSwitch 36 | viewHolder.dataBinding.title = model.title 37 | viewHolder.dataBinding.msg = model.msg 38 | viewHolder.dataBinding.listener = View.OnClickListener { listener?.onSettingClick(model) } 39 | 40 | viewHolder.dataBinding.swSwitch.setOnCheckedChangeListener { _, isChecked -> 41 | model.isSwitch = isChecked 42 | listener?.onItemSwitch( 43 | viewHolder.dataBinding, 44 | model, 45 | isChecked 46 | ) 47 | } 48 | } 49 | 50 | fun submitList(list: List) { 51 | this.list.clear() 52 | this.list.addAll(list) 53 | notifyDataSetChanged() 54 | } 55 | 56 | class ViewHolder(val dataBinding: ItemSettingBinding) : 57 | RecyclerView.ViewHolder(dataBinding.root) 58 | 59 | interface OnItemListener { 60 | fun onItemSwitch( 61 | dataBinding: ItemSettingBinding?, 62 | model: SettingMsgModel, 63 | isChecked: Boolean 64 | ) 65 | 66 | fun onSettingClick(model: SettingMsgModel) 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/adaper/SimpleDividerDecoration.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.adaper 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | 5 | import android.content.Context 6 | import android.graphics.Canvas 7 | import android.graphics.Paint 8 | import android.graphics.Rect 9 | import android.view.View 10 | import androidx.recyclerview.widget.RecyclerView.ItemDecoration 11 | import com.blankj.utilcode.util.ConvertUtils 12 | import com.huolala.mockgps.R 13 | 14 | 15 | /** 16 | * @author jiayu.liu 17 | */ 18 | class SimpleDividerDecoration( 19 | context: Context, 20 | color: Int = R.color.grey, 21 | height: Float = 1f 22 | ) : 23 | ItemDecoration() { 24 | private val dividerHeight: Int 25 | private val dividerPaint: Paint = Paint() 26 | 27 | override fun getItemOffsets( 28 | outRect: Rect, 29 | view: View, 30 | parent: RecyclerView, 31 | state: RecyclerView.State 32 | ) { 33 | super.getItemOffsets(outRect, view, parent, state) 34 | outRect.bottom = dividerHeight 35 | } 36 | 37 | override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { 38 | super.onDraw(c, parent, state) 39 | val childCount = parent.childCount 40 | val left = parent.paddingLeft 41 | val right = parent.width - parent.paddingRight 42 | for (i in 0 until childCount - 1) { 43 | val view: View = parent.getChildAt(i) 44 | val top: Int = view.bottom 45 | val bottom: Int = view.bottom + dividerHeight 46 | c.drawRect( 47 | left.toFloat() + dividerHeight * 10, 48 | top.toFloat(), 49 | right.toFloat(), 50 | bottom.toFloat(), 51 | dividerPaint 52 | ) 53 | } 54 | } 55 | 56 | init { 57 | dividerPaint.color = context.resources.getColor(color) 58 | dividerHeight = ConvertUtils.dp2px(height) 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/http/Api.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.http 2 | 3 | import com.castiel.common.base.BaseResponse 4 | import com.huolala.mockgps.model.AppUpdateModel 5 | import okhttp3.RequestBody 6 | import retrofit2.http.FieldMap 7 | import retrofit2.http.FormUrlEncoded 8 | import retrofit2.http.Headers 9 | import retrofit2.http.POST 10 | import retrofit2.http.PartMap 11 | 12 | /** 13 | * @author jiayu.liu 14 | */ 15 | interface Api { 16 | /** 17 | * 检测app版本 18 | */ 19 | @POST("/apiv2/app/check") 20 | @FormUrlEncoded 21 | suspend fun checkAppUpdate(@FieldMap args: Map): BaseResponse 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/listener/FloatingTouchListener.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.listener 2 | 3 | import android.animation.ValueAnimator 4 | import android.view.MotionEvent 5 | import android.view.View 6 | import android.view.WindowManager 7 | import android.view.animation.LinearInterpolator 8 | import com.blankj.utilcode.util.ScreenUtils 9 | import kotlin.math.min 10 | 11 | 12 | /** 13 | * 悬浮窗touch监听处理 14 | * @author jiayu.liu 15 | */ 16 | class FloatingTouchListener( 17 | private var windowManager: WindowManager, 18 | private var layoutParams: WindowManager.LayoutParams, 19 | private var isAutoMove: Boolean = false, 20 | private var touchView: View? = null //悬浮窗根布局 解决touch的View与悬浮窗根布局不一致的情况 21 | ) : View.OnTouchListener { 22 | private val mScreenWidth = ScreenUtils.getScreenWidth() 23 | private val mScreenHeight = ScreenUtils.getScreenHeight() 24 | var callBack: FloatingTouchCallBack? = null 25 | 26 | private var initialX = 0f 27 | private var initialY = 0f 28 | private var dX = 0f 29 | private var dY = 0f 30 | 31 | 32 | override fun onTouch(view: View?, event: MotionEvent?): Boolean { 33 | if (touchView == null) { 34 | return false 35 | } 36 | return when (event!!.action) { 37 | MotionEvent.ACTION_DOWN -> { 38 | initialX = layoutParams.x.toFloat() 39 | initialY = layoutParams.y.toFloat() 40 | dX = event.rawX - initialX 41 | dY = event.rawY - initialY 42 | callBack?.onActionDown() 43 | true 44 | } 45 | 46 | MotionEvent.ACTION_MOVE -> { 47 | val x: Int = (event.rawX - dX).toInt() 48 | val y: Int = (event.rawY - dY).toInt() 49 | layoutParams.x = if (x <= 0) 0 else min( 50 | x, 51 | (mScreenWidth - (touchView?.width ?: 0)) 52 | ) 53 | layoutParams.y = if (y <= 0) 0 else min( 54 | y, 55 | (mScreenHeight - (touchView?.height ?: 0)) 56 | ) 57 | 58 | windowManager.updateViewLayout(touchView, layoutParams) 59 | true 60 | } 61 | 62 | MotionEvent.ACTION_UP -> { 63 | if (!isAutoMove) { 64 | return false 65 | } 66 | // 在手指抬起时,将View移动到屏幕边缘 67 | val isLeft = layoutParams.x + touchView!!.width / 2 <= mScreenWidth / 2 68 | layoutParams.x = if (isLeft) 0 else mScreenWidth - touchView!!.width 69 | windowManager.updateViewLayout(touchView, layoutParams) 70 | callBack?.onActionUp(isLeft) 71 | true 72 | } 73 | 74 | else -> false 75 | } 76 | } 77 | 78 | interface FloatingTouchCallBack { 79 | /** 80 | * 抬起 81 | */ 82 | fun onActionUp(isLeft: Boolean) 83 | 84 | /** 85 | * 按下 86 | */ 87 | fun onActionDown() 88 | } 89 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/manager/utils/MapConvertUtils.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.manager.utils 2 | 3 | import com.baidu.mapapi.model.LatLng 4 | import com.baidu.mapapi.search.route.DrivingRouteLine 5 | 6 | /** 7 | * @author jiayu.liu 8 | */ 9 | object MapConvertUtils { 10 | /** 11 | * 导航数据转换点串 12 | */ 13 | fun convertLatLngList(routeLine: DrivingRouteLine?): List { 14 | val polylineList = arrayListOf() 15 | routeLine?.let { 16 | for (step in it.allStep) { 17 | if (step.wayPoints != null && step.wayPoints.isNotEmpty()) { 18 | polylineList.addAll(step.wayPoints) 19 | } 20 | } 21 | } 22 | return polylineList 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/manager/utils/MapDrawUtils.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.manager.utils 2 | 3 | import android.R 4 | import android.graphics.Rect 5 | import android.text.TextUtils 6 | import com.baidu.mapapi.map.BaiduMap 7 | import com.baidu.mapapi.map.BitmapDescriptorFactory 8 | import com.baidu.mapapi.map.MapStatusUpdateFactory 9 | import com.baidu.mapapi.map.MarkerOptions 10 | import com.baidu.mapapi.map.Overlay 11 | import com.baidu.mapapi.map.OverlayOptions 12 | import com.baidu.mapapi.map.PolylineOptions 13 | import com.baidu.mapapi.model.LatLng 14 | import com.baidu.mapapi.model.LatLngBounds 15 | 16 | 17 | /** 18 | * @author jiayu.liu 19 | */ 20 | object MapDrawUtils { 21 | 22 | fun drawMarkerToMap(baiduMap: BaiduMap, point: LatLng, assetName: String) { 23 | if (TextUtils.isEmpty(assetName)) { 24 | return 25 | } 26 | val bitmap = BitmapDescriptorFactory 27 | .fromAsset(assetName) 28 | val option: OverlayOptions = MarkerOptions() 29 | .position(point) 30 | .icon(bitmap) 31 | .zIndex(2) 32 | baiduMap.addOverlay(option) 33 | 34 | } 35 | 36 | fun drawLineToMap( 37 | baiduMap: BaiduMap, 38 | polylineList: List, 39 | rect: Rect, 40 | isMainLine: Boolean = true, 41 | animateMapStatus: Boolean = true 42 | ): Overlay? { 43 | if (polylineList.isEmpty()) { 44 | return null 45 | } 46 | 47 | val mOverlayOptions: OverlayOptions = PolylineOptions() 48 | .width(30) 49 | .keepScale(true) 50 | .customTexture( 51 | BitmapDescriptorFactory.fromAsset( 52 | if (isMainLine) 53 | "navi_lbs_texture.png" 54 | else 55 | "navi_lbs_texture_unselected.png" 56 | ) 57 | ) 58 | .lineJoinType(PolylineOptions.LineJoinType.LineJoinRound) 59 | .lineCapType(PolylineOptions.LineCapType.LineCapRound) 60 | .points(polylineList) 61 | .zIndex(if (isMainLine) 1 else 0) 62 | val addOverlay = baiduMap.addOverlay(mOverlayOptions) 63 | if (isMainLine && animateMapStatus) { 64 | baiduMap.animateMapStatus( 65 | MapStatusUpdateFactory.newLatLngBounds( 66 | LatLngBounds.Builder().include(polylineList).build(), 67 | rect.left, 68 | rect.top, 69 | rect.right, 70 | rect.bottom 71 | ) 72 | ) 73 | } 74 | return addOverlay 75 | } 76 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/model/AppUpdateModel.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.model 2 | 3 | /** 4 | * @author jiayu.liu 5 | */ 6 | data class AppUpdateModel( 7 | var buildVersionNo: String?, 8 | var downloadURL: String?, 9 | var buildUpdateDescription: String?, 10 | var appURl: String?, 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/model/ExpandModel.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.model 2 | 3 | /** 4 | * @author jiayu.liu 5 | */ 6 | data class ExpandModel( 7 | /** 8 | * 标题 9 | */ 10 | var title: String?, 11 | /** 12 | * 描述 13 | */ 14 | var msg: String?, 15 | ) 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/model/MockMessageModel.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.model 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import androidx.annotation.IntDef 6 | import com.huolala.mockgps.utils.LocationUtils 7 | 8 | 9 | @IntDef(NaviType.NONE, NaviType.LOCATION, NaviType.NAVI, NaviType.NAVI_FILE) 10 | @Retention(AnnotationRetention.SOURCE) 11 | annotation class NaviType { 12 | companion object { 13 | const val NONE = -1 14 | const val LOCATION = 0 15 | const val NAVI = 1 16 | const val NAVI_FILE = 2 17 | } 18 | } 19 | 20 | /** 21 | * @author jiayu.liu 22 | */ 23 | data class MockMessageModel( 24 | var locationModel: PoiInfoModel? = null, 25 | var startNavi: PoiInfoModel? = null, 26 | var wayNaviList: List? = null, 27 | var endNavi: PoiInfoModel? = null, 28 | /** 29 | * 0.模拟定位 1.模拟导航 2.文件数据导航 30 | */ 31 | @NaviType 32 | var naviType: Int = 0, 33 | /*** 34 | * 速度 单位 KM/H 35 | */ 36 | var speed: Int = 60, 37 | var uid: String? = "", 38 | var pointType: String? = LocationUtils.gcj02 39 | ) : Parcelable { 40 | constructor(parcel: Parcel) : this( 41 | parcel.readParcelable(PoiInfoModel::class.java.classLoader), 42 | parcel.readParcelable(PoiInfoModel::class.java.classLoader), 43 | parcel.createTypedArrayList(PoiInfoModel), 44 | parcel.readParcelable(PoiInfoModel::class.java.classLoader), 45 | parcel.readInt(), 46 | parcel.readInt(), 47 | parcel.readString(), 48 | parcel.readString() 49 | ) 50 | 51 | override fun writeToParcel(parcel: Parcel, flags: Int) { 52 | parcel.writeParcelable(locationModel, flags) 53 | parcel.writeParcelable(startNavi, flags) 54 | parcel.writeTypedList(wayNaviList) 55 | parcel.writeParcelable(endNavi, flags) 56 | parcel.writeInt(naviType) 57 | parcel.writeInt(speed) 58 | parcel.writeString(uid) 59 | parcel.writeString(pointType) 60 | } 61 | 62 | override fun describeContents(): Int { 63 | return 0 64 | } 65 | 66 | companion object CREATOR : Parcelable.Creator { 67 | override fun createFromParcel(parcel: Parcel): MockMessageModel { 68 | return MockMessageModel(parcel) 69 | } 70 | 71 | override fun newArray(size: Int): Array { 72 | return arrayOfNulls(size) 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/model/PoiInfoModel.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.model 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import androidx.annotation.IntDef 6 | import com.baidu.mapapi.model.LatLng 7 | 8 | 9 | @IntDef(PoiInfoType.DEFAULT, PoiInfoType.LOCATION, PoiInfoType.NAVI_START, PoiInfoType.NAVI_END) 10 | @Retention(AnnotationRetention.SOURCE) 11 | annotation class PoiInfoType { 12 | companion object { 13 | const val DEFAULT = -1 14 | const val LOCATION = 0 15 | const val NAVI_START = 1 16 | const val NAVI_END = 2 17 | } 18 | } 19 | 20 | /** 21 | * @author jiayu.liu 22 | */ 23 | data class PoiInfoModel( 24 | var latLng: LatLng? = null, 25 | var uid: String? = null, 26 | var name: String? = "", 27 | /** 28 | * -1.默认选址模式 0.模拟定位 1.模拟导航起点 2.模拟导航终点 29 | */ 30 | @PoiInfoType 31 | var poiInfoType: Int = PoiInfoType.LOCATION, 32 | var city: String? = "北京市" 33 | ) : Parcelable { 34 | constructor(parcel: Parcel) : this( 35 | parcel.readParcelable(LatLng::class.java.classLoader), 36 | parcel.readString(), parcel.readString(), parcel.readInt(), 37 | parcel.readString() 38 | ) 39 | 40 | override fun writeToParcel(parcel: Parcel, flags: Int) { 41 | parcel.writeParcelable(latLng, flags) 42 | parcel.writeString(uid) 43 | parcel.writeString(name) 44 | parcel.writeInt(poiInfoType) 45 | parcel.writeString(city) 46 | } 47 | 48 | override fun describeContents(): Int { 49 | return 0 50 | } 51 | 52 | companion object CREATOR : Parcelable.Creator { 53 | override fun createFromParcel(parcel: Parcel): PoiInfoModel { 54 | return PoiInfoModel(parcel) 55 | } 56 | 57 | override fun newArray(size: Int): Array { 58 | return arrayOfNulls(size) 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/model/SettingModel.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.model 2 | 3 | /** 4 | * 设置功能 开关状态 5 | * @author jiayu.liu 6 | */ 7 | data class SettingModel( 8 | var isLocationQuiver: Boolean = false, 9 | var isNaviRouteBinding: Boolean = true 10 | ) 11 | 12 | /** 13 | * 设置功能 描述信息 14 | */ 15 | data class SettingMsgModel( 16 | var title: String = "", 17 | var msg: String = "", 18 | var isSwitch: Boolean = false 19 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/utils/HandlerUtils.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.utils 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | 6 | class HandlerUtils private constructor() : Handler(Looper.getMainLooper()) { 7 | 8 | companion object { 9 | val INSTANCE: HandlerUtils by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { 10 | HandlerUtils() 11 | } 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/utils/MapSensorManager.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.utils 2 | 3 | import android.content.Context 4 | import android.hardware.Sensor 5 | import android.hardware.SensorEventListener 6 | import android.hardware.SensorManager 7 | 8 | 9 | /** 10 | * @author jiayu.liu 11 | */ 12 | class MapSensorManager(context: Context) { 13 | private var mSensorManager: SensorManager? = null 14 | private var listener: SensorEventListener? = null 15 | 16 | init { 17 | mSensorManager = 18 | context.applicationContext.getSystemService(Context.SENSOR_SERVICE) as SensorManager 19 | } 20 | 21 | fun start(listener: SensorEventListener) { 22 | this.listener = listener 23 | mSensorManager?.getDefaultSensor(Sensor.TYPE_ORIENTATION)?.let { 24 | mSensorManager?.registerListener(listener, it, SensorManager.SENSOR_DELAY_UI) 25 | } 26 | } 27 | 28 | fun stop() { 29 | //停止定位 30 | mSensorManager?.unregisterListener(listener) 31 | } 32 | 33 | 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/utils/ReflectionUtil.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.utils 2 | 3 | import kotlin.reflect.full.declaredFunctions 4 | import kotlin.reflect.full.declaredMemberProperties 5 | import kotlin.reflect.jvm.isAccessible 6 | import kotlin.reflect.jvm.javaField 7 | 8 | object ReflectionUtil { 9 | 10 | /** 11 | * 修改私有成员变量的值 12 | */ 13 | fun setPrivateField(instance: Any, propertyName: String, value: Any?) { 14 | try { 15 | val property = instance::class.declaredMemberProperties.find { it.name == propertyName } 16 | property?.let { 17 | it.isAccessible = true 18 | val javaField = it.javaField 19 | javaField?.isAccessible = true 20 | javaField?.set(instance, value) 21 | } 22 | } catch (e: Exception) { 23 | e.printStackTrace() 24 | } 25 | } 26 | 27 | /** 28 | * 获取私有成员变量的值 29 | */ 30 | fun getPrivateField(instance: Any, propertyName: String): Any? { 31 | return try { 32 | val property = instance::class.declaredMemberProperties.find { it.name == propertyName } 33 | property?.let { 34 | it.isAccessible = true 35 | val javaField = it.javaField 36 | javaField?.isAccessible = true 37 | javaField?.get(instance) 38 | } 39 | } catch (e: Exception) { 40 | e.printStackTrace() 41 | null 42 | } 43 | } 44 | 45 | /** 46 | * 调用私有方法 47 | */ 48 | fun callPrivateMethod(instance: Any, methodName: String, vararg args: Any?): Any? { 49 | return try { 50 | val method = instance::class.declaredFunctions.find { it.name == methodName } 51 | method?.let { 52 | it.isAccessible = true 53 | it.call(instance, *args) 54 | } 55 | } catch (e: Exception) { 56 | e.printStackTrace() 57 | null 58 | } 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/utils/RouteBindingUtils.java: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.utils; 2 | 3 | import com.baidu.mapapi.model.LatLng; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 绑路优化工具 9 | * @author jiayu.liu 10 | */ 11 | public class RouteBindingUtils { 12 | 13 | public static LatLng snapToPath(LatLng point, List path) { 14 | LatLng closestPoint = null; 15 | double minDistance = Double.MAX_VALUE; 16 | 17 | for (int i = 0; i < path.size() - 1; i++) { 18 | LatLng segmentStart = path.get(i); 19 | LatLng segmentEnd = path.get(i + 1); 20 | 21 | LatLng projectedPoint = projectPointOntoSegment(point, segmentStart, segmentEnd); 22 | 23 | double distance = haversine(point.latitude, point.longitude, 24 | projectedPoint.latitude, projectedPoint.longitude); 25 | 26 | if (distance < minDistance) { 27 | minDistance = distance; 28 | closestPoint = projectedPoint; 29 | } 30 | } 31 | 32 | return closestPoint; 33 | } 34 | 35 | // 将点投影到线段上,返回投影点 36 | private static LatLng projectPointOntoSegment(LatLng point, LatLng segmentStart, LatLng segmentEnd) { 37 | double lat1 = Math.toRadians(segmentStart.latitude); 38 | double lon1 = Math.toRadians(segmentStart.longitude); 39 | double lat2 = Math.toRadians(segmentEnd.latitude); 40 | double lon2 = Math.toRadians(segmentEnd.longitude); 41 | double lat3 = Math.toRadians(point.latitude); 42 | double lon3 = Math.toRadians(point.longitude); 43 | 44 | double a = lat3 - lat1; 45 | double b = lon3 - lon1; 46 | double c = lat2 - lat1; 47 | double d = lon2 - lon1; 48 | 49 | double dot = a * c + b * d; 50 | double lenSq = c * c + d * d; 51 | double param = dot / lenSq; 52 | 53 | if (param < 0) { 54 | return segmentStart; 55 | } else if (param > 1) { 56 | return segmentEnd; 57 | } 58 | 59 | double projectedLat = lat1 + param * c; 60 | double projectedLon = lon1 + param * d; 61 | 62 | return new LatLng(Math.toDegrees(projectedLat), Math.toDegrees(projectedLon)); 63 | } 64 | 65 | // Haversine公式计算两个经纬度点之间的距离 66 | private static double haversine(double lat1, double lon1, double lat2, double lon2) { 67 | double R = 6378137.0; // 地球半径(米) 68 | double dLat = lat2 - lat1; 69 | double dLon = lon2 - lon1; 70 | double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + 71 | Math.cos(lat1) * Math.cos(lat2) * 72 | Math.sin(dLon / 2) * Math.sin(dLon / 2); 73 | double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 74 | double distance = R * c; 75 | return distance; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/utils/WarnDialogUtils.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.utils 2 | 3 | import android.app.AlertDialog 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.net.Uri 7 | import android.os.Build 8 | import android.provider.Settings 9 | import java.lang.Exception 10 | 11 | /** 12 | * @author jiayu.liu 13 | */ 14 | object WarnDialogUtils { 15 | 16 | /** 17 | * 悬浮窗提示 18 | */ 19 | fun setFloatWindowDialog(context: Context?) { 20 | if (context == null) { 21 | return 22 | } 23 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { 24 | return 25 | } 26 | AlertDialog.Builder(context) 27 | .setTitle("警告") 28 | .setMessage("需要开启悬浮窗,否则容易导致App被系统回收") 29 | .setPositiveButton( 30 | "开启" 31 | ) { _, _ -> 32 | try { 33 | val intent = Intent( 34 | Settings.ACTION_MANAGE_OVERLAY_PERMISSION, 35 | Uri.parse("package:${context.packageName}") 36 | ) 37 | context.startActivity(intent) 38 | } catch (e: Exception) { 39 | e.printStackTrace() 40 | } 41 | }.setNegativeButton( 42 | "取消" 43 | ) { _, _ -> } 44 | .show() 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/viewmodel/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import com.blankj.utilcode.util.AppUtils 6 | import com.castiel.common.base.BaseViewModel 7 | import com.castiel.common.http.RetrofitClient 8 | import com.huolala.mockgps.http.Api 9 | import com.huolala.mockgps.model.AppUpdateModel 10 | 11 | /** 12 | * @author jiayu.liu 13 | */ 14 | class HomeViewModel : BaseViewModel() { 15 | private val _updateApp = MutableLiveData() 16 | val updateApp: LiveData = _updateApp 17 | 18 | /** 19 | * 检测是否需要升级 20 | */ 21 | fun checkAppUpdate() { 22 | lauch( 23 | { 24 | RetrofitClient.INSTANCE.getApi(Api::class.java) 25 | .checkAppUpdate( 26 | mapOf( 27 | "_api_key" to "24078410e0636c07449112c2a380ae56", 28 | "appKey" to "b4159c34808b3b4e3f75bdb52f4868ab" 29 | ) 30 | ) 31 | }, 32 | { model -> 33 | val appVersionCode = AppUtils.getAppVersionCode() 34 | 35 | if (appVersionCode == -1) { 36 | toast.value = "获取当前app版本号失败,无法检测当前版本是否是最新版" 37 | return@lauch 38 | } 39 | //获取版本号 40 | model?.buildVersionNo?.let { buildVersionNo -> 41 | //云端版本号大于当前版本 42 | if (buildVersionNo.toInt() > appVersionCode) { 43 | model.appURl?.let { 44 | //下载接口不未null 传递到view层 45 | this._updateApp.value = model 46 | } 47 | } 48 | } 49 | }, 50 | ) 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/widget/FloatingCardView.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.widget 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Color 6 | import android.graphics.Outline 7 | import android.graphics.Paint 8 | import android.graphics.Path 9 | import android.graphics.PorterDuff 10 | import android.graphics.PorterDuffXfermode 11 | import android.graphics.RectF 12 | import android.os.Build 13 | import android.util.AttributeSet 14 | import android.view.MotionEvent 15 | import android.view.View 16 | import android.view.ViewOutlineProvider 17 | import android.widget.FrameLayout 18 | 19 | 20 | /** 21 | * @author jiayu.liu 22 | */ 23 | class FloatingCardView : FrameLayout { 24 | constructor(context: Context) : super(context, null) 25 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 26 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( 27 | context, 28 | attrs, 29 | defStyleAttr 30 | ) 31 | 32 | init { 33 | outlineProvider = FloatingViewOutlineProvider() 34 | clipToOutline = true 35 | } 36 | 37 | 38 | override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { 39 | return true 40 | } 41 | 42 | inner class FloatingViewOutlineProvider : ViewOutlineProvider() { 43 | override fun getOutline(view: View?, outline: Outline?) { 44 | outline?.setOval(0, 0, view?.width ?: 0, view?.height ?: 0) 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/widget/GuideView.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.widget 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.appcompat.widget.AppCompatImageView 8 | import androidx.constraintlayout.widget.ConstraintLayout 9 | import androidx.core.view.setPadding 10 | import com.blankj.utilcode.util.ClickUtils 11 | import com.blankj.utilcode.util.ColorUtils 12 | import com.huolala.mockgps.R 13 | import com.huolala.mockgps.utils.MMKVUtils 14 | 15 | /** 16 | * @author jiayu.liu 17 | */ 18 | class GuideView : ConstraintLayout { 19 | private var ivGuideArrows: View 20 | 21 | constructor(context: Context) : super(context) 22 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 23 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( 24 | context, 25 | attrs, 26 | defStyleAttr 27 | ) 28 | 29 | init { 30 | View.inflate(context, R.layout.layout_main_guide, this) 31 | ivGuideArrows = findViewById(R.id.iv_guide_arrows) 32 | 33 | ClickUtils.applySingleDebouncing( 34 | findViewById(R.id.tv_affirm) 35 | ) { 36 | (parent as ViewGroup).removeView(this@GuideView) 37 | MMKVUtils.setGuideVisible(true) 38 | } 39 | 40 | setBackgroundColor(ColorUtils.string2Int("#80000000")) 41 | isClickable = true 42 | } 43 | 44 | fun setGuideView(view: View?) { 45 | view?.let { 46 | addView( 47 | AppCompatImageView(context).apply { 48 | it.isDrawingCacheEnabled = true 49 | setImageBitmap(it.drawingCache) 50 | }, 51 | LayoutParams( 52 | it.width, 53 | it.height, 54 | ).apply { 55 | leftToLeft = LayoutParams.PARENT_ID 56 | topToTop = LayoutParams.PARENT_ID 57 | marginStart = it.x.toInt() 58 | topMargin = it.y.toInt() 59 | }) 60 | 61 | (ivGuideArrows.layoutParams as MarginLayoutParams).let { layoutParams -> 62 | layoutParams.rightMargin = it.width / 2 63 | layoutParams.topMargin = it.height 64 | ivGuideArrows.layoutParams = layoutParams 65 | } 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/widget/HintDialog.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.widget 2 | 3 | import android.app.Dialog 4 | import android.content.Context 5 | import android.view.LayoutInflater 6 | import android.view.ViewGroup 7 | import androidx.databinding.DataBindingUtil 8 | import com.blankj.utilcode.util.ClickUtils 9 | import com.blankj.utilcode.util.ConvertUtils 10 | import com.blankj.utilcode.util.ScreenUtils 11 | import com.huolala.mockgps.R 12 | import com.huolala.mockgps.databinding.DialogHintBinding 13 | 14 | /** 15 | * @author jiayu.liu 16 | */ 17 | class HintDialog(context: Context, title: String = "", msg: String = "") : Dialog(context) { 18 | 19 | init { 20 | DataBindingUtil.bind( 21 | LayoutInflater.from(context) 22 | .inflate(R.layout.dialog_hint, null, false) 23 | )?.let { 24 | setContentView(it.root) 25 | window?.run { 26 | setBackgroundDrawableResource(R.color.transparent); 27 | val lp = attributes; 28 | lp.width = ScreenUtils.getScreenWidth() - ConvertUtils.dp2px(20f) 29 | lp.height = ViewGroup.LayoutParams.WRAP_CONTENT 30 | attributes = lp 31 | } 32 | it.tvTitle.text = title 33 | it.tvMsg.text = msg 34 | ClickUtils.applySingleDebouncing(it.btnConfirm) { 35 | dismiss() 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/widget/InputLatLngDialog.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.widget 2 | 3 | import android.app.Dialog 4 | import android.content.Context 5 | import android.view.LayoutInflater 6 | import android.view.ViewGroup 7 | import androidx.databinding.DataBindingUtil 8 | import com.baidu.mapapi.model.LatLng 9 | import com.blankj.utilcode.util.ClickUtils 10 | import com.blankj.utilcode.util.ConvertUtils 11 | import com.blankj.utilcode.util.ScreenUtils 12 | import com.blankj.utilcode.util.ToastUtils 13 | import com.huolala.mockgps.R 14 | import com.huolala.mockgps.databinding.DialogInputLatlngBinding 15 | 16 | /** 17 | * @author jiayu.liu 18 | * 19 | */ 20 | class InputLatLngDialog( 21 | context: Context, 22 | listener: InputLatLngDialogListener? 23 | ) : Dialog(context) { 24 | 25 | init { 26 | DataBindingUtil.bind( 27 | LayoutInflater.from(context) 28 | .inflate(R.layout.dialog_input_latlng, null, false) 29 | )?.let { dataBinding -> 30 | setContentView(dataBinding.root) 31 | window?.run { 32 | setBackgroundDrawableResource(R.color.transparent) 33 | val lp = attributes 34 | lp.width = ScreenUtils.getScreenWidth() - ConvertUtils.dp2px(20f) 35 | lp.height = ViewGroup.LayoutParams.WRAP_CONTENT 36 | attributes = lp 37 | } 38 | 39 | setCanceledOnTouchOutside(false) 40 | 41 | ClickUtils.applySingleDebouncing(dataBinding.btnConfirm) { 42 | dataBinding.editLatlng.text?.toString()?.let { text -> 43 | var input = text 44 | if (text.contains(",")) { 45 | input = text.replace(",", ",") 46 | } 47 | 48 | val split = input.split(",") 49 | 50 | if (split.size == 2) { 51 | val latLng = try { 52 | LatLng(split[0].toDouble(), split[1].toDouble()) 53 | } catch (e: Exception) { 54 | e.printStackTrace() 55 | ToastUtils.showShort("请输入正确的经纬度") 56 | return@let 57 | } 58 | listener?.onConfirm(latLng) 59 | dismiss() 60 | } 61 | } 62 | } 63 | 64 | ClickUtils.applySingleDebouncing(dataBinding.btnCancel) { 65 | dismiss() 66 | } 67 | } 68 | } 69 | 70 | interface InputLatLngDialogListener { 71 | fun onConfirm(latLng: LatLng) 72 | } 73 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/widget/InputLocationVibrationDialog.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.widget 2 | 3 | import android.app.Dialog 4 | import android.content.Context 5 | import android.content.DialogInterface 6 | import android.view.Gravity 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.SeekBar 11 | import androidx.databinding.DataBindingUtil 12 | import com.blankj.utilcode.util.ConvertUtils 13 | import com.blankj.utilcode.util.ScreenUtils 14 | import com.huolala.mockgps.R 15 | import com.huolala.mockgps.databinding.DialogInputLocationVibrationBinding 16 | import com.huolala.mockgps.utils.MMKVUtils 17 | import com.xw.repo.BubbleSeekBar 18 | 19 | /** 20 | * @author jiayu.liu 21 | */ 22 | class InputLocationVibrationDialog( 23 | context: Context, 24 | private val locationVibrationValue: Int, 25 | private val locationFrequencyValue: Int 26 | ) : Dialog(context) { 27 | var mDismissListener: LocationVibrationDismissListener? = null 28 | 29 | init { 30 | val dataBinding = DataBindingUtil.bind( 31 | LayoutInflater.from(context) 32 | .inflate(R.layout.dialog_input_location_vibration, null, false) 33 | ) 34 | dataBinding?.let { 35 | setContentView(dataBinding.root) 36 | window?.run { 37 | setBackgroundDrawableResource(R.color.transparent) 38 | val lp = attributes 39 | lp.width = ScreenUtils.getScreenWidth() - ConvertUtils.dp2px(20f) 40 | lp.height = ViewGroup.LayoutParams.WRAP_CONTENT 41 | lp.gravity = Gravity.CENTER 42 | attributes = lp 43 | } 44 | dataBinding.seekbar.setProgress(locationVibrationValue.toFloat()) 45 | dataBinding.seekbarFrequency.setProgress(locationFrequencyValue.toFloat()) 46 | 47 | dataBinding.radiusValue = 48 | "设置半径范围(1m~20m),当前范围: ${locationVibrationValue}m" 49 | dataBinding.freqValue = "设置频率(1s~60s),当前频率: ${locationFrequencyValue}s" 50 | 51 | dataBinding.seekbar.onProgressChangedListener = object : 52 | OnProgressChangedListener() { 53 | override fun onProgressChanged( 54 | bubbleSeekBar: BubbleSeekBar?, 55 | progress: Int, 56 | progressFloat: Float, 57 | fromUser: Boolean 58 | ) { 59 | dataBinding.radiusValue = "设置半径范围(1m~20m),当前范围: ${progress}m" 60 | } 61 | } 62 | 63 | dataBinding.seekbarFrequency.onProgressChangedListener = object : 64 | OnProgressChangedListener() { 65 | override fun onProgressChanged( 66 | bubbleSeekBar: BubbleSeekBar?, 67 | progress: Int, 68 | progressFloat: Float, 69 | fromUser: Boolean 70 | ) { 71 | dataBinding.freqValue = "设置频率(1s~60s),当前频率: ${progress}s" 72 | } 73 | } 74 | 75 | 76 | dataBinding.clickListener = View.OnClickListener { 77 | saveValue(dataBinding) 78 | dismiss() 79 | } 80 | 81 | setOnDismissListener { 82 | saveValue(dataBinding) 83 | mDismissListener?.onDismiss() 84 | } 85 | } 86 | 87 | 88 | } 89 | 90 | private fun saveValue(dataBinding: DialogInputLocationVibrationBinding) { 91 | MMKVUtils.setLocationVibrationValue(dataBinding.seekbar.progress) 92 | MMKVUtils.setLocationFrequencyValue(dataBinding.seekbarFrequency.progress) 93 | } 94 | 95 | abstract class OnProgressChangedListener : BubbleSeekBar.OnProgressChangedListener { 96 | 97 | override fun getProgressOnActionUp( 98 | bubbleSeekBar: BubbleSeekBar?, 99 | progress: Int, 100 | progressFloat: Float 101 | ) { 102 | } 103 | 104 | override fun getProgressOnFinally( 105 | bubbleSeekBar: BubbleSeekBar?, 106 | progress: Int, 107 | progressFloat: Float, 108 | fromUser: Boolean 109 | ) { 110 | } 111 | } 112 | 113 | interface LocationVibrationDismissListener { 114 | fun onDismiss() 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/widget/InputSpeed.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.widget 2 | 3 | import android.app.Dialog 4 | import android.content.Context 5 | import android.view.LayoutInflater 6 | import android.view.ViewGroup 7 | import androidx.databinding.DataBindingUtil 8 | import com.blankj.utilcode.util.ClickUtils 9 | import com.blankj.utilcode.util.ConvertUtils 10 | import com.blankj.utilcode.util.ScreenUtils 11 | import com.blankj.utilcode.util.ToastUtils 12 | import com.huolala.mockgps.R 13 | import com.huolala.mockgps.databinding.DialogInputSpeedBinding 14 | import com.huolala.mockgps.utils.MMKVUtils 15 | import kotlinx.android.synthetic.main.dialog_input_speed.view.* 16 | 17 | /** 18 | * @author jiayu.liu 19 | */ 20 | class InputSpeed(context: Context) : Dialog(context) { 21 | init { 22 | DataBindingUtil.bind( 23 | LayoutInflater.from(context) 24 | .inflate(R.layout.dialog_input_speed, null, false) 25 | )?.let { it -> 26 | setContentView(it.root) 27 | window?.run { 28 | setBackgroundDrawableResource(R.color.transparent); 29 | val lp = attributes; 30 | lp.width = ScreenUtils.getScreenWidth() - ConvertUtils.dp2px(20f) 31 | lp.height = ViewGroup.LayoutParams.WRAP_CONTENT 32 | attributes = lp 33 | } 34 | ClickUtils.applySingleDebouncing(it.btnConfirm) { view -> 35 | try { 36 | val speed = it.editSpeed.text.toString().toInt() 37 | if (speed <= 0 || speed > Int.MAX_VALUE) { 38 | ToastUtils.showShort("请输入有效数值(大于0)") 39 | } 40 | MMKVUtils.setSpeed(speed) 41 | } catch (e: Exception) { 42 | e.printStackTrace() 43 | ToastUtils.showShort("存储异常,设置失败") 44 | } 45 | dismiss() 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/widget/LongClickImageButton.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.widget 2 | 3 | import android.app.Service 4 | import android.content.Context 5 | import android.os.Build 6 | import android.os.CombinedVibration 7 | import android.os.Handler 8 | import android.os.Looper 9 | import android.os.Message 10 | import android.os.VibrationEffect 11 | import android.os.Vibrator 12 | import android.os.VibratorManager 13 | import android.util.AttributeSet 14 | import android.view.MotionEvent 15 | import androidx.appcompat.widget.AppCompatImageButton 16 | import com.blankj.utilcode.util.Utils 17 | import java.lang.ref.WeakReference 18 | 19 | /** 20 | * @author jiayu.liu 21 | */ 22 | class LongClickImageButton : AppCompatImageButton { 23 | var listener: LongClickListener? = null 24 | private var vibratorManager: VibratorManager? = null 25 | private var vibrator: Vibrator? = null 26 | private var isLong = false 27 | 28 | companion object { 29 | const val LONG_CLICK_CODE = 1001 30 | } 31 | 32 | constructor(context: Context) : super(context) 33 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 34 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( 35 | context, 36 | attrs, 37 | defStyleAttr 38 | ) 39 | 40 | private val handler: LongClick = LongClick(this) 41 | 42 | init { 43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 44 | vibratorManager = 45 | Utils.getApp().getSystemService(Service.VIBRATOR_MANAGER_SERVICE) as VibratorManager 46 | } else { 47 | vibrator = Utils.getApp().getSystemService(Service.VIBRATOR_SERVICE) as Vibrator 48 | } 49 | setOnLongClickListener { 50 | isLong = true 51 | triggerVibrator() 52 | handler.sendEmptyMessage(LONG_CLICK_CODE) 53 | return@setOnLongClickListener true 54 | } 55 | } 56 | 57 | override fun onTouchEvent(event: MotionEvent?): Boolean { 58 | when (event?.action) { 59 | MotionEvent.ACTION_UP -> { 60 | if (isLong) { 61 | handler.removeCallbacksAndMessages(null) 62 | listener?.touchLongClickFinish() 63 | isLong = false 64 | } 65 | } 66 | 67 | else -> {} 68 | } 69 | return super.onTouchEvent(event) 70 | } 71 | 72 | fun onLongClickCallBack() { 73 | if (handler.delayMillis > 150) { 74 | triggerVibrator() 75 | } 76 | listener?.touchLongClickEvent() 77 | } 78 | 79 | private fun triggerVibrator() { 80 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 81 | vibratorManager?.vibrate( 82 | CombinedVibration.createParallel( 83 | VibrationEffect.createOneShot( 84 | 30, 85 | VibrationEffect.DEFAULT_AMPLITUDE 86 | ) 87 | ) 88 | ) 89 | } else { 90 | vibrator?.vibrate(30) 91 | } 92 | } 93 | 94 | fun setDelayMillis(delayMillis: Long) { 95 | handler.delayMillis = delayMillis 96 | } 97 | 98 | class LongClick(view: LongClickImageButton) : Handler(Looper.getMainLooper()) { 99 | private var reference: WeakReference? = null 100 | var delayMillis = 150L 101 | 102 | init { 103 | reference = WeakReference(view) 104 | } 105 | 106 | 107 | override fun handleMessage(msg: Message) { 108 | reference?.get()?.run { 109 | onLongClickCallBack() 110 | } 111 | //150ms后重复 112 | sendEmptyMessageDelayed(LONG_CLICK_CODE, delayMillis) 113 | } 114 | } 115 | 116 | interface LongClickListener { 117 | fun touchLongClickEvent() 118 | 119 | fun touchLongClickFinish() 120 | } 121 | 122 | 123 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/widget/NaviPopupWindow.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.widget 2 | 3 | import android.content.Context 4 | import android.widget.PopupWindow 5 | import android.graphics.drawable.BitmapDrawable 6 | import android.view.Gravity 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | 10 | import android.view.ViewGroup 11 | import androidx.appcompat.widget.AppCompatButton 12 | import androidx.appcompat.widget.AppCompatTextView 13 | import com.blankj.utilcode.util.ClickUtils 14 | import com.blankj.utilcode.util.ConvertUtils 15 | import com.huolala.mockgps.R 16 | import com.huolala.mockgps.utils.MMKVUtils 17 | import com.xw.repo.BubbleSeekBar 18 | 19 | 20 | /** 21 | * @author jiayu.liu 22 | */ 23 | class NaviPopupWindow(context: Context) : PopupWindow(context) { 24 | private var tvSpeed: AppCompatTextView? = null 25 | private var seekBar: BubbleSeekBar? = null 26 | private var btnInputSpeed: AppCompatButton? = null 27 | 28 | init { 29 | width = ConvertUtils.dp2px(200f) 30 | height = ViewGroup.LayoutParams.WRAP_CONTENT 31 | isFocusable = true 32 | //点击 back 键的时候,窗口会自动消失 33 | setBackgroundDrawable(BitmapDrawable()) 34 | 35 | LayoutInflater.from(context).inflate(R.layout.popupwindow_navi_setting, null, false).apply { 36 | contentView = this 37 | tvSpeed = findViewById(R.id.tv_speed) 38 | seekBar = findViewById(R.id.seekbar) 39 | btnInputSpeed = findViewById(R.id.btn_input_speed) 40 | ClickUtils.applySingleDebouncing(btnInputSpeed) { 41 | InputSpeed(context).show() 42 | dismiss() 43 | } 44 | seekBar?.onProgressChangedListener = object : BubbleSeekBar.OnProgressChangedListener { 45 | override fun onProgressChanged( 46 | bubbleSeekBar: BubbleSeekBar?, 47 | progress: Int, 48 | progressFloat: Float, 49 | fromUser: Boolean 50 | ) { 51 | } 52 | 53 | override fun getProgressOnActionUp( 54 | bubbleSeekBar: BubbleSeekBar?, 55 | progress: Int, 56 | progressFloat: Float 57 | ) { 58 | } 59 | 60 | override fun getProgressOnFinally( 61 | bubbleSeekBar: BubbleSeekBar?, 62 | progress: Int, 63 | progressFloat: Float, 64 | fromUser: Boolean 65 | ) { 66 | tvSpeed?.text = String.format("当前速度:$progress km/h") 67 | MMKVUtils.setSpeed(progress) 68 | } 69 | 70 | } 71 | } 72 | } 73 | 74 | fun show(view: View, gravity: Int = Gravity.BOTTOM) { 75 | MMKVUtils.getSpeed().let { 76 | tvSpeed?.text = String.format("当前速度:$it km/h") 77 | if (it == 30 || it == 60 || it == 90 || it == 120) { 78 | seekBar?.setProgress(it.toFloat()) 79 | } 80 | } 81 | //设置窗口显示位置, 后面两个0 是表示偏移量,可以自由设置 82 | showAsDropDown(view, 0, 1, gravity) 83 | //更新窗口状态 84 | update() 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/widget/PointTypeDialog.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.widget 2 | 3 | import android.app.Dialog 4 | import android.content.Context 5 | import android.text.TextUtils 6 | import android.view.LayoutInflater 7 | import android.view.ViewGroup 8 | import android.widget.RadioButton 9 | import androidx.core.view.children 10 | import androidx.databinding.DataBindingUtil 11 | import com.blankj.utilcode.util.ClickUtils 12 | import com.blankj.utilcode.util.ConvertUtils 13 | import com.blankj.utilcode.util.ScreenUtils 14 | import com.huolala.mockgps.R 15 | import com.huolala.mockgps.databinding.DialogHintBinding 16 | import com.huolala.mockgps.databinding.DialogPointTypeBinding 17 | import com.huolala.mockgps.utils.LocationUtils 18 | 19 | /** 20 | * @author jiayu.liu 21 | */ 22 | class PointTypeDialog(context: Context) : Dialog(context) { 23 | private var binding: DialogPointTypeBinding? = null 24 | var listener: PointTypeDialogListener? = null 25 | 26 | 27 | init { 28 | DataBindingUtil.bind( 29 | LayoutInflater.from(context) 30 | .inflate(R.layout.dialog_point_type, null, false) 31 | )?.let { 32 | binding = it 33 | setContentView(it.root) 34 | window?.run { 35 | setBackgroundDrawableResource(R.color.transparent); 36 | val lp = attributes; 37 | lp.width = ScreenUtils.getScreenWidth() - ConvertUtils.dp2px(20f) 38 | lp.height = ViewGroup.LayoutParams.WRAP_CONTENT 39 | attributes = lp 40 | } 41 | ClickUtils.applySingleDebouncing(it.btnConfirm) { 42 | binding?.rgType?.checkedRadioButtonId?.let { id -> 43 | var type = "" 44 | when (id) { 45 | R.id.rb_gcj02 -> { 46 | type = LocationUtils.gcj02 47 | } 48 | R.id.rb_bd09 -> { 49 | type = LocationUtils.bd09 50 | } 51 | R.id.rb_gps84 -> { 52 | type = LocationUtils.gps84 53 | } 54 | else -> {} 55 | } 56 | if (!TextUtils.isEmpty(type)) { 57 | listener?.onDismiss(type) 58 | } 59 | } 60 | dismiss() 61 | } 62 | } 63 | } 64 | 65 | fun show(type: String) { 66 | if (TextUtils.isEmpty(type)) { 67 | return 68 | } 69 | when (type) { 70 | LocationUtils.gcj02 -> { 71 | binding?.checkId = R.id.rb_gcj02 72 | } 73 | LocationUtils.bd09 -> { 74 | binding?.checkId = R.id.rb_bd09 75 | } 76 | LocationUtils.gps84 -> { 77 | binding?.checkId = R.id.rb_gps84 78 | } 79 | else -> {} 80 | } 81 | 82 | super.show() 83 | } 84 | 85 | interface PointTypeDialogListener { 86 | fun onDismiss(type: String) 87 | } 88 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huolala/mockgps/widget/VerticalRecyclerView.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps.widget 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.MotionEvent 6 | import androidx.recyclerview.widget.RecyclerView 7 | 8 | /** 9 | * @author jiayu.liu 10 | */ 11 | class VerticalRecyclerView : RecyclerView { 12 | constructor(context: Context) : super(context) 13 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 14 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( 15 | context, 16 | attrs, 17 | defStyleAttr 18 | ) 19 | 20 | override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { 21 | parent.requestDisallowInterceptTouchEvent(true) 22 | return super.dispatchTouchEvent(ev) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_add.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_adjust_order.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_adjust_order.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_app_update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_app_update.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_change.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_change.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_cur_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_cur_location.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_floating_adjust_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_floating_adjust_close.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_floating_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_floating_setting.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_guide_arrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_guide_arrows.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_guide_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_guide_button.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_location.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_location_adjust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_location_adjust.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_navi_remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_navi_remove.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_navi_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_navi_setting.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_pause.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_play.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_refresh.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_select_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_select_arrow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_setting.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_speed_setting_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_speed_setting_left.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_speed_setting_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/ic_speed_setting_right.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/drawable-xxhdpi/img.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/floating_seek_bar_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/seek_bar_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_floating_play.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_circle_color_master.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_10_color_black_30_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_10_color_black_30_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_10_color_black_30_right_circle_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_10_color_red_stroke.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_10_color_white_stroke.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_15_color_grey_50.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_15_color_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_15_color_white_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_30_color_master.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_5_color_black_30.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_5_color_black_50_stroke.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_5_color_master_50_stroke.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_5_color_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_expand.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_guide.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 30 | 31 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_navi.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | 25 | 26 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_pick_location.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 11 | 12 | 13 | 22 | 23 | 29 | 30 | 36 | 37 | 38 | 39 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 21 | 22 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_file_mock_hint.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 26 | 27 | 39 | 40 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_hint.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 26 | 27 | 39 | 40 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_input_latlng.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 34 | 35 | 46 | 47 | 62 | 63 | 64 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_input_speed.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 27 | 28 | 38 | 39 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_navi_path.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 17 | 18 | 19 | 29 | 30 | 31 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_point_type.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | 15 | 16 | 28 | 29 | 40 | 41 | 42 | 47 | 48 | 53 | 54 | 59 | 60 | 61 | 62 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_select_navi_map.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 44 | 45 | 46 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_history.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 19 | 20 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_navi_poi_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | 18 | 22 | 23 | 34 | 35 | 41 | 42 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_poiinfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 14 | 15 | 18 | 19 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | 38 | 43 | 44 | 49 | 50 | 51 | 60 | 61 | 70 | 71 | 72 | 73 | 74 | 79 | 80 | 81 | 82 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_title.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 25 | 26 | 34 | 35 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_floating_location_adjust.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 18 | 25 | 26 | 39 | 40 | 51 | 52 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_location_always_allow.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 22 | 23 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_main_card_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 23 | 24 | 28 | 29 | 34 | 35 | 36 | 37 | 42 | 43 | 52 | 53 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_main_guide.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 28 | 29 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_navi_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 25 | 26 | 35 | 36 | 43 | 44 | 59 | 60 | 72 | 73 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_regulate_value.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 28 | 29 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/popupwindow_navi_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 25 | 26 | 42 | 43 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/app/src/main/res/mipmap-xxxhdpi/ic_launcher.jpeg -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #17abe3 4 | #1195db 5 | #3f81c1 6 | #FF000000 7 | #FFFFFFFF 8 | #00000000 9 | #80000000 10 | #4D000000 11 | #FFbfbfbf 12 | #80bfbfbf 13 | #FFFFFF00 14 | #FFFFA400 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MockGps 3 | 切换 4 | 设置 5 | 进入mockGps 6 | 确认 7 | 8 | Intent intentBroadcast = new Intent();\n 9 | intentBroadcast.setAction(\"com.huolala.mockgps.navi\");\n 10 | intentBroadcast.putExtra(\"start\", \"116.419431,40.028795\");\n 11 | intentBroadcast.putExtra(\"end\", \"116.409816,40.05139\");\n 12 | //bd09 gps84 gcj02\n 13 | intentBroadcast.putExtra(\"type\", \"gcj02\");\n// 14 | //startNavi stopNavi\n 15 | intentBroadcast.putExtra(\"event\", \"startNavi\");\n 16 | sendBroadcast(intentBroadcast); 17 | 18 | 19 | 坐标类型:gcj02\n 20 | 数据格式:116.123123,40.123123;116.123123,40.123123;116.123123,40.123123;116.123123,40.123123;116.123123,40.123123;116.123123,40.123123… 21 | 22 | 扩展功能在此处 23 | 知道了 24 | 历史数据(最多显示10条) 25 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 17 | 18 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_descriptor.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/test/java/com/huolala/mockgps/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.huolala.mockgps 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | apply from: 'config.gradle' 3 | 4 | buildscript { 5 | repositories { 6 | maven { url 'https://maven.aliyun.com/repository/google' } 7 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } 8 | maven { url 'https://maven.aliyun.com/repository/public' } 9 | maven { url 'https://maven.aliyun.com/repository/jcenter' } 10 | // mavenCentral() 11 | maven { url "https://jitpack.io" } 12 | } 13 | dependencies { 14 | classpath "com.android.tools.build:gradle:7.0.4" 15 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10" 16 | classpath "org.jetbrains.kotlin:kotlin-android-extensions:1.6.10" 17 | 18 | // NOTE: Do not place your application dependencies here; they belong 19 | // in the individual module build.gradle files 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } -------------------------------------------------------------------------------- /common/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion rootProject.ext.android.compileSdkVersion 8 | 9 | dataBinding { 10 | enabled true 11 | } 12 | 13 | defaultConfig { 14 | minSdkVersion rootProject.ext.android.minSdkVersion 15 | targetSdkVersion rootProject.ext.android.targetSdkVersion 16 | versionCode rootProject.ext.android.versionCode 17 | versionName rootProject.ext.android.versionName 18 | 19 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 20 | consumerProguardFiles "consumer-rules.pro" 21 | 22 | javaCompileOptions { 23 | annotationProcessorOptions { 24 | arguments = [AROUTER_MODULE_NAME: project.getName()] 25 | } 26 | } 27 | } 28 | 29 | buildTypes { 30 | release { 31 | minifyEnabled false 32 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 33 | } 34 | } 35 | } 36 | 37 | dependencies { 38 | api fileTree(dir: "libs", include: ["*.jar"]) 39 | api rootProject.ext.support.core_ktx 40 | api rootProject.ext.support.appcompat 41 | api rootProject.ext.support.constraintlayout 42 | api rootProject.ext.support.material 43 | 44 | api rootProject.ext.dependencies.retrofit 45 | api rootProject.ext.dependencies.converter_gson 46 | api rootProject.ext.dependencies.logging 47 | api rootProject.ext.dependencies.arouter 48 | api rootProject.ext.dependencies.lifecycle_viewmodel 49 | api rootProject.ext.dependencies.lifecycle_runtime 50 | api rootProject.ext.dependencies.banner 51 | api rootProject.ext.dependencies.agentweb 52 | api rootProject.ext.dependencies.autosize 53 | api(rootProject.ext.dependencies.lottie) { 54 | exclude group: 'androidx.appcompat', module: 'appcompat' 55 | } 56 | api rootProject.ext.dependencies.refresh_layout 57 | api rootProject.ext.dependencies.chips_layout 58 | api rootProject.ext.dependencies.utilcodex 59 | api rootProject.ext.dependencies.mmkv 60 | api rootProject.ext.dependencies.bugly 61 | api rootProject.ext.dependencies.bubbleseekbar 62 | api rootProject.ext.dependencies.kotlin_reflect 63 | 64 | api rootProject.ext.dependencies.glide 65 | kapt rootProject.ext.dependencies.glide_compiler 66 | 67 | } -------------------------------------------------------------------------------- /common/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/common/consumer-rules.pro -------------------------------------------------------------------------------- /common/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /common/src/androidTest/java/com/castiel/common/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/AppManager.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common 2 | 3 | import android.app.Application 4 | import com.alibaba.android.arouter.launcher.ARouter 5 | import com.blankj.utilcode.util.LogUtils 6 | import com.tencent.bugly.crashreport.CrashReport 7 | import com.tencent.mmkv.MMKV 8 | 9 | class AppManager private constructor() { 10 | private var context: Application? = null 11 | 12 | companion object { 13 | val INSTANCE: AppManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { 14 | AppManager() 15 | } 16 | } 17 | 18 | fun init(context: Application?) { 19 | if (context == null) 20 | throw NullPointerException("Application isn't null") 21 | if (this.context != null) { 22 | throw Exception("AppManager Already initialized") 23 | } 24 | this.context = context 25 | MMKV.initialize(context) 26 | if (BuildConfig.DEBUG) { 27 | ARouter.openLog() 28 | ARouter.openDebug() 29 | } 30 | ARouter.init(context) 31 | CrashReport.initCrashReport(context, "8d25df392e", BuildConfig.DEBUG) 32 | LogUtils.getConfig().isLogSwitch = BuildConfig.DEBUG 33 | LogUtils.getConfig().globalTag = "castiel" 34 | } 35 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/BaseApp.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common 2 | 3 | import android.app.Application 4 | import com.castiel.common.widget.MsbRefreshFooter 5 | import com.castiel.common.widget.MsbRefreshHeader 6 | import com.scwang.smart.refresh.layout.SmartRefreshLayout 7 | 8 | 9 | open class BaseApp : Application() { 10 | 11 | override fun onCreate() { 12 | super.onCreate() 13 | SmartRefreshLayout.setDefaultRefreshHeaderCreator { context, _ -> 14 | MsbRefreshHeader( 15 | context 16 | ) 17 | } 18 | //设置全局的Footer构建器 19 | SmartRefreshLayout.setDefaultRefreshFooterCreator { context, _ -> 20 | MsbRefreshFooter( 21 | context 22 | ) 23 | } 24 | AppManager.INSTANCE.init(this) 25 | } 26 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.base 2 | 3 | import android.app.Activity 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.TextView 9 | import androidx.databinding.DataBindingUtil 10 | import androidx.databinding.ViewDataBinding 11 | import androidx.fragment.app.Fragment 12 | import androidx.lifecycle.Observer 13 | import androidx.lifecycle.ViewModelProvider 14 | import com.alibaba.android.arouter.launcher.ARouter 15 | import com.blankj.utilcode.util.ToastUtils 16 | import com.castiel.common.R 17 | import com.castiel.common.dialog.LoadingDialog 18 | import com.castiel.common.widget.MultiStateView 19 | 20 | abstract class BaseFragment : Fragment() { 21 | protected lateinit var mContext: Activity 22 | 23 | private var isDataInit = false 24 | protected lateinit var dataBinding: V 25 | protected val viewModel: VM by lazy { ViewModelProvider(this)[this.initViewModel()] } 26 | 27 | protected abstract fun initViewModel(): Class 28 | protected abstract fun initViewModelId(): Int? 29 | protected abstract fun getLayoutId(): Int 30 | protected abstract fun initView() 31 | protected abstract fun initData() 32 | protected abstract fun initObserver() 33 | 34 | private var loading: LoadingDialog? = null 35 | 36 | override fun onCreateView( 37 | inflater: LayoutInflater, 38 | container: ViewGroup?, 39 | savedInstanceState: Bundle? 40 | ): View? { 41 | mContext = activity!! 42 | dataBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false) as V 43 | dataBinding.lifecycleOwner = this 44 | ARouter.getInstance().inject(this) 45 | val initViewModelId = initViewModelId() 46 | initViewModelId?.let { dataBinding.setVariable(initViewModelId, viewModel) } 47 | return dataBinding.root 48 | } 49 | 50 | protected open fun setStatusBar() { 51 | } 52 | 53 | override fun onActivityCreated(savedInstanceState: Bundle?) { 54 | super.onActivityCreated(savedInstanceState) 55 | loading = LoadingDialog(mContext) 56 | initView() 57 | initObserver() 58 | addObserver() 59 | setStatusBar() 60 | } 61 | 62 | override fun onResume() { 63 | super.onResume() 64 | if (!isDataInit) { 65 | initData() 66 | isDataInit = true 67 | } 68 | } 69 | 70 | private fun addObserver() { 71 | val stateView: MultiStateView? = 72 | dataBinding.root.findViewById(R.id.state_view) 73 | stateView?.let { 74 | stateView.getView(MultiStateView.ViewState.ERROR)?.findViewById(R.id.retry) 75 | ?.setOnClickListener { 76 | viewModel.toast.value = "重试" 77 | initData() 78 | } 79 | viewModel.state.observe(this, Observer { 80 | stateView.viewState = it 81 | }) 82 | } 83 | viewModel.toast.observe(this, Observer { 84 | showToast(it) 85 | }) 86 | viewModel.loading.observe(this, Observer { 87 | if (it) { 88 | loading?.show() 89 | } else { 90 | loading?.dismiss() 91 | } 92 | }) 93 | } 94 | 95 | private fun showToast(msg: String?) { 96 | msg?.let { ToastUtils.showShort(msg) } 97 | } 98 | 99 | override fun onPause() { 100 | super.onPause() 101 | loading?.dismiss() 102 | } 103 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/base/BaseListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.base 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.databinding.DataBindingUtil 7 | import androidx.recyclerview.widget.DiffUtil 8 | import androidx.recyclerview.widget.ListAdapter 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.blankj.utilcode.util.ClickUtils 11 | 12 | abstract class BaseListAdapter(callback: DiffUtil.ItemCallback) : 13 | ListAdapter(callback) { 14 | 15 | var clickListener: OnItemClickListener? = null 16 | var clickLongListener: OnItemLongClickListener? = null 17 | 18 | override fun onBindViewHolder(holder: VH, position: Int) { 19 | clickListener?.run { 20 | ClickUtils.applySingleDebouncing(holder.itemView) { 21 | onItemClick(it, getItem(position), position) 22 | } 23 | } 24 | clickLongListener?.run { 25 | holder.itemView.setOnLongClickListener { v -> 26 | onItemLongClick( 27 | v, 28 | getItem(position), 29 | position 30 | ) 31 | true 32 | } 33 | } 34 | onBindViewHolderModel(holder, position) 35 | } 36 | 37 | abstract fun onBindViewHolderModel(holder: VH, position: Int) 38 | 39 | interface OnItemClickListener { 40 | fun onItemClick(view: View?, t: T, position: Int) 41 | } 42 | 43 | interface OnItemLongClickListener { 44 | fun onItemLongClick(view: View?, t: T, position: Int) 45 | } 46 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/base/BaseResponse.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.base 2 | 3 | data class BaseResponse( 4 | val code: Int, 5 | val message: String, 6 | val data: T? 7 | ) { 8 | 9 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/base/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.base 2 | 3 | import android.text.TextUtils 4 | import androidx.lifecycle.* 5 | import com.castiel.common.widget.MultiStateView 6 | import kotlinx.coroutines.Job 7 | import kotlinx.coroutines.launch 8 | import java.lang.Exception 9 | 10 | typealias Block = suspend () -> BaseResponse 11 | typealias Success = (T?) -> Unit 12 | typealias Failure = (BaseResponse<*>) -> Unit 13 | typealias Error = (e: String) -> Unit 14 | typealias Complete = () -> Unit 15 | 16 | open class BaseViewModel : ViewModel() { 17 | var loading: MutableLiveData = MutableLiveData() 18 | var toast: MutableLiveData = MutableLiveData() 19 | var state: MutableLiveData = MutableLiveData() 20 | 21 | protected fun lauch( 22 | block: Block, 23 | success: Success, 24 | failure: Failure? = null, 25 | error: Error? = null, 26 | complete: Complete? = null 27 | ): Job { 28 | return viewModelScope.launch { 29 | try { 30 | val response = block() 31 | if (response.code == 0) { 32 | success(response.data) 33 | } else { 34 | failure?.run { 35 | invoke(response) 36 | } ?: run { 37 | if (!TextUtils.isEmpty(response.message)) 38 | toast.value = response.message 39 | } 40 | } 41 | } catch (e: Exception) { 42 | e.printStackTrace() 43 | error?.run { invoke(e.toString()) } ?: run { toast.value = "网络异常,请稍后重试" } 44 | } finally { 45 | complete?.invoke() 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/base/IFragmentProvider.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.base 2 | 3 | import androidx.fragment.app.Fragment 4 | import com.alibaba.android.arouter.facade.template.IProvider 5 | 6 | interface IFragmentProvider : IProvider { 7 | fun getFragment(): Fragment 8 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/dialog/BaseDialog.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.dialog 2 | 3 | import android.app.Dialog 4 | import android.content.Context 5 | import android.graphics.Color 6 | import android.graphics.drawable.ColorDrawable 7 | import android.view.Gravity 8 | import android.view.Window 9 | import android.view.WindowManager 10 | 11 | 12 | open class BaseDialog(context: Context) : Dialog(context) { 13 | 14 | /** 15 | * 默认无背景 居中 16 | */ 17 | init { 18 | requestWindowFeature(Window.FEATURE_NO_TITLE) 19 | if (window != null) { 20 | window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) 21 | window!!.setGravity(Gravity.CENTER) 22 | window!!.decorView.setPadding(0, 0, 0, 0) 23 | val layoutParams = window!!.attributes 24 | layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT 25 | layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT 26 | window!!.setDimAmount(0f) 27 | layoutParams.horizontalMargin = 0f 28 | window!!.attributes = layoutParams 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/dialog/LoadingDialog.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.dialog 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import com.castiel.common.R 6 | 7 | class LoadingDialog(context: Context) : BaseDialog(context) { 8 | 9 | init { 10 | val view: View = layoutInflater.inflate(R.layout.dialog_loading, null) 11 | setContentView(view) 12 | setCanceledOnTouchOutside(false) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/http/RetrofitClient.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.http 2 | 3 | import okhttp3.OkHttpClient 4 | import okhttp3.logging.HttpLoggingInterceptor 5 | import retrofit2.Retrofit 6 | import retrofit2.converter.gson.GsonConverterFactory 7 | import java.util.concurrent.TimeUnit 8 | 9 | 10 | class RetrofitClient private constructor() { 11 | private val baseUrl = "https://www.pgyer.com" 12 | 13 | private val retrofit: Retrofit by lazy { 14 | Retrofit.Builder().apply { 15 | val okHttpClient = OkHttpClient.Builder().apply { 16 | readTimeout(30, TimeUnit.SECONDS) 17 | readTimeout(30, TimeUnit.SECONDS) 18 | addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) 19 | } 20 | client(okHttpClient.build()) 21 | addConverterFactory(GsonConverterFactory.create()) 22 | baseUrl(baseUrl) 23 | }.build() 24 | } 25 | 26 | companion object { 27 | val INSTANCE: RetrofitClient by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { 28 | RetrofitClient() 29 | } 30 | } 31 | 32 | 33 | fun getApi(t: Class): T { 34 | return retrofit.create(t) 35 | } 36 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/recycler/decoration/HorizontalItemDecoration.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.recycler.decoration 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.drawable.Drawable 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.blankj.utilcode.util.ConvertUtils 8 | 9 | /** 10 | * 横向recycleView分割线LinearLayoutManager 11 | * @author jiayu.liu 12 | */ 13 | class HorizontalItemDecoration constructor( 14 | context: Context, 15 | drawable: Int, 16 | top: Float, 17 | bottom: Float 18 | ) : RecyclerView.ItemDecoration() { 19 | private val mDivider: Drawable = 20 | context.resources.getDrawable(drawable, null) 21 | private val top: Int = ConvertUtils.dp2px(top) 22 | private val bottom: Int = ConvertUtils.dp2px(bottom) 23 | 24 | override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { 25 | val top = parent.paddingTop 26 | val bottom = parent.height - parent.paddingBottom 27 | val childCount = parent.childCount 28 | //最后一个item不画分割线 29 | for (i in 0 until childCount - 1) { 30 | val child = parent.getChildAt(i) 31 | val params = child.layoutParams as RecyclerView.LayoutParams 32 | 33 | val left = child.left + params.leftMargin 34 | val right = left + mDivider.intrinsicWidth 35 | mDivider.setBounds(left, top + this.top, right, bottom + this.bottom) 36 | mDivider.draw(c) 37 | } 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/recycler/decoration/RecyclerItemDecoration.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.recycler.decoration 2 | 3 | import android.content.Context 4 | import android.graphics.Rect 5 | import android.view.View 6 | import androidx.recyclerview.widget.GridLayoutManager 7 | import androidx.recyclerview.widget.LinearLayoutManager 8 | import androidx.recyclerview.widget.RecyclerView 9 | import androidx.recyclerview.widget.StaggeredGridLayoutManager 10 | import com.beloo.widget.chipslayoutmanager.ChipsLayoutManager 11 | import com.blankj.utilcode.util.ConvertUtils 12 | 13 | /** 14 | * recyclerview 增加间隔 15 | */ 16 | class RecyclerItemDecoration : RecyclerView.ItemDecoration { 17 | 18 | private var distance = 0 19 | private var indent = 0 20 | private var bottom = 0 21 | private var isFirst = true 22 | 23 | constructor( 24 | context: Context?, 25 | distance: Float, 26 | indent: Float 27 | ) { 28 | this.distance = ConvertUtils.dp2px(distance) 29 | this.indent = ConvertUtils.dp2px(indent) 30 | } 31 | 32 | //GridLayoutManager 或者 StaggeredGridLayoutManager 33 | constructor( 34 | context: Context?, 35 | bottom: Float 36 | ) { 37 | this.bottom = ConvertUtils.dp2px(bottom) 38 | isFirst = false 39 | } 40 | 41 | //头部偏移 42 | fun setFirst(firstTop: Boolean) { 43 | isFirst = firstTop 44 | } 45 | 46 | override fun getItemOffsets( 47 | outRect: Rect, 48 | view: View, 49 | parent: RecyclerView, 50 | state: RecyclerView.State 51 | ) { 52 | val layoutManager = parent.layoutManager 53 | val position = parent.getChildAdapterPosition(view) 54 | val adapter = parent.adapter 55 | when (layoutManager) { 56 | is GridLayoutManager, 57 | is StaggeredGridLayoutManager 58 | -> { 59 | outRect.set(0, if (position == 0 && isFirst) bottom else 0, 0, bottom) 60 | } 61 | is LinearLayoutManager -> { 62 | val orientation = layoutManager.orientation 63 | if (orientation == LinearLayoutManager.VERTICAL) { 64 | outRect.set( 65 | 0, 66 | if (position == 0 && isFirst) distance + indent else 0, 67 | 0, 68 | if (adapter != null && position == adapter.itemCount - 1) distance + indent else distance 69 | ) 70 | } else { 71 | outRect.set( 72 | if (position == 0) distance + indent else 0, 73 | 0, 74 | if (adapter != null && position == adapter.itemCount - 1) distance + indent else distance, 75 | 0 76 | ) 77 | } 78 | } 79 | is ChipsLayoutManager -> { 80 | outRect.set(0, if (position == 0 && isFirst) bottom else 0, 10, bottom) 81 | } 82 | } 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/recycler/decoration/VerticalItemDecoration.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.recycler.decoration 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.drawable.Drawable 6 | import androidx.recyclerview.widget.RecyclerView 7 | import androidx.recyclerview.widget.RecyclerView.ItemDecoration 8 | import com.blankj.utilcode.util.ConvertUtils 9 | 10 | /** 11 | * 竖向recycleView分割线LinearLayoutManager 12 | * @author jiayu.liu 13 | */ 14 | class VerticalItemDecoration constructor( 15 | context: Context, 16 | drawable: Int, 17 | left: Float, 18 | right: Float 19 | ) : ItemDecoration() { 20 | private val mDivider: Drawable = 21 | context.resources.getDrawable(drawable, null) 22 | private val left: Int = ConvertUtils.dp2px(left) 23 | private val right: Int = ConvertUtils.dp2px(right) 24 | 25 | override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { 26 | val left = parent.paddingLeft 27 | val right = parent.width - parent.paddingRight 28 | val childCount = parent.childCount 29 | //最后一个item不画分割线 30 | for (i in 0 until childCount - 1) { 31 | val child = parent.getChildAt(i) 32 | val params = child.layoutParams as RecyclerView.LayoutParams 33 | val top = child.bottom + params.bottomMargin 34 | val bottom = top + mDivider.intrinsicHeight 35 | mDivider.setBounds(left + this.left, top, right - this.right, bottom) 36 | mDivider.draw(c) 37 | } 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/utils/DataBindingAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.utils 2 | 3 | import android.graphics.drawable.Drawable 4 | import android.text.TextUtils 5 | import android.view.View 6 | import androidx.appcompat.widget.AppCompatImageView 7 | import androidx.databinding.BindingAdapter 8 | import com.blankj.utilcode.util.ClickUtils 9 | import com.bumptech.glide.Glide 10 | import com.bumptech.glide.request.RequestOptions 11 | 12 | object DataBindingAdapter { 13 | 14 | @JvmStatic 15 | @BindingAdapter("url", "error", "placeholder", "options", requireAll = false) 16 | fun imageViewUrl( 17 | imageView: AppCompatImageView, 18 | url: String, 19 | error: Drawable?, 20 | placeholder: Drawable?, 21 | options: RequestOptions?, 22 | ) { 23 | if (TextUtils.isEmpty(url)) return 24 | val glide = Glide.with(imageView) 25 | .load(url) 26 | error?.let { 27 | glide.error(it) 28 | } 29 | placeholder?.let { 30 | glide.placeholder(it) 31 | } 32 | options?.let { 33 | glide.apply(options) 34 | } 35 | glide.into(imageView) 36 | } 37 | 38 | 39 | @JvmStatic 40 | @BindingAdapter("resource", "error", "placeholder", "options", requireAll = false) 41 | fun imageViewResource( 42 | imageView: AppCompatImageView, 43 | resource: Drawable, 44 | error: Int?, 45 | placeholder: Int?, 46 | options: RequestOptions?, 47 | ) { 48 | val glide = Glide.with(imageView) 49 | .load(resource) 50 | error?.let { 51 | glide.error(it) 52 | } 53 | placeholder?.let { 54 | glide.placeholder(it) 55 | } 56 | options?.let { 57 | glide.apply(options) 58 | } 59 | glide.into(imageView) 60 | } 61 | 62 | @JvmStatic 63 | @BindingAdapter("clickListener", requireAll = false) 64 | fun viewClick(view: View, clickListener: View.OnClickListener?) { 65 | if (clickListener == null) { 66 | throw IllegalArgumentException("clickListener is null") 67 | } 68 | ClickUtils.applySingleDebouncing(view, clickListener) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/utils/GlideUtils.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.utils 2 | 3 | import com.bumptech.glide.load.resource.bitmap.CenterCrop 4 | import com.bumptech.glide.load.resource.bitmap.CircleCrop 5 | import com.bumptech.glide.load.resource.bitmap.RoundedCorners 6 | 7 | import com.bumptech.glide.request.RequestOptions 8 | 9 | 10 | /** 11 | * @author jiayu.liu 12 | */ 13 | object GlideUtils { 14 | 15 | /** 16 | * 设置圆形图片 17 | */ 18 | fun circleImage(): RequestOptions { 19 | return RequestOptions.bitmapTransform(CircleCrop()) 20 | } 21 | 22 | 23 | /** 24 | * 设置圆角图片 25 | */ 26 | fun rounderImage(roundingRadius: Int = 0): RequestOptions { 27 | return RequestOptions.bitmapTransform(RoundedCorners(roundingRadius)) 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/utils/MMKVWrap.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.utils 2 | 3 | import com.tencent.mmkv.MMKV 4 | 5 | class MMKVWrap private constructor() { 6 | private var mMMKV: MMKV = MMKV.defaultMMKV() 7 | 8 | companion object { 9 | val instance = MMKVWrapHolder.holder.mMMKV 10 | } 11 | 12 | private object MMKVWrapHolder { 13 | val holder = MMKVWrap() 14 | } 15 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/widget/BoldTextView.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.widget 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Paint 6 | import android.util.AttributeSet 7 | import androidx.appcompat.widget.AppCompatTextView 8 | import com.castiel.common.R 9 | 10 | /** 11 | * 粗体 12 | */ 13 | class BoldTextView @JvmOverloads constructor( 14 | context: Context, 15 | attrs: AttributeSet? = null, 16 | defStyleAttr: Int = 0 17 | ) : AppCompatTextView(context, attrs, defStyleAttr) { 18 | private var mStriking = 0.5f 19 | override fun onDraw(canvas: Canvas) { 20 | //获取当前控件的画笔 21 | val paint = paint 22 | //设置画笔的描边宽度值 23 | paint.strokeWidth = mStriking 24 | paint.style = Paint.Style.FILL_AND_STROKE 25 | super.onDraw(canvas) 26 | } 27 | 28 | init { 29 | val typedArray = 30 | context.obtainStyledAttributes(attrs, R.styleable.BoldTextView, defStyleAttr, 0) 31 | val striking = typedArray.getInt(R.styleable.BoldTextView_striking, 0).toFloat() 32 | if (striking > 0.0f) { 33 | this.mStriking = striking 34 | } 35 | typedArray.recycle() 36 | } 37 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/widget/MixedTextView.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.widget 2 | 3 | import android.content.Context 4 | import android.graphics.Paint 5 | import android.text.TextUtils 6 | import android.util.AttributeSet 7 | import android.widget.TextView 8 | import androidx.appcompat.widget.AppCompatTextView 9 | import com.blankj.utilcode.util.LogUtils 10 | 11 | /** 12 | * 中英文混排自动换行 13 | * 需要设置maxWidth才能做到适配 14 | * 15 | * @author jiayu.liu 16 | */ 17 | open class MixedTextView : AppCompatTextView { 18 | private var mEnabled = true 19 | 20 | constructor(context: Context?) : super(context!!) {} 21 | constructor(context: Context?, attrs: AttributeSet?) : super( 22 | context!!, attrs 23 | ) 24 | 25 | constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super( 26 | context!!, attrs, defStyle 27 | ) 28 | 29 | fun setAutoSplitEnabled(enabled: Boolean) { 30 | mEnabled = enabled 31 | } 32 | 33 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 34 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 35 | val widthMode = MeasureSpec.getMode(widthMeasureSpec) 36 | } 37 | 38 | override fun setText(text: CharSequence, type: BufferType) { 39 | if (mEnabled) { 40 | val newText = autoSplitText(text) 41 | if (!TextUtils.isEmpty(newText)) { 42 | super.setText(newText, type) 43 | } 44 | } else { 45 | super.setText(text, type) 46 | } 47 | } 48 | 49 | private fun autoSplitText(text: CharSequence): String { 50 | if (TextUtils.isEmpty(text)) { 51 | return "" 52 | } 53 | //原始文本 54 | val rawText = text.toString() 55 | //paint,包含字体等信息 56 | val tvPaint: Paint = paint 57 | //控件可用宽度 58 | val maxWidth: Int = maxWidth 59 | 60 | val tvWidth = (maxWidth - paddingLeft - paddingRight).toFloat() 61 | 62 | //将原始文本按行拆分 63 | val rawTextLines = rawText.replace("\r".toRegex(), "").split("\n").toTypedArray() 64 | val sbNewText = StringBuilder() 65 | for (rawTextLine in rawTextLines) { 66 | if (tvPaint.measureText(rawTextLine) <= tvWidth) { 67 | //如果整行宽度在控件可用宽度之内,就不处理了 68 | sbNewText.append(rawTextLine) 69 | } else { 70 | //如果整行宽度超过控件可用宽度,则按字符测量,在超过可用宽度的前一个字符处手动换行 71 | var lineWidth = 0f 72 | var cnt = 0 73 | while (cnt != rawTextLine.length) { 74 | val ch = rawTextLine[cnt] 75 | lineWidth += tvPaint.measureText(ch.toString()) 76 | if (lineWidth <= tvWidth) { 77 | sbNewText.append(ch) 78 | } else { 79 | sbNewText.append("\n") 80 | lineWidth = 0f 81 | --cnt 82 | } 83 | ++cnt 84 | } 85 | } 86 | sbNewText.append("\n") 87 | } 88 | 89 | //把结尾多余的\n去掉 90 | if (!rawText.endsWith("\n")) { 91 | sbNewText.deleteCharAt(sbNewText.length - 1) 92 | } 93 | return sbNewText.toString() 94 | } 95 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/widget/MsbRefreshFooter.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.widget 2 | 3 | import android.content.Context 4 | import android.view.Gravity 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.widget.LinearLayout 8 | import com.castiel.common.R 9 | import com.scwang.smart.refresh.layout.api.RefreshFooter 10 | import com.scwang.smart.refresh.layout.api.RefreshKernel 11 | import com.scwang.smart.refresh.layout.api.RefreshLayout 12 | import com.scwang.smart.refresh.layout.constant.RefreshState 13 | import com.scwang.smart.refresh.layout.constant.SpinnerStyle 14 | 15 | class MsbRefreshFooter(context: Context) : LinearLayout(context), RefreshFooter { 16 | 17 | 18 | init { 19 | // setBackgroundColor(ContextCompat.getColor(context, R.color.white)) 20 | gravity = Gravity.CENTER_HORIZONTAL; 21 | addView(LayoutInflater.from(context).inflate(R.layout.layout_footer_view, this, false)) 22 | } 23 | 24 | override fun getSpinnerStyle(): SpinnerStyle { 25 | return SpinnerStyle.Translate 26 | } 27 | 28 | override fun onFinish(refreshLayout: RefreshLayout, success: Boolean): Int { 29 | return 0 30 | } 31 | 32 | override fun onInitialized(kernel: RefreshKernel, height: Int, maxDragHeight: Int) { 33 | 34 | } 35 | 36 | override fun onHorizontalDrag(percentX: Float, offsetX: Int, offsetMax: Int) { 37 | 38 | } 39 | 40 | override fun setNoMoreData(noMoreData: Boolean): Boolean { 41 | return noMoreData 42 | } 43 | 44 | override fun onReleased(refreshLayout: RefreshLayout, height: Int, maxDragHeight: Int) { 45 | 46 | } 47 | 48 | override fun getView(): View { 49 | return this 50 | } 51 | 52 | override fun setPrimaryColors(vararg colors: Int) { 53 | 54 | } 55 | 56 | override fun onStartAnimator(refreshLayout: RefreshLayout, height: Int, maxDragHeight: Int) { 57 | 58 | } 59 | 60 | override fun onStateChanged( 61 | refreshLayout: RefreshLayout, 62 | oldState: RefreshState, 63 | newState: RefreshState 64 | ) { 65 | 66 | } 67 | 68 | override fun onMoving( 69 | isDragging: Boolean, 70 | percent: Float, 71 | offset: Int, 72 | height: Int, 73 | maxDragHeight: Int 74 | ) { 75 | 76 | } 77 | 78 | override fun isSupportHorizontalDrag(): Boolean { 79 | return false 80 | } 81 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/widget/MsbRefreshHeader.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common.widget 2 | 3 | import android.content.Context 4 | import android.view.Gravity 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.widget.LinearLayout 8 | import com.castiel.common.R 9 | import com.scwang.smart.refresh.layout.api.RefreshHeader 10 | import com.scwang.smart.refresh.layout.api.RefreshKernel 11 | import com.scwang.smart.refresh.layout.api.RefreshLayout 12 | import com.scwang.smart.refresh.layout.constant.RefreshState 13 | import com.scwang.smart.refresh.layout.constant.SpinnerStyle 14 | 15 | class MsbRefreshHeader(context: Context) : LinearLayout(context), RefreshHeader { 16 | 17 | 18 | init { 19 | // setBackgroundColor(ContextCompat.getColor(context, R.color.white)) 20 | gravity = Gravity.CENTER_HORIZONTAL; 21 | addView(LayoutInflater.from(context).inflate(R.layout.layout_header_view, this, false)) 22 | } 23 | 24 | override fun getSpinnerStyle(): SpinnerStyle { 25 | return SpinnerStyle.Translate 26 | } 27 | 28 | override fun onFinish(refreshLayout: RefreshLayout, success: Boolean): Int { 29 | return 0 30 | } 31 | 32 | override fun onInitialized(kernel: RefreshKernel, height: Int, maxDragHeight: Int) { 33 | 34 | } 35 | 36 | override fun onHorizontalDrag(percentX: Float, offsetX: Int, offsetMax: Int) { 37 | 38 | } 39 | 40 | override fun onReleased(refreshLayout: RefreshLayout, height: Int, maxDragHeight: Int) { 41 | 42 | } 43 | 44 | override fun getView(): View { 45 | return this 46 | } 47 | 48 | override fun setPrimaryColors(vararg colors: Int) { 49 | 50 | } 51 | 52 | override fun onStartAnimator(refreshLayout: RefreshLayout, height: Int, maxDragHeight: Int) { 53 | 54 | } 55 | 56 | override fun onStateChanged( 57 | refreshLayout: RefreshLayout, 58 | oldState: RefreshState, 59 | newState: RefreshState 60 | ) { 61 | 62 | } 63 | 64 | override fun onMoving( 65 | isDragging: Boolean, 66 | percent: Float, 67 | offset: Int, 68 | height: Int, 69 | maxDragHeight: Int 70 | ) { 71 | 72 | } 73 | 74 | override fun isSupportHorizontalDrag(): Boolean { 75 | return false 76 | } 77 | } -------------------------------------------------------------------------------- /common/src/main/java/com/castiel/common/widget/StrokeTextView.java: -------------------------------------------------------------------------------- 1 | package com.castiel.common.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.text.TextPaint; 8 | import android.util.AttributeSet; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | 12 | import com.blankj.utilcode.util.ConvertUtils; 13 | 14 | 15 | /** 16 | * 中英文混排自动换行描边 描边 17 | * 需要设置width或者maxWidth才能做到适配 18 | * 19 | * @author jiayu.liu 20 | */ 21 | public class StrokeTextView extends MixedTextView { 22 | public TextView borderText; 23 | private final int mWidth; 24 | 25 | public StrokeTextView(Context context) { 26 | this(context, null); 27 | } 28 | 29 | public StrokeTextView(Context context, AttributeSet attrs) { 30 | this(context, attrs, 0); 31 | } 32 | 33 | public StrokeTextView(Context context, AttributeSet attrs, int defStyle) { 34 | super(context, attrs, defStyle); 35 | mWidth = ConvertUtils.dp2px(2); 36 | borderText = new TextView(context, attrs); 37 | init(); 38 | } 39 | 40 | public void init() { 41 | TextPaint tp1 = borderText.getPaint(); 42 | tp1.setStrokeWidth(mWidth); 43 | tp1.setStyle(Paint.Style.STROKE); 44 | borderText.setTextColor(Color.WHITE); 45 | borderText.setGravity(getGravity()); 46 | } 47 | 48 | @Override 49 | public void setLayoutParams(ViewGroup.LayoutParams params) { 50 | super.setLayoutParams(params); 51 | borderText.setLayoutParams(params); 52 | } 53 | 54 | 55 | @Override 56 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 57 | CharSequence tt = borderText.getText(); 58 | if (tt == null || !tt.equals(this.getText())) { 59 | borderText.setText(getText()); 60 | this.postInvalidate(); 61 | } 62 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 63 | borderText.measure(widthMeasureSpec, heightMeasureSpec); 64 | } 65 | 66 | 67 | @Override 68 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 69 | super.onLayout(changed, left, top, right, bottom); 70 | borderText.layout(left, top, right, bottom); 71 | } 72 | 73 | @Override 74 | protected void onDraw(Canvas canvas) { 75 | borderText.draw(canvas); 76 | super.onDraw(canvas); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /common/src/main/res/drawable-xxxhdpi/common_ic_appbar_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/common/src/main/res/drawable-xxxhdpi/common_ic_appbar_back.png -------------------------------------------------------------------------------- /common/src/main/res/drawable/button_select.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/shape_item_line_horizontall.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/shape_item_line_verticall.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/shape_round_1_red_50_line.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/shape_round_20_color_accent.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/shape_round_20_color_accent_50.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/shape_round_20_white_50.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/shape_round_20_white_80.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/shape_round_8_color_black_50.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/text_color_select.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /common/src/main/res/layout/dialog_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /common/src/main/res/layout/empty_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 16 | 17 | -------------------------------------------------------------------------------- /common/src/main/res/layout/error_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /common/src/main/res/layout/layout_appbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 39 | 40 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /common/src/main/res/layout/layout_footer_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /common/src/main/res/layout/layout_header_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /common/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /common/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1A1A2A 4 | #1A1A2A 5 | #FFBC00 6 | #80FFBC00 7 | 8 | 9 | 10 | 11 | #FFFFFF 12 | #80FFFFFF 13 | #CCFFFFFF 14 | #000000 15 | #1A000000 16 | #80000000 17 | #CC000000 18 | #333333 19 | #666666 20 | #999999 21 | #00000000 22 | #00FF00 23 | #8000FF00 24 | #FF0000 25 | #80FF0000 26 | -------------------------------------------------------------------------------- /common/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /common/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 网络异常,请稍后重试 4 | 作者: 5 | 时间: 6 | 分类: 7 | -------------------------------------------------------------------------------- /common/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /common/src/test/java/com/castiel/common/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.castiel.common 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /config.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | android = [ 3 | compileSdkVersion: 31, 4 | buildToolsVersion: '29.0.3', 5 | applicationId : 'com.huolala.mockgps', 6 | minSdkVersion : 21, 7 | targetSdkVersion : 31, 8 | versionCode : 255, 9 | versionName : '2.5.5', 10 | map_key : 'lmSE5lD0ZXcSAuNs34A2DXQdIzaDI8GR', 11 | ] 12 | 13 | support = [ 14 | core_ktx : 'androidx.core:core-ktx:1.7.0', 15 | appcompat : 'androidx.appcompat:appcompat:1.4.0', 16 | constraintlayout: 'androidx.constraintlayout:constraintlayout:2.1.3', 17 | junit : 'junit:junit:4.13.2', 18 | test_junit : 'androidx.test.ext:junit:1.1.1', 19 | espresso_core : 'androidx.test.espresso:espresso-core:3.2.0', 20 | material : 'com.google.android.material:material:1.5.0', 21 | ] 22 | 23 | dependencies = [ 24 | retrofit : 'com.squareup.retrofit2:retrofit:2.9.0', 25 | converter_gson : 'com.squareup.retrofit2:converter-gson:2.9.0', 26 | logging : 'com.squareup.okhttp3:logging-interceptor:4.7.2', 27 | arouter : 'com.alibaba:arouter-api:1.4.1', 28 | arouter_compiler : 'com.alibaba:arouter-compiler:1.2.2', 29 | kotlinx_coroutines_core: 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7', 30 | kotlin_reflect : 'org.jetbrains.kotlin:kotlin-reflect:1.6.0', 31 | lifecycle_viewmodel : 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0', 32 | lifecycle_runtime : 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0', 33 | lifecycle_compiler : 'androidx.lifecycle:lifecycle-compiler:2.2.0', 34 | banner : 'com.youth.banner:banner:2.1.0', 35 | glide : 'com.github.bumptech.glide:glide:4.11.0', 36 | glide_compiler : 'com.github.bumptech.glide:compiler:4.11.0', 37 | downloader : 'com.download.library:Downloader:4.1.4', 38 | agentweb : 'com.just.agentweb:agentweb:4.1.4', 39 | autosize : 'com.github.JessYanCoding:AndroidAutoSize:v1.2.1', 40 | lottie : 'com.airbnb.android:lottie:6.2.0', 41 | refresh_layout : 'com.scwang.smart:refresh-layout-kernel:2.0.2', 42 | chips_layout : 'com.beloo.widget:ChipsLayoutManager:0.3.7@aar', 43 | utilcodex : 'com.blankj:utilcodex:1.30.5', 44 | mmkv : 'com.tencent:mmkv-static:1.2.5', 45 | gif_drawable : 'pl.droidsonroids.gif:android-gif-drawable:1.2.+', 46 | bugly : 'com.tencent.bugly:crashreport:3.3.3', 47 | bubbleseekbar : 'com.xw.repo:bubbleseekbar:3.20-lite', 48 | ] 49 | } 50 | 51 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | android.injected.testOnly=false -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jan 19 16:23:06 CST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /img/download.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/img/download.jpeg -------------------------------------------------------------------------------- /img/floating_window.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/img/floating_window.jpeg -------------------------------------------------------------------------------- /img/floating_window_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/img/floating_window_location.png -------------------------------------------------------------------------------- /img/floating_window_navi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/img/floating_window_navi.png -------------------------------------------------------------------------------- /img/home_location.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/img/home_location.jpeg -------------------------------------------------------------------------------- /img/home_navi.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/img/home_navi.jpeg -------------------------------------------------------------------------------- /img/home_navi_multiple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/img/home_navi_multiple.png -------------------------------------------------------------------------------- /img/location_control_panel.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/img/location_control_panel.jpeg -------------------------------------------------------------------------------- /img/navi_control_panel.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/img/navi_control_panel.jpeg -------------------------------------------------------------------------------- /img/v2.0.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/img/v2.0.jpeg -------------------------------------------------------------------------------- /img/wx.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujiayu5566/MockGps/e415e78ed063b990bf313ada2f2d48fd6c0162e7/img/wx.jpeg -------------------------------------------------------------------------------- /pack.sh: -------------------------------------------------------------------------------- 1 | #当前包名&当前百度sdk key %不要修改 2 | cur_package="com.huolala.mockgps" 3 | cur_key="lmSE5lD0ZXcSAuNs34A2DXQdIzaDI8GR" 4 | 5 | #百度开发者平台申请key 需要的信息 开发版SHA1和发布版SHA1相同即可 6 | 发布版SHA1="7B:11:FA:A1:1E:F9:A8:85:76:D1:FD:79:D7:66:50:99:5E:3A:D4:0D" 7 | 8 | #替换包名&百度sdk key <***>随意修改 %需要上百度sdk开发者平台先申请key 9 | replace_package="com.***.***" 10 | replace_key="***" 11 | 12 | 13 | sed -i '' 's/'${cur_package}'/'${replace_package}'/g' config.gradle 14 | sed -i '' 's/'${cur_key}'/'${replace_key}'/g' config.gradle 15 | ./gradlew app:assembleRelease 16 | sed -i '' 's/'${replace_package}'/'${cur_package}'/g' config.gradle 17 | sed -i '' 's/'${replace_key}'/'${cur_key}'/g' config.gradle 18 | open app/build/outputs/apk/release/ 19 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 3 | repositories { 4 | maven { url 'https://maven.aliyun.com/repository/google' } 5 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } 6 | maven { url 'https://maven.aliyun.com/repository/public' } 7 | maven { url 'https://maven.aliyun.com/repository/jcenter' } 8 | mavenCentral() 9 | maven { url "https://jitpack.io" } 10 | } 11 | } 12 | rootProject.name = "MockGps" 13 | include ':app' 14 | include ':common' 15 | --------------------------------------------------------------------------------