├── .gitignore ├── Motion.apk ├── README.md ├── ScreenShot ├── apk_dl.png ├── splash.jpg ├── 开始运动.jpg ├── 运动-地图模式.png ├── 运动-地图模式_暂停.png ├── 运动-跑步模式.png ├── 运动倒计时.jpg ├── 运动结果.png ├── 运动记录.png └── 首页.jpg ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── james │ │ └── motion │ │ ├── MyApplication.java │ │ ├── commmon │ │ ├── bean │ │ │ ├── PathRecord.java │ │ │ ├── SportMotionRecord.java │ │ │ ├── TabEntity.java │ │ │ └── UserAccount.java │ │ └── utils │ │ │ ├── Conn.java │ │ │ ├── CountTimerUtil.java │ │ │ ├── DateUtils.java │ │ │ ├── DeviceHelper.java │ │ │ ├── Htmls.java │ │ │ ├── LogUtils.java │ │ │ ├── MySp.java │ │ │ ├── UIHelper.java │ │ │ ├── Utils.java │ │ │ └── Validator.java │ │ ├── db │ │ ├── DBHelper.java │ │ ├── DataManager.java │ │ └── RealmHelper.java │ │ ├── sport_motion │ │ ├── LocationService.java │ │ ├── MotionUtils.java │ │ ├── PathSmoothTool.java │ │ ├── TraceRePlay.java │ │ └── servicecode │ │ │ ├── RecordService.java │ │ │ └── impl │ │ │ └── RecordServiceImpl.java │ │ └── ui │ │ ├── BaseActivity.java │ │ ├── BaseFragment.java │ │ ├── activity │ │ ├── HomeActivity.java │ │ ├── LoginActivity.java │ │ ├── RegistActivity.java │ │ ├── SplashActivity.java │ │ ├── SportMapActivity.java │ │ ├── SportRecordDetailsActivity.java │ │ ├── SportResultActivity.java │ │ └── SportsActivity.java │ │ ├── adapter │ │ └── SportCalendarAdapter.java │ │ ├── fragment │ │ ├── FastLoginFragment.java │ │ ├── PsdLoginFragment.java │ │ ├── SportRecordDetailsFragment.java │ │ ├── SportRecordDetailsMapFragment.java │ │ └── SportRecordDetailsSpeedFragment.java │ │ ├── permission │ │ ├── APermission.java │ │ ├── PermissionActivity.java │ │ ├── PermissionHelper.java │ │ ├── PermissionListener.java │ │ ├── PermissionRetryCallBack.java │ │ ├── PermissionUtil.java │ │ └── Permissions.java │ │ └── weight │ │ ├── AMapScrollViewPager.java │ │ ├── CountDownProgressView.java │ │ ├── CustomPopWindow.java │ │ ├── CustomProgressDialog.java │ │ └── calendarview │ │ └── custom │ │ ├── CustomMonthView.java │ │ ├── CustomWeekBar.java │ │ ├── CustomWeekView.java │ │ └── CustomYearView.java │ └── res │ ├── anim │ ├── slide_left_in.xml │ ├── slide_left_out.xml │ ├── slide_right_in.xml │ └── slide_right_out.xml │ ├── drawable-hdpi │ └── mylocation_point.png │ ├── drawable-v21 │ └── bg_ripple.xml │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── back_selector.xml │ ├── back_selector_n.xml │ ├── back_selector_p.xml │ ├── bg_edittext.xml │ ├── btn_gray_bg.xml │ ├── btn_green_box_selector.xml │ ├── btn_green_box_selector_n.xml │ ├── btn_green_box_selector_p.xml │ ├── btn_green_selector.xml │ ├── btn_green_selector_n.xml │ ├── btn_green_selector_p.xml │ ├── btn_nor_bg.xml │ ├── btn_white_selector.xml │ ├── btn_white_selector_n.xml │ ├── btn_white_selector_p.xml │ ├── et_coner_box_bg.xml │ ├── et_coner_white_bg.xml │ ├── et_underline.xml │ ├── green_round.xml │ ├── ic_launcher_background.xml │ ├── layer_splash.xml │ ├── loadind_dialog_background.xml │ ├── sport_change_bg.xml │ ├── sport_end.png │ ├── sport_start.png │ ├── sport_walk.png │ └── sport_white_round.xml │ ├── layout │ ├── activity_home.xml │ ├── activity_login.xml │ ├── activity_regist.xml │ ├── activity_splash.xml │ ├── activity_sportmap.xml │ ├── activity_sportrecorddetails.xml │ ├── activity_sportresult.xml │ ├── activity_sports.xml │ ├── adapter_sportcalendar.xml │ ├── custom_progress_dialog_layout.xml │ ├── custom_week_bar.xml │ ├── fragment_fastlogin.xml │ ├── fragment_psdlogin.xml │ ├── fragment_sportrecorddetails.xml │ ├── fragment_sportrecorddetailsmap.xml │ ├── fragment_sportrecorddetailsspeed.xml │ ├── layout_sport_result_tip.xml │ ├── layout_title.xml │ └── tip_dialog_layout.xml │ ├── mipmap-xhdpi │ ├── app_icon.png │ ├── big_no_star.png │ ├── big_star.png │ ├── gps_point.png │ ├── icon_account.png │ ├── icon_back_white.png │ ├── icon_code.png │ ├── icon_code_white.png │ ├── icon_et_del.png │ ├── icon_help.png │ ├── icon_phone_white.png │ ├── icon_psd.png │ ├── icon_psd_ck_white.png │ ├── icon_psd_h.png │ ├── icon_psd_s.png │ ├── icon_psd_white.png │ ├── icon_qq.png │ ├── icon_sport.png │ ├── icon_tip.png │ ├── icon_user.png │ ├── icon_wc.png │ ├── map_mode.png │ ├── run_mode.png │ ├── small_no_star.png │ ├── small_star.png │ ├── sport_bg.png │ ├── sport_mark.png │ └── star_bg.png │ ├── mipmap-xxhdpi │ ├── login_bg.png │ ├── splash_bg.png │ └── start.png │ ├── mipmap-xxxhdpi │ └── ic_calendar.png │ ├── values-v19 │ └── dimens.xml │ ├── values-v21 │ └── styles.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── layout_styles.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── config.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | -------------------------------------------------------------------------------- /Motion.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aqx1991/Motion/0368b18db1b63425a9139e89320075100ac7cc59/Motion.apk -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Motion 2 | 3 | ## Android端记录跑步运动轨迹数据的App 4 | 5 | 本项目功能实现了跑步运动轨迹、运动数据(运动日期记录、单个日期运动次数、每次运动的基本数据(运动距离、时长、消耗、速度等))。地图及运动轨迹展示使用的高德地图(APPKey需要替换为自己创建的,可参考高德官方文档进行改进);运动相关数据使用Realm数据库保存,登录注册功能也是由本地Realm数据库模拟完成;运动日历展示使用开源框架,可根据需求自行修改。 6 | 7 | 运动轨迹处理修改优化可参考高德官方文档 : https://lbs.amap.com/dev/demo/path-record#Android 8 | 9 | 界面参考自IOS开源项目:YSRun(项目地址:https://github.com/moshuqi/YSRun ) 10 | 11 | 代码简易,欢迎来指点交流!觉得还可以,给个Star^_^ 12 | 13 | ### 扫码下载APK 14 | 15 | 16 | ## 更新 17 | ### 更新: 18 | * 1.修改定位方式,解决坐标偏移问题; 19 | * 2.修改轨迹绘制方式,轨迹平滑优化; 20 | * 3.修改轨迹数据格式,存取数据优化。 21 | 22 | ## 界面预览 23 | ### 首页运动日历 24 | 25 | 26 | ### 开始运动 27 | 28 | 29 | ### 运动倒计时 30 | 31 | 32 | ### 地图模式 33 | 34 | 35 | ### 地图模式-暂停 36 | 37 | 38 | ### 跑步模式 39 | 40 | 41 | ### 运动结果 42 | 43 | 44 | ### 运动记录 45 | 46 | 47 | -------------------------------------------------------------------------------- /ScreenShot/apk_dl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aqx1991/Motion/0368b18db1b63425a9139e89320075100ac7cc59/ScreenShot/apk_dl.png -------------------------------------------------------------------------------- /ScreenShot/splash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aqx1991/Motion/0368b18db1b63425a9139e89320075100ac7cc59/ScreenShot/splash.jpg -------------------------------------------------------------------------------- /ScreenShot/开始运动.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aqx1991/Motion/0368b18db1b63425a9139e89320075100ac7cc59/ScreenShot/开始运动.jpg -------------------------------------------------------------------------------- /ScreenShot/运动-地图模式.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aqx1991/Motion/0368b18db1b63425a9139e89320075100ac7cc59/ScreenShot/运动-地图模式.png -------------------------------------------------------------------------------- /ScreenShot/运动-地图模式_暂停.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aqx1991/Motion/0368b18db1b63425a9139e89320075100ac7cc59/ScreenShot/运动-地图模式_暂停.png -------------------------------------------------------------------------------- /ScreenShot/运动-跑步模式.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aqx1991/Motion/0368b18db1b63425a9139e89320075100ac7cc59/ScreenShot/运动-跑步模式.png -------------------------------------------------------------------------------- /ScreenShot/运动倒计时.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aqx1991/Motion/0368b18db1b63425a9139e89320075100ac7cc59/ScreenShot/运动倒计时.jpg -------------------------------------------------------------------------------- /ScreenShot/运动结果.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aqx1991/Motion/0368b18db1b63425a9139e89320075100ac7cc59/ScreenShot/运动结果.png -------------------------------------------------------------------------------- /ScreenShot/运动记录.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aqx1991/Motion/0368b18db1b63425a9139e89320075100ac7cc59/ScreenShot/运动记录.png -------------------------------------------------------------------------------- /ScreenShot/首页.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aqx1991/Motion/0368b18db1b63425a9139e89320075100ac7cc59/ScreenShot/首页.jpg -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'realm-android' 3 | 4 | android { 5 | 6 | compileSdkVersion rootProject.ext.android.compileSdkVersion 7 | buildToolsVersion rootProject.ext.android.buildToolsVersion 8 | 9 | compileOptions { 10 | sourceCompatibility rootProject.ext.android.java_version 11 | targetCompatibility rootProject.ext.android.java_version 12 | } 13 | 14 | defaultConfig { 15 | applicationId rootProject.ext.android.applicationId 16 | minSdkVersion rootProject.ext.android.minSdkVersion 17 | targetSdkVersion rootProject.ext.android.targetSdkVersion 18 | versionCode rootProject.ext.android.versionCode 19 | versionName rootProject.ext.android.versionName 20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 21 | 22 | // dex突破65535的限制 23 | multiDexEnabled true 24 | 25 | ndk { 26 | //设置支持的SO库架构(开发者可以根据需要,选择一个或多个平台的so) 27 | abiFilters 'armeabi', 'armeabi-v7a', 'x86' 28 | // 还可以添加 , 'x86_64', 'mips', 'mips64', 'arm64-v8a', "arm64-v8a" 29 | } 30 | 31 | } 32 | buildTypes { 33 | release { 34 | minifyEnabled true 35 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 36 | buildConfigField "boolean", "DEBUG_MODE", "false" 37 | } 38 | debug { 39 | minifyEnabled false 40 | // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 41 | buildConfigField "boolean", "DEBUG_MODE", "true" 42 | } 43 | } 44 | 45 | dexOptions { 46 | javaMaxHeapSize "4g" 47 | } 48 | 49 | sourceSets { 50 | main { 51 | jniLibs.srcDirs = ['libs'] 52 | } 53 | } 54 | } 55 | 56 | dependencies { 57 | implementation fileTree(dir: 'libs', include: ['*.jar']) 58 | implementation rootProject.ext.support["appcompat"] 59 | implementation rootProject.ext.support["design"] 60 | implementation rootProject.ext.support["recyclerview"] 61 | implementation rootProject.ext.support["cardview"] 62 | implementation rootProject.ext.support["layout"] 63 | testImplementation rootProject.ext.support["testImplementation"] 64 | androidTestImplementation rootProject.ext.support["androidtestimplementation_ext"] 65 | androidTestImplementation rootProject.ext.support["androidtestimplementation_espresso"] 66 | 67 | //日历控件 68 | implementation rootProject.ext.dependencies["calendarview"] 69 | 70 | //butterknife 71 | implementation rootProject.ext.dependencies["butterknife_api"] 72 | annotationProcessor rootProject.ext.dependencies["annotationProcessor"] 73 | 74 | // RecycleVierw-Adapter 75 | implementation rootProject.ext.dependencies["recyclevierw_adapter"] 76 | 77 | //自定义TabLayout 78 | implementation rootProject.ext.dependencies["flycotablayout_lib"] 79 | 80 | //沉浸式 81 | implementation rootProject.ext.dependencies["immersionbar"] 82 | implementation rootProject.ext.dependencies["immersionbar_components"] 83 | 84 | //gson 85 | implementation rootProject.ext.dependencies["gson"] 86 | 87 | //utilcode 88 | implementation(rootProject.ext.dependencies["utilcode"]) { 89 | exclude group: 'com.blankj', module: 'bus' 90 | } 91 | 92 | //3D地图 93 | implementation rootProject.ext.dependencies["Map_3D"] 94 | //搜索功能 95 | implementation rootProject.ext.dependencies["Map_search"] 96 | //定位SDK 97 | implementation rootProject.ext.dependencies["Map_location"] 98 | 99 | // 内存泄漏检测 100 | debugImplementation rootProject.ext.dependencies["leakcanary_debug"] 101 | releaseImplementation rootProject.ext.dependencies["leakcanary_release"] 102 | // Optional, if you use support library fragments: 103 | debugImplementation rootProject.ext.dependencies["leakcanary_debug_fragmen"] 104 | } 105 | -------------------------------------------------------------------------------- /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 22 | 23 | #-------------------------------------------定制化区域---------------------------------------------- 24 | #---------------------------------1.实体类--------------------------------- 25 | 26 | -keep class com.james.motion.** { *; } 27 | -ignorewarnings 28 | #------------------------------------------------------------------------- 29 | 30 | #---------------------------------2.第三方包------------------------------- 31 | 32 | #高德地图-start_bg 33 | #3D 34 | -keep class com.amap.api.maps.**{*;} 35 | -keep class com.autonavi.**{*;} 36 | -keep class com.amap.api.trace.**{*;} 37 | #定位 38 | -keep class com.amap.api.location.**{*;} 39 | -keep class com.amap.api.fence.**{*;} 40 | -keep class com.autonavi.aps.amapapi.model.**{*;} 41 | #搜索 42 | -keep class com.amap.api.services.**{*;} 43 | #高德地图-end 44 | 45 | #BaseQuickAdapter-start_bg 46 | -keep class com.chad.library.adapter.** { 47 | *; 48 | } 49 | -keep public class * extends com.chad.library.adapter.base.BaseQuickAdapter 50 | -keep public class * extends com.chad.library.adapter.base.BaseViewHolder 51 | -keepclassmembers class **$** extends com.chad.library.adapter.base.BaseViewHolder { 52 | (...); 53 | } 54 | #BaseQuickAdapter-end 55 | 56 | #butterknife 57 | -keep class butterknife.** { *; } 58 | -dontwarn butterknife.internal.** 59 | -keep class **$$ViewBinder { *; } 60 | -keepclasseswithmembernames class * { 61 | @butterknife.* ; 62 | } 63 | -keepclasseswithmembernames class * { 64 | @butterknife.* ; 65 | } 66 | 67 | #------------------------------------------------------------------------- 68 | 69 | #---------------------------------3.与js互相调用的类------------------------ 70 | 71 | 72 | #------------------------------------------------------------------------- 73 | 74 | #---------------------------------4.反射相关的类和方法----------------------- 75 | 76 | 77 | #---------------------------------------------------------------------------- 78 | #--------------------------------------------------------------------------------------------------- 79 | 80 | #-------------------------------------------基本不用动区域-------------------------------------------- 81 | #---------------------------------基本指令区---------------------------------- 82 | -optimizationpasses 5 83 | -dontusemixedcaseclassnames 84 | -dontskipnonpubliclibraryclasses 85 | -dontskipnonpubliclibraryclassmembers 86 | #-dontpreverify 87 | -verbose 88 | -printmapping proguardMapping.txt 89 | -optimizations !code/simplification/cast,!field/*,!class/merging/* 90 | -keepattributes *Annotation*,InnerClasses 91 | -keepattributes Signature 92 | -keepattributes SourceFile,LineNumberTable 93 | #---------------------------------------------------------------------------- 94 | 95 | #---------------------------------默认保留区--------------------------------- 96 | -keep public class * extends android.app.Activity 97 | -keep public class * extends android.app.Application 98 | -keep public class * extends android.app.Service 99 | -keep public class * extends android.content.BroadcastReceiver 100 | -keep public class * extends android.content.ContentProvider 101 | -keep public class * extends android.app.backup.BackupAgentHelper 102 | -keep public class * extends android.preference.Preference 103 | -keep public class * extends android.view.View 104 | -keep public class com.android.vending.licensing.ILicensingService 105 | -keep class android.support.** {*;} 106 | 107 | -keep class com.google.android.material.** {*;} 108 | -keep class androidx.** {*;} 109 | -keep public class * extends androidx.** 110 | -keep interface androidx.** {*;} 111 | -dontwarn com.google.android.material.** 112 | -dontnote com.google.android.material.** 113 | -dontwarn androidx.** 114 | 115 | -keepclasseswithmembernames class * { 116 | native ; 117 | } 118 | -keepclassmembers class * extends android.app.Activity{ 119 | public void *(android.view.View); 120 | } 121 | -keepclassmembers enum * { 122 | public static **[] values(); 123 | public static ** valueOf(java.lang.String); 124 | } 125 | -keep public class * extends android.view.View{ 126 | *** get*(); 127 | void set*(***); 128 | public (android.content.Context); 129 | public (android.content.Context, android.util.AttributeSet); 130 | public (android.content.Context, android.util.AttributeSet, int); 131 | } 132 | -keepclasseswithmembers class * { 133 | public (android.content.Context, android.util.AttributeSet); 134 | public (android.content.Context, android.util.AttributeSet, int); 135 | } 136 | -keep class * implements android.os.Parcelable { 137 | public static final android.os.Parcelable$Creator *; 138 | } 139 | -keepclassmembers class * implements java.io.Serializable { 140 | static final long serialVersionUID; 141 | private static final java.io.ObjectStreamField[] serialPersistentFields; 142 | private void writeObject(java.io.ObjectOutputStream); 143 | private void readObject(java.io.ObjectInputStream); 144 | java.lang.Object writeReplace(); 145 | java.lang.Object readResolve(); 146 | } 147 | -keep class **.R$* { 148 | *; 149 | } 150 | -keepclassmembers class * { 151 | void *(**On*Event); 152 | } 153 | 154 | #---------------------------------------------------------------------------- 155 | #---------------------------------webview------------------------------------ 156 | 157 | #---------------------------------------------------------------------------- 158 | #--------------------------------------------------------------------------------------------------- 159 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/commmon/bean/PathRecord.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.commmon.bean; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import com.amap.api.location.AMapLocation; 7 | import com.amap.api.maps.model.LatLng; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * 用于记录一条轨迹,包括起点、终点、轨迹中间点、距离、耗时、时间 14 | */ 15 | public class PathRecord implements Parcelable { 16 | 17 | //主键 18 | private Long id; 19 | //运动开始点 20 | private LatLng mStartPoint; 21 | //运动结束点 22 | private LatLng mEndPoint; 23 | //运动轨迹 24 | private List mPathLinePoints = new ArrayList<>(); 25 | //运动距离 26 | private Double mDistance; 27 | //运动时长 28 | private Long mDuration; 29 | //运动开始时间 30 | private Long mStartTime; 31 | //运动结束时间 32 | private Long mEndTime; 33 | //消耗卡路里 34 | private Double mCalorie; 35 | //平均时速(公里/小时) 36 | private Double mSpeed; 37 | //平均配速(分钟/公里) 38 | private Double mDistribution; 39 | //日期标记 40 | private String mDateTag; 41 | 42 | public PathRecord() { 43 | 44 | } 45 | 46 | public Long getId() { 47 | return id; 48 | } 49 | 50 | public void setId(Long id) { 51 | this.id = id; 52 | } 53 | 54 | public LatLng getStartpoint() { 55 | return mStartPoint; 56 | } 57 | 58 | public void setStartpoint(LatLng startpoint) { 59 | this.mStartPoint = startpoint; 60 | } 61 | 62 | public LatLng getEndpoint() { 63 | return mEndPoint; 64 | } 65 | 66 | public void setEndpoint(LatLng endpoint) { 67 | this.mEndPoint = endpoint; 68 | } 69 | 70 | public List getPathline() { 71 | return mPathLinePoints; 72 | } 73 | 74 | public void setPathline(List pathline) { 75 | this.mPathLinePoints = pathline; 76 | } 77 | 78 | public Double getDistance() { 79 | return mDistance; 80 | } 81 | 82 | public void setDistance(Double distance) { 83 | this.mDistance = distance; 84 | } 85 | 86 | public Long getDuration() { 87 | return mDuration; 88 | } 89 | 90 | public void setDuration(Long duration) { 91 | this.mDuration = duration; 92 | } 93 | 94 | public Long getStartTime() { 95 | return mStartTime; 96 | } 97 | 98 | public void setStartTime(Long mStartTime) { 99 | this.mStartTime = mStartTime; 100 | } 101 | 102 | public Long getEndTime() { 103 | return mEndTime; 104 | } 105 | 106 | public void setEndTime(Long mEndTime) { 107 | this.mEndTime = mEndTime; 108 | } 109 | 110 | public void addpoint(LatLng point) { 111 | mPathLinePoints.add(point); 112 | } 113 | 114 | public Double getCalorie() { 115 | return mCalorie; 116 | } 117 | 118 | public void setCalorie(Double mCalorie) { 119 | this.mCalorie = mCalorie; 120 | } 121 | 122 | public Double getSpeed() { 123 | return mSpeed; 124 | } 125 | 126 | public void setSpeed(Double mSpeed) { 127 | this.mSpeed = mSpeed; 128 | } 129 | 130 | public Double getDistribution() { 131 | return mDistribution; 132 | } 133 | 134 | public void setDistribution(Double mDistribution) { 135 | this.mDistribution = mDistribution; 136 | } 137 | 138 | public String getDateTag() { 139 | return mDateTag; 140 | } 141 | 142 | public void setDateTag(String mDateTag) { 143 | this.mDateTag = mDateTag; 144 | } 145 | 146 | @Override 147 | public String toString() { 148 | StringBuilder record = new StringBuilder(); 149 | record.append("recordSize:" + getPathline().size() + ", "); 150 | record.append("distance:" + getDistance() + "m, "); 151 | record.append("duration:" + getDuration() + "s"); 152 | return record.toString(); 153 | } 154 | 155 | @Override 156 | public int describeContents() { 157 | return 0; 158 | } 159 | 160 | @Override 161 | public void writeToParcel(Parcel dest, int flags) { 162 | dest.writeValue(this.id); 163 | dest.writeParcelable(this.mStartPoint, flags); 164 | dest.writeParcelable(this.mEndPoint, flags); 165 | dest.writeTypedList(this.mPathLinePoints); 166 | dest.writeValue(this.mDistance); 167 | dest.writeValue(this.mDuration); 168 | dest.writeValue(this.mStartTime); 169 | dest.writeValue(this.mEndTime); 170 | dest.writeValue(this.mCalorie); 171 | dest.writeValue(this.mSpeed); 172 | dest.writeValue(this.mDistribution); 173 | dest.writeString(this.mDateTag); 174 | } 175 | 176 | protected PathRecord(Parcel in) { 177 | this.id = (Long) in.readValue(Long.class.getClassLoader()); 178 | this.mStartPoint = in.readParcelable(AMapLocation.class.getClassLoader()); 179 | this.mEndPoint = in.readParcelable(AMapLocation.class.getClassLoader()); 180 | this.mPathLinePoints = in.createTypedArrayList(LatLng.CREATOR); 181 | this.mDistance = (Double) in.readValue(Double.class.getClassLoader()); 182 | this.mDuration = (Long) in.readValue(Long.class.getClassLoader()); 183 | this.mStartTime = (Long) in.readValue(Long.class.getClassLoader()); 184 | this.mEndTime = (Long) in.readValue(Long.class.getClassLoader()); 185 | this.mCalorie = (Double) in.readValue(Double.class.getClassLoader()); 186 | this.mSpeed = (Double) in.readValue(Double.class.getClassLoader()); 187 | this.mDistribution = (Double) in.readValue(Double.class.getClassLoader()); 188 | this.mDateTag = in.readString(); 189 | } 190 | 191 | public static final Creator CREATOR = new Creator() { 192 | @Override 193 | public PathRecord createFromParcel(Parcel source) { 194 | return new PathRecord(source); 195 | } 196 | 197 | @Override 198 | public PathRecord[] newArray(int size) { 199 | return new PathRecord[size]; 200 | } 201 | }; 202 | } -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/commmon/bean/SportMotionRecord.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.commmon.bean; 2 | 3 | import java.io.Serializable; 4 | 5 | import io.realm.RealmObject; 6 | import io.realm.annotations.PrimaryKey; 7 | import io.realm.annotations.Required; 8 | 9 | /** 10 | * 描述: 用于记录一条轨迹,包括起点、终点、轨迹中间点、距离、耗时、时间 11 | * 作者: james 12 | * 日期: 2019/2/25 19:59 13 | * 类名: SportMotionRecord 14 | */ 15 | public class SportMotionRecord extends RealmObject implements Serializable { 16 | 17 | /** 18 | * 表示该字段是主键 19 | *

20 | * 字段类型必须是字符串(String)或整数(byte,short,int或long) 21 | * 以及它们的包装类型(Byte,Short, Integer, 或 Long)。不可以存在多个主键, 22 | * 使用字符串字段作为主键意味着字段被索引(注释@PrimaryKey隐式地设置注释@Index)。 23 | */ 24 | @PrimaryKey 25 | private Long id; 26 | 27 | //登录者ID 28 | // @Required 29 | private int master; 30 | 31 | //运动距离 32 | @Required 33 | private Double distance; 34 | //运动时长 35 | @Required 36 | private Long duration; 37 | //运动轨迹 38 | @Required 39 | private String pathLine; 40 | //运动开始点 41 | @Required 42 | private String stratPoint; 43 | //运动结束点 44 | @Required 45 | private String endPoint; 46 | //运动开始时间 47 | @Required 48 | private Long mStartTime; 49 | //运动结束时间 50 | @Required 51 | private Long mEndTime; 52 | //消耗卡路里 53 | @Required 54 | private Double calorie; 55 | //平均时速(公里/小时) 56 | @Required 57 | private Double speed; 58 | //平均配速(分钟/公里) 59 | @Required 60 | private Double distribution; 61 | //日期标记 62 | @Required 63 | private String dateTag; 64 | 65 | private String str1;//预留字段 66 | private String str2;//预留字段 67 | private String str3;//预留字段 68 | 69 | public Long getId() { 70 | return id; 71 | } 72 | 73 | public void setId(Long id) { 74 | this.id = id; 75 | } 76 | 77 | public int getMaster() { 78 | return master; 79 | } 80 | 81 | public void setMaster(int master) { 82 | this.master = master; 83 | } 84 | 85 | public Double getDistance() { 86 | return distance; 87 | } 88 | 89 | public void setDistance(Double distance) { 90 | this.distance = distance; 91 | } 92 | 93 | public Long getDuration() { 94 | return duration; 95 | } 96 | 97 | public void setDuration(Long duration) { 98 | this.duration = duration; 99 | } 100 | 101 | public String getPathLine() { 102 | return pathLine; 103 | } 104 | 105 | public void setPathLine(String pathLine) { 106 | this.pathLine = pathLine; 107 | } 108 | 109 | public String getStratPoint() { 110 | return stratPoint; 111 | } 112 | 113 | public void setStratPoint(String stratPoint) { 114 | this.stratPoint = stratPoint; 115 | } 116 | 117 | public String getEndPoint() { 118 | return endPoint; 119 | } 120 | 121 | public void setEndPoint(String endPoint) { 122 | this.endPoint = endPoint; 123 | } 124 | 125 | public Long getmStartTime() { 126 | return mStartTime; 127 | } 128 | 129 | public void setmStartTime(Long mStartTime) { 130 | this.mStartTime = mStartTime; 131 | } 132 | 133 | public Long getmEndTime() { 134 | return mEndTime; 135 | } 136 | 137 | public void setmEndTime(Long mEndTime) { 138 | this.mEndTime = mEndTime; 139 | } 140 | 141 | public Double getCalorie() { 142 | return calorie; 143 | } 144 | 145 | public void setCalorie(Double calorie) { 146 | this.calorie = calorie; 147 | } 148 | 149 | public Double getSpeed() { 150 | return speed; 151 | } 152 | 153 | public void setSpeed(Double speed) { 154 | this.speed = speed; 155 | } 156 | 157 | public Double getDistribution() { 158 | return distribution; 159 | } 160 | 161 | public void setDistribution(Double distribution) { 162 | this.distribution = distribution; 163 | } 164 | 165 | public String getDateTag() { 166 | return dateTag; 167 | } 168 | 169 | public void setDateTag(String dateTag) { 170 | this.dateTag = dateTag; 171 | } 172 | 173 | public String getStr1() { 174 | return str1; 175 | } 176 | 177 | public void setStr1(String str1) { 178 | this.str1 = str1; 179 | } 180 | 181 | public String getStr2() { 182 | return str2; 183 | } 184 | 185 | public void setStr2(String str2) { 186 | this.str2 = str2; 187 | } 188 | 189 | public String getStr3() { 190 | return str3; 191 | } 192 | 193 | public void setStr3(String str3) { 194 | this.str3 = str3; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/commmon/bean/TabEntity.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.commmon.bean; 2 | 3 | import com.flyco.tablayout.listener.CustomTabEntity; 4 | 5 | public class TabEntity implements CustomTabEntity { 6 | public String title; 7 | public int selectedIcon; 8 | public int unSelectedIcon; 9 | 10 | public TabEntity(String title, int selectedIcon, int unSelectedIcon) { 11 | this.title = title; 12 | this.selectedIcon = selectedIcon; 13 | this.unSelectedIcon = unSelectedIcon; 14 | } 15 | 16 | @Override 17 | public String getTabTitle() { 18 | return title; 19 | } 20 | 21 | @Override 22 | public int getTabSelectedIcon() { 23 | return selectedIcon; 24 | } 25 | 26 | @Override 27 | public int getTabUnselectedIcon() { 28 | return unSelectedIcon; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/commmon/bean/UserAccount.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.commmon.bean; 2 | 3 | import java.io.Serializable; 4 | 5 | import io.realm.RealmObject; 6 | import io.realm.annotations.PrimaryKey; 7 | import io.realm.annotations.Required; 8 | 9 | /** 10 | * 描述: 账号密码 11 | * 作者: james 12 | * 日期: 2019/2/25 19:59 13 | * 类名: UserAccount 14 | */ 15 | public class UserAccount extends RealmObject implements Serializable { 16 | 17 | @PrimaryKey 18 | private Long id; 19 | 20 | //用户名 21 | @Required 22 | private String account; 23 | 24 | //密码 25 | @Required 26 | private String psd; 27 | 28 | public Long getId() { 29 | return id; 30 | } 31 | 32 | public void setId(Long id) { 33 | this.id = id; 34 | } 35 | 36 | public String getAccount() { 37 | return account; 38 | } 39 | 40 | public void setAccount(String account) { 41 | this.account = account; 42 | } 43 | 44 | public String getPsd() { 45 | return psd; 46 | } 47 | 48 | public void setPsd(String psd) { 49 | this.psd = psd; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/commmon/utils/Conn.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.commmon.utils; 2 | 3 | public class Conn { 4 | 5 | public static int Delayed = 1500;//延迟 6 | 7 | public static String sdCardPathDown = "";//数据保存路径 8 | 9 | public static final String PagerSize = "12";// 分页加载数据的每页数据量 10 | 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/commmon/utils/CountTimerUtil.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.commmon.utils; 2 | 3 | import android.view.View; 4 | import android.view.animation.AlphaAnimation; 5 | import android.view.animation.Animation; 6 | import android.view.animation.AnimationSet; 7 | import android.view.animation.ScaleAnimation; 8 | import android.widget.TextView; 9 | 10 | /** 11 | * 描述: 倒计时 12 | * 作者: james 13 | * 日期: 2019/2/18 19:34 14 | * 类名: CountTimerUtil 15 | */ 16 | public class CountTimerUtil { 17 | 18 | // 默认计时 19 | private static final int DEFAULT_REPEAT_COUNT = 4; 20 | // 最后一秒显示的文本 21 | private static final String LAST_SECOND_TEXT = "Go"; 22 | 23 | // 当前的计时 24 | private static int sCurCount = DEFAULT_REPEAT_COUNT; 25 | 26 | public static void start(T animationViewTv, AnimationState animationState) { 27 | start(animationViewTv, DEFAULT_REPEAT_COUNT, animationState); 28 | } 29 | 30 | /** 31 | * @param animationViewTv 要被倒计时的控件 32 | * @param repeatCount 要倒计时多久,单位,秒。 33 | */ 34 | private static void start(final T animationViewTv, final int repeatCount, AnimationState animationState) { 35 | 36 | // 设置计时 37 | sCurCount = repeatCount - 1; 38 | animationViewTv.setText(String.valueOf(sCurCount)); 39 | animationViewTv.setVisibility(View.VISIBLE); 40 | 41 | // 透明度渐变动画 42 | AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0); 43 | // 缩放渐变动画 44 | ScaleAnimation scaleAnimation = new ScaleAnimation( 45 | 0.1f, 1.3f, 0.1f, 1.3f, 46 | Animation.RELATIVE_TO_SELF, 0.5f, 47 | Animation.RELATIVE_TO_SELF, 0.5f); 48 | 49 | scaleAnimation.setRepeatCount(sCurCount); 50 | alphaAnimation.setRepeatCount(sCurCount); 51 | alphaAnimation.setDuration(1000); 52 | scaleAnimation.setDuration(1000); 53 | 54 | // scaleAnimation.setRepeatCount(1); //设置重复次数 55 | // alphaAnimation.setRepeatMode(Animation.REVERSE); //设置重复模式为倒转,即先从小变大再变小 56 | 57 | scaleAnimation.setAnimationListener(new Animation.AnimationListener() { 58 | @Override 59 | public void onAnimationStart(Animation animation) { 60 | animationState.start(); 61 | } 62 | 63 | @Override 64 | public void onAnimationEnd(Animation animation) { 65 | // 动画结束时,隐藏 66 | animationViewTv.setVisibility(View.GONE); 67 | animationState.end(); 68 | } 69 | 70 | @Override 71 | public void onAnimationRepeat(Animation animation) { 72 | // 减秒 73 | --sCurCount; 74 | // 设置文本 75 | if (sCurCount == 0) animationViewTv.setText(LAST_SECOND_TEXT); 76 | else animationViewTv.setText(String.valueOf(sCurCount)); 77 | 78 | animationState.repeat(); 79 | } 80 | }); 81 | 82 | // 两个动画同时播放 83 | AnimationSet animationSet = new AnimationSet(true); 84 | animationSet.addAnimation(alphaAnimation); 85 | animationSet.addAnimation(scaleAnimation); 86 | animationViewTv.startAnimation(animationSet); 87 | } 88 | 89 | public interface AnimationState { 90 | void start(); 91 | 92 | void repeat(); 93 | 94 | void end(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/commmon/utils/DateUtils.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.commmon.utils; 2 | 3 | import com.james.motion.R; 4 | 5 | import java.text.SimpleDateFormat; 6 | import java.util.Date; 7 | 8 | /** 9 | * 描述: 时间格式工具类 10 | * 作者: james 11 | * 日期: 2019/2/27 14:34 12 | * 类名: DateUtils 13 | */ 14 | public class DateUtils { 15 | 16 | /** 17 | * 短日期补位 18 | * 19 | * @param year 年 20 | * @param month 月 21 | * @param day 日 22 | * @return 返回短时间字符串格式yyyy-MM-dd 23 | */ 24 | public static String formatStringDateShort(int year, int month, int day) { 25 | return UIHelper.getString(R.string.date_year_month_day, String.valueOf(year), 26 | month < 10 ? "0" + month : String.valueOf(month), 27 | day < 10 ? "0" + day : String.valueOf(day)); 28 | } 29 | 30 | /** 31 | * 获取现在时间 32 | * 33 | * @return 返回短时间字符串格式yyyy-MM-dd 34 | */ 35 | public static String getStringDateShort(long time) { 36 | Date currentTime = new Date(time); 37 | SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); 38 | String dateString = formatter.format(currentTime); 39 | return dateString; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/commmon/utils/Htmls.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.commmon.utils; 2 | 3 | /** 4 | * 用途:html 标签关键字 5 | */ 6 | public interface Htmls { 7 | 8 | /** 9 | * 换行符号 10 | */ 11 | String br = "
"; 12 | /** 13 | * 空格 14 | */ 15 | String space = " "; 16 | 17 | /** 18 | * 颜色 19 | */ 20 | String color = "%s"; 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/commmon/utils/MySp.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.commmon.utils; 2 | 3 | public class MySp { 4 | 5 | public static final String ISLOGIN = "isloign";//是否登录 6 | 7 | public static final String USERID = "userid";//用户ID 8 | 9 | public static final String PHONE = "phone";//手机 10 | public static final String PASSWORD = "password";//密码 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/commmon/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.commmon.utils; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.james.motion.MyApplication; 6 | 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * 描述: 工具类 12 | * 作者: james 13 | * 日期: 2018-09-18 15:03 14 | * 类名: TabUtils 15 | */ 16 | 17 | public class Utils { 18 | 19 | /** 20 | * 验证手机格式 21 | */ 22 | public static boolean isMobile(String number) { 23 | /* 24 | 移动:134、135、136、137、138、139、150、151、157(TD)、158、159、187、188、170 25 | 联通:130、131、132、152、155、156、185、186 26 | 电信:133、153、180、189、(1349卫通) 27 | 总结起来就是第一位必定为1,第二位必定为3或5或8,其他位置的可以为0-9 28 | 添加新号段:第二位新增4、6、7、9 29 | */ 30 | String num = "[1][3456789]\\d{9}";//"[1]"代表第1位为数字1,"[3578]"代表第二位可以为3、5、7、8中的一个,"\\d{9}"代表后面是可以是0~9的数字,有9位。 31 | if (!isString(number)) { 32 | return false; 33 | } else { 34 | //matches():字符串是否在给定的正则表达式匹配 35 | return number.matches(num); 36 | } 37 | } 38 | 39 | /** 40 | * 手机号用****号隐藏前面中间数字 41 | */ 42 | public static String settingPhone(String phone) { 43 | String phones = phone.substring(0, 3) + "****" + phone.substring(7); 44 | return phones; 45 | } 46 | 47 | /** 48 | * 验证邮箱格式 49 | */ 50 | public static boolean isEmail(String email) { 51 | String emailMatch = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"; 52 | if (TextUtils.isEmpty(email)) { 53 | return false; 54 | } else { 55 | //matches():字符串是否在给定的正则表达式匹配 56 | return email.matches(emailMatch); 57 | } 58 | } 59 | 60 | /** 61 | * 判断是否为整数 62 | * 63 | * @param str 传入的字符串 64 | * @return 是整数返回true, 否则返回false 65 | */ 66 | public static boolean isInteger(String str) { 67 | Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$"); 68 | return pattern.matcher(str).matches(); 69 | } 70 | 71 | /** 72 | * 邮箱用****号隐藏前面的字母 73 | */ 74 | public static String settingEmail(String email) { 75 | String emails = email.replaceAll("(\\w?)(\\w+)(\\w)(@\\w+\\.[a-z]+(\\.[a-z]+)?)", "$1****$3$4"); 76 | return emails; 77 | } 78 | 79 | /** 80 | * 验证身份证号是否符合规则 81 | * 82 | * @param IDNumber 身份证号 83 | */ 84 | public static boolean personIdValidation(String IDNumber) { 85 | 86 | if (!isString(IDNumber)) 87 | return false; 88 | // 定义判别用户身份证号的正则表达式(15位或者18位,最后一位可以为字母) 89 | String regularExpression = "(^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|" + 90 | "(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}$)"; 91 | //假设18位身份证号码:41000119910101123X 410001 19910101 123X 92 | //^开头 93 | //[1-9] 第一位1-9中的一个 4 94 | //\\d{5} 五位数字 10001(前六位省市县地区) 95 | //(18|19|20) 19(现阶段可能取值范围18xx-20xx年) 96 | //\\d{2} 91(年份) 97 | //((0[1-9])|(10|11|12)) 01(月份) 98 | //(([0-2][1-9])|10|20|30|31)01(日期) 99 | //\\d{3} 三位数字 123(第十七位奇数代表男,偶数代表女) 100 | //[0-9Xx] 0123456789Xx其中的一个 X(第十八位为校验值) 101 | //$结尾 102 | 103 | //假设15位身份证号码:410001910101123 410001 910101 123 104 | //^开头 105 | //[1-9] 第一位1-9中的一个 4 106 | //\\d{5} 五位数字 10001(前六位省市县地区) 107 | //\\d{2} 91(年份) 108 | //((0[1-9])|(10|11|12)) 01(月份) 109 | //(([0-2][1-9])|10|20|30|31)01(日期) 110 | //\\d{3} 三位数字 123(第十五位奇数代表男,偶数代表女),15位身份证不含X 111 | //$结尾 112 | return IDNumber.matches(regularExpression); 113 | } 114 | 115 | /** 116 | * 把Runnable 方法提交到主线程运行 117 | */ 118 | public static void runOnUiThread(Runnable runnable) { 119 | // 在主线程运行 120 | if (android.os.Process.myTid() == android.os.Process.myTid()) { 121 | runnable.run(); 122 | } else { 123 | //获取handler 124 | MyApplication.getHandler().post(runnable); 125 | } 126 | } 127 | 128 | /** 129 | * 判断s字符串是否为空 130 | */ 131 | public static String isEmpry(String s) { 132 | if (null == s) { 133 | return ""; 134 | } else if (s.equals("")) { 135 | return ""; 136 | } else if (s.equals("null")) { 137 | return ""; 138 | } else { 139 | return s; 140 | } 141 | } 142 | 143 | /** 144 | * 判断s字符串是String 145 | */ 146 | public static boolean isString(String s) { 147 | if (null == s) { 148 | return false; 149 | } else if (s.equals("")) { 150 | return false; 151 | } else if (s.equals("null")) { 152 | return false; 153 | } else return s.length() > 0; 154 | } 155 | 156 | /** 157 | * 判断s字符串是String 158 | */ 159 | public static boolean isString(String... s) { 160 | if (s.length <= 0) 161 | return false; 162 | for (String str : s) { 163 | if (null == str) { 164 | return false; 165 | } else if (str.equals("")) { 166 | return false; 167 | } else if (str.equals("null")) { 168 | return false; 169 | } else if (str.length() <= 0) { 170 | return false; 171 | } 172 | } 173 | return true; 174 | } 175 | 176 | /** 177 | * 从字符串中提取数字 178 | */ 179 | public static String getStrNum(String str) { 180 | if (isString(str)) { 181 | String regEx = "[^0-9]"; 182 | Pattern p = Pattern.compile(regEx); 183 | Matcher m = p.matcher(str); 184 | return m.replaceAll("").trim(); 185 | } else { 186 | return ""; 187 | } 188 | } 189 | 190 | /** 191 | * 获取Realm数据库64位秘钥 192 | * 193 | * @param key 秘钥字符 194 | * @return 秘钥 195 | */ 196 | public static byte[] getRealmKey(String key) { 197 | StringBuilder newKey = new StringBuilder(); 198 | for (int i = 0; i < 4; i++) { 199 | newKey.append(key); 200 | } 201 | return newKey.toString().getBytes(); 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/commmon/utils/Validator.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.commmon.utils; 2 | 3 | import android.text.TextUtils; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | /** 13 | * 正则表达式 14 | */ 15 | public class Validator { 16 | public static boolean isNull(List objs) { 17 | if (objs == null || objs.isEmpty()) { 18 | return true; 19 | } 20 | for (Object o : objs) { 21 | if (o == null) { 22 | return true; 23 | } 24 | } 25 | 26 | return false; 27 | } 28 | 29 | public static boolean isNull(Object[] objs) { 30 | if (objs == null || objs.length == 0) return true; 31 | for (Object o : objs) { 32 | if (o == null) return true; 33 | } 34 | 35 | return false; 36 | } 37 | 38 | public static boolean password(String password) { 39 | return password.matches("[0-9a-zA-Z]{8,16}") && !password.matches("[0-9]+") && !password.matches("[a-zA-Z]+"); 40 | } 41 | 42 | public static List findMac(String src) { 43 | List list = new ArrayList(); 44 | if (src == null || src.equals("")) 45 | return list; 46 | Pattern pattern = Pattern 47 | .compile("[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}"); 48 | Matcher matcher = pattern.matcher(src); 49 | while (matcher.find()) { 50 | list.add(matcher.group(0)); 51 | } 52 | return list; 53 | } 54 | 55 | public static List findColor(String src) { 56 | List list = new ArrayList(); 57 | if (src == null || src.equals("")) 58 | return list; 59 | Pattern pattern = Pattern 60 | .compile("#[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8}"); 61 | Matcher matcher = pattern.matcher(src); 62 | while (matcher.find()) { 63 | list.add(matcher.group(0)); 64 | } 65 | return list; 66 | } 67 | 68 | /** 69 | * 去除汉字,归正编码 70 | */ 71 | public static String replaceHanzi(String input) { 72 | if (TextUtils.isEmpty(input)) { 73 | return ""; 74 | } 75 | 76 | /** 77 | * 归正编码 78 | */ 79 | byte[] bytes = input.getBytes(); 80 | String info = ""; 81 | 82 | for (int i = 0; i < bytes.length; i++) { 83 | if (bytes[i] < 0) { 84 | bytes[i] = 32; 85 | } 86 | info = info + new String(new byte[]{bytes[i]}); 87 | } 88 | 89 | /** 90 | * 去除中文 91 | */ 92 | Pattern p = Pattern.compile("[\u4e00-\u9fa5]"); 93 | Matcher m = p.matcher(info); 94 | List inputs = new ArrayList<>(); 95 | 96 | if (m.find()) { 97 | for (int i = 0; i < info.length(); i++) { 98 | String ever = info.substring(i, i + 1); 99 | Matcher m1 = p.matcher(ever); 100 | if (m1.find()) { 101 | ever = ""; 102 | } 103 | inputs.add(ever); 104 | } 105 | 106 | String inputNew = ""; 107 | for (int i = 0; i < inputs.size(); i++) { 108 | inputNew = inputNew + inputs.get(i); 109 | } 110 | return inputNew.trim(); 111 | 112 | } 113 | return info.trim(); 114 | } 115 | 116 | /** 117 | * 验证邮箱 118 | */ 119 | public static boolean checkEmail(String email) { 120 | boolean flag; 121 | try { 122 | String check = "^([a-z0-9A-Z]+[-|_|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"; 123 | Pattern regex = Pattern.compile(check); 124 | Matcher matcher = regex.matcher(email); 125 | flag = matcher.matches(); 126 | } catch (Exception e) { 127 | flag = false; 128 | } 129 | return flag; 130 | } 131 | 132 | /** 133 | * 获得汉语拼音首字母 134 | */ 135 | public static String getAlpha(String str) { 136 | if (str == null) { 137 | return "#"; 138 | } 139 | 140 | if (str.trim().length() == 0) { 141 | return "#"; 142 | } 143 | 144 | char c = str.trim().substring(0, 1).charAt(0); 145 | // 正则表达式,判断首字母是否是英文字母 146 | Pattern pattern = Pattern.compile("^[A-Za-z]+$"); 147 | if (pattern.matcher(c + "").matches()) { 148 | return (c + "").toUpperCase(); 149 | } else { 150 | return "#"; 151 | } 152 | } 153 | 154 | /** 155 | * 校验URL 156 | */ 157 | public static boolean checkUrl(String url) { 158 | if (TextUtils.isEmpty(url)) { 159 | return false; 160 | } 161 | String temp = url.replace(" ", ""); 162 | if (!temp.startsWith("http") && !temp.startsWith("https")) { 163 | temp = "https://" + temp; 164 | } 165 | boolean flag; 166 | try { 167 | String check = "(http|https):\\/\\/[\\w\\-_]+(\\.[\\w\\-_]+)+([\\w\\-\\.,@?^=%&:/~\\+#]*[\\w\\-\\@?^=%&/~\\+#])?"; 168 | Pattern regex = Pattern.compile(check, Pattern.CASE_INSENSITIVE); 169 | Matcher matcher = regex.matcher(temp.replaceAll(" ", "")); 170 | flag = matcher.matches(); 171 | } catch (Exception e) { 172 | flag = false; 173 | } 174 | 175 | return flag; 176 | } 177 | 178 | 179 | public static boolean checkColor(@NonNull String color) { 180 | 181 | return color.matches("^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"); 182 | } 183 | 184 | /** 185 | * @return 从下载连接中解析出文件名 186 | */ 187 | @NonNull 188 | public static String getNameFromUrl(String url) { 189 | String temp = url; 190 | if (url.contains("?")) { 191 | temp = url.substring(0, url.indexOf("?")); 192 | } 193 | temp = temp.substring(temp.lastIndexOf("/") + 1); 194 | if (TextUtils.isEmpty(temp)) { 195 | temp = System.currentTimeMillis() + ".temp"; 196 | } 197 | return temp; 198 | } 199 | 200 | 201 | } 202 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/db/DBHelper.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.db; 2 | 3 | import com.james.motion.commmon.bean.SportMotionRecord; 4 | import com.james.motion.commmon.bean.UserAccount; 5 | 6 | import java.util.List; 7 | 8 | public interface DBHelper { 9 | 10 | /** 11 | * 增加 运动数据 12 | * 13 | * @param record 运动数据 14 | */ 15 | void insertSportRecord(SportMotionRecord record); 16 | 17 | /** 18 | * 删除 运动数据 19 | * 20 | * @param record 运动数据 21 | */ 22 | void deleteSportRecord(SportMotionRecord record); 23 | 24 | /** 25 | * 删除 全部运动数据 26 | */ 27 | void deleteSportRecord(); 28 | 29 | /** 30 | * 获取 某人的运动数据 31 | * 32 | * @param master 登陆者 33 | */ 34 | List queryRecordList(int master); 35 | 36 | /** 37 | * 获取 某人的运动数据 38 | * 39 | * @param master 登陆者 40 | * @param dateTag 时间标记 41 | */ 42 | List queryRecordList(int master, String dateTag); 43 | 44 | /** 45 | * 获取 全部的运动数据 46 | */ 47 | List queryRecordList(); 48 | 49 | /** 50 | * 获取 运动数据 51 | * 52 | * @param master 登陆者 53 | * @param startTime 开始时间 54 | * @param endTime 结束时间 55 | */ 56 | SportMotionRecord queryRecord(int master, long startTime, long endTime); 57 | 58 | /** 59 | * 获取 运动数据 60 | * 61 | * @param master 登陆者 62 | * @param dateTag 时间标记 63 | */ 64 | SportMotionRecord queryRecord(int master, String dateTag); 65 | 66 | /** 67 | * 关闭数据库 68 | */ 69 | void closeRealm(); 70 | 71 | /** 72 | * 增加 账号 73 | * 74 | * @param account 账号 75 | */ 76 | void insertAccount(UserAccount account); 77 | 78 | /** 79 | * 获取 账号 80 | * 81 | * @param account 账号 82 | */ 83 | UserAccount queryAccount(String account); 84 | 85 | /** 86 | * 查詢 账号 87 | * 88 | * @param account 账号 89 | * @param psd 密码 90 | */ 91 | boolean checkAccount(String account, String psd); 92 | 93 | /** 94 | * 查詢 账号 95 | * 96 | * @param account 账号 97 | */ 98 | boolean checkAccount(String account); 99 | 100 | /** 101 | * 获取 全部的账号 102 | */ 103 | List queryAccountList(); 104 | 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/db/DataManager.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.db; 2 | 3 | import com.james.motion.commmon.bean.SportMotionRecord; 4 | import com.james.motion.commmon.bean.UserAccount; 5 | 6 | import java.util.List; 7 | 8 | public class DataManager implements DBHelper { 9 | 10 | private RealmHelper realmHelper; 11 | 12 | private DataManager() { 13 | 14 | } 15 | 16 | public DataManager(RealmHelper helper) { 17 | realmHelper = helper; 18 | } 19 | 20 | @Override 21 | public void insertSportRecord(SportMotionRecord record) { 22 | realmHelper.insertSportRecord(record); 23 | } 24 | 25 | @Override 26 | public void deleteSportRecord(SportMotionRecord record) { 27 | realmHelper.deleteSportRecord(record); 28 | } 29 | 30 | @Override 31 | public void deleteSportRecord() { 32 | realmHelper.deleteSportRecord(); 33 | } 34 | 35 | @Override 36 | public List queryRecordList(int master) { 37 | return realmHelper.queryRecordList(master); 38 | } 39 | 40 | @Override 41 | public List queryRecordList(int master, String dateTag) { 42 | return realmHelper.queryRecordList(master, dateTag); 43 | } 44 | 45 | @Override 46 | public List queryRecordList() { 47 | return realmHelper.queryRecordList(); 48 | } 49 | 50 | @Override 51 | public SportMotionRecord queryRecord(int master, long startTime, long endTime) { 52 | return realmHelper.queryRecord(master, startTime, endTime); 53 | } 54 | 55 | @Override 56 | public SportMotionRecord queryRecord(int master, String dateTag) { 57 | return realmHelper.queryRecord(master, dateTag); 58 | } 59 | 60 | @Override 61 | public void closeRealm() { 62 | realmHelper.closeRealm(); 63 | } 64 | 65 | @Override 66 | public void insertAccount(UserAccount account) { 67 | realmHelper.insertAccount(account); 68 | } 69 | 70 | @Override 71 | public UserAccount queryAccount(String account) { 72 | return realmHelper.queryAccount(account); 73 | } 74 | 75 | @Override 76 | public boolean checkAccount(String account, String psd) { 77 | return realmHelper.checkAccount(account, psd); 78 | } 79 | 80 | @Override 81 | public boolean checkAccount(String account) { 82 | return realmHelper.checkAccount(account); 83 | } 84 | 85 | @Override 86 | public List queryAccountList() { 87 | return realmHelper.queryAccountList(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/sport_motion/LocationService.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.sport_motion; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.Binder; 6 | import android.os.IBinder; 7 | 8 | import androidx.annotation.Nullable; 9 | 10 | import com.amap.api.location.AMapLocation; 11 | import com.amap.api.location.AMapLocationClient; 12 | import com.amap.api.location.AMapLocationClientOption; 13 | import com.amap.api.location.AMapLocationListener; 14 | import com.amap.api.maps.model.LatLng; 15 | import com.james.motion.commmon.utils.LogUtils; 16 | import com.james.motion.sport_motion.servicecode.RecordService; 17 | import com.james.motion.sport_motion.servicecode.impl.RecordServiceImpl; 18 | 19 | /** 20 | * 定位的Service类,用户在运动时此服务会在后台进行定位。 21 | */ 22 | public class LocationService extends Service { 23 | 24 | private InterfaceLocationed interfaceLocationed = null; 25 | 26 | public static final String TAG = "LocationService"; 27 | 28 | public final IBinder mBinder = new LocalBinder(); 29 | 30 | public class LocalBinder extends Binder { 31 | // 在Binder中定义一个自定义的接口用于数据交互 32 | // 这里直接把当前的服务传回给宿主 33 | public LocationService getService() { 34 | return LocationService.this; 35 | } 36 | } 37 | 38 | //定位的时间间隔,单位是毫秒 39 | private static final int LOCATION_SPAN = 10 * 1000; 40 | 41 | //百度地图中定位的类 42 | public AMapLocationClient mLocationClient = null; 43 | //记录着运动中移动的坐标位置 44 | // private List mSportLatLngs = new LinkedList<>(); 45 | 46 | //记录运动信息的Service 47 | private RecordService mRecordService = null; 48 | 49 | @Override 50 | public void onCreate() { 51 | super.onCreate(); 52 | 53 | //声明LocationClient类 54 | mLocationClient = new AMapLocationClient(this); 55 | //给定位类加入自定义的配置 56 | initLocationOption(); 57 | //注册监听函数 58 | mLocationClient.setLocationListener(MyAMapLocationListener); 59 | 60 | //初始化信息记录类 61 | mRecordService = new RecordServiceImpl(this); 62 | 63 | //启动定位 64 | mLocationClient.startLocation(); 65 | } 66 | 67 | //初始化定位的配置 68 | private void initLocationOption() { 69 | AMapLocationClientOption mOption = new AMapLocationClientOption(); 70 | mOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);//可选,设置定位模式,可选的模式有高精度、仅设备、仅网络。默认为高精度模式 71 | mOption.setGpsFirst(true);//可选,设置是否gps优先,只在高精度模式下有效。默认关闭 72 | mOption.setHttpTimeOut(30000);//可选,设置网络请求超时时间。默认为30秒。在仅设备模式下无效 73 | mOption.setInterval(4000);//可选,设置定位间隔。默认为2秒 74 | mOption.setNeedAddress(true);//可选,设置是否返回逆地理地址信息。默认是true 75 | mOption.setOnceLocation(false);//可选,设置是否单次定位。默认是false 76 | mOption.setOnceLocationLatest(false);//可选,设置是否等待wifi刷新,默认为false.如果设置为true,会自动变为单次定位,持续定位时不要使用 77 | AMapLocationClientOption.setLocationProtocol(AMapLocationClientOption.AMapLocationProtocol.HTTP);//可选, 设置网络请求的协议。可选HTTP或者HTTPS。默认为HTTP 78 | mOption.setSensorEnable(false);//可选,设置是否使用传感器。默认是false 79 | mOption.setWifiScan(true); //可选,设置是否开启wifi扫描。默认为true,如果设置为false会同时停止主动刷新,停止以后完全依赖于系统刷新,定位位置可能存在误差 80 | mOption.setLocationCacheEnable(false); //可选,设置是否使用缓存定位,默认为true 81 | mOption.setGeoLanguage(AMapLocationClientOption.GeoLanguage.DEFAULT);//可选,设置逆地理信息的语言,默认值为默认语言(根据所在地区选择语言) 82 | mLocationClient.setLocationOption(mOption); 83 | } 84 | 85 | //定位回调 86 | private AMapLocationListener MyAMapLocationListener = aMapLocation -> { 87 | 88 | if (null == aMapLocation) 89 | return; 90 | 91 | if (aMapLocation.getErrorCode() == 0) { 92 | //先暂时获得经纬度信息,并将其记录在List中 93 | LogUtils.d("纬度信息为" + aMapLocation.getLatitude() + "\n经度信息为" + aMapLocation.getLongitude()); 94 | LatLng locationValue = new LatLng(aMapLocation.getLatitude(), aMapLocation.getLongitude()); 95 | // mSportLatLngs.add(locationValue); 96 | 97 | //将运动信息上传至服务器 98 | recordLocation(locationValue, aMapLocation.getLocationDetail()); 99 | 100 | //定位成功,发送通知 101 | if (null != interfaceLocationed) 102 | interfaceLocationed.locationed(aMapLocation); 103 | 104 | } else { 105 | String errText = "定位失败," + aMapLocation.getErrorCode() + ": " + aMapLocation.getErrorInfo(); 106 | LogUtils.e("AmapErr", errText); 107 | } 108 | }; 109 | 110 | private void recordLocation(LatLng latLng, String location) { 111 | if (mRecordService != null) { 112 | mRecordService.recordSport(latLng, location); 113 | } 114 | } 115 | 116 | @Nullable 117 | @Override 118 | public IBinder onBind(Intent intent) { 119 | LogUtils.i(TAG, "绑定服务 The service is binding!"); 120 | // 绑定服务,把当前服务的IBinder对象的引用传递给宿主 121 | return mBinder; 122 | } 123 | 124 | @Override 125 | public boolean onUnbind(Intent intent) { 126 | LogUtils.i(TAG, "解除绑定服务 The service is unbinding!"); 127 | //解除绑定后销毁服务 128 | stopSelf(); 129 | return super.onUnbind(intent); 130 | } 131 | 132 | @Override 133 | public void onDestroy() { 134 | super.onDestroy(); 135 | if (null != mLocationClient) { 136 | mLocationClient.stopLocation(); 137 | mLocationClient.unRegisterLocationListener(MyAMapLocationListener); 138 | mLocationClient.onDestroy(); 139 | mLocationClient = null; 140 | } 141 | } 142 | 143 | public void setInterfaceLocationed(InterfaceLocationed interfaceLocationed) { 144 | this.interfaceLocationed = null; 145 | this.interfaceLocationed = interfaceLocationed; 146 | } 147 | 148 | public interface InterfaceLocationed { 149 | void locationed(AMapLocation aMapLocation); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/sport_motion/TraceRePlay.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.sport_motion; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.os.Message; 6 | 7 | import com.amap.api.maps.model.LatLng; 8 | 9 | import java.lang.ref.WeakReference; 10 | import java.util.List; 11 | 12 | /** 13 | * 轨迹回放工具类 14 | */ 15 | public class TraceRePlay implements Runnable { 16 | private TraceRePlayHandler mTraceHandler; 17 | private static final int TRACE_MOVE = 1; 18 | private static final int TRACE_FINISH = 2; 19 | private List mTraceList; 20 | private int mIntervalMillisecond; 21 | private TraceRePlayListener mTraceUpdateListener; 22 | private boolean mStop = false; 23 | 24 | /** 25 | * 构造轨迹回放需要,List、时间间隔、回调listenenr 26 | * 27 | * @param list 轨迹list 28 | * @param intervalMillisecond 回调时间间隔 29 | * @param listener 回调listener 30 | */ 31 | public TraceRePlay(List list, int intervalMillisecond, 32 | TraceRePlayListener listener) { 33 | mTraceList = list; 34 | mIntervalMillisecond = intervalMillisecond; 35 | mTraceUpdateListener = listener; 36 | mTraceHandler = new TraceRePlayHandler(this); 37 | } 38 | 39 | public void stopTrace() { 40 | mStop = true; 41 | } 42 | 43 | /** 44 | * 接收run发送的消息,达到按照设定时间间隔轮巡轨迹list目的 45 | */ 46 | static class TraceRePlayHandler extends Handler { 47 | WeakReference mTraceRePaly; 48 | 49 | public TraceRePlayHandler(TraceRePlay traceRePlay) { 50 | super(Looper.getMainLooper()); 51 | mTraceRePaly = new WeakReference<>(traceRePlay); 52 | } 53 | 54 | @Override 55 | public void handleMessage(Message message) { 56 | super.handleMessage(message); 57 | TraceRePlay trace = mTraceRePaly.get(); 58 | switch (message.what) { 59 | case TRACE_MOVE: 60 | LatLng latLng = (LatLng) message.obj; 61 | if (trace.mTraceUpdateListener != null) { 62 | trace.mTraceUpdateListener.onTraceUpdating(latLng); 63 | } 64 | break; 65 | case TRACE_FINISH: 66 | if (trace.mTraceUpdateListener != null) { 67 | trace.mTraceUpdateListener.onTraceUpdateFinish(); 68 | } 69 | break; 70 | default: 71 | break; 72 | } 73 | } 74 | } 75 | 76 | public interface TraceRePlayListener { 77 | /** 78 | * 轨迹回放过程回调 79 | * 80 | * @param latLng 81 | */ 82 | public void onTraceUpdating(LatLng latLng); 83 | 84 | /** 85 | * 轨迹回放结束回调 86 | */ 87 | public void onTraceUpdateFinish(); 88 | 89 | } 90 | 91 | /** 92 | * 将mTraceList 按照给定的时间间隔和次序发消息给TraceRePlayHandler以达到轨迹回放效果 93 | */ 94 | @Override 95 | public void run() { 96 | if (mTraceList != null) { 97 | for (int i = 0; i < mTraceList.size(); i++) { 98 | if (mStop) { 99 | break; 100 | } 101 | LatLng latLng = mTraceList.get(i); 102 | Message message = mTraceHandler.obtainMessage(); 103 | message.what = TRACE_MOVE; 104 | message.obj = latLng; 105 | mTraceHandler.sendMessage(message); 106 | try { 107 | Thread.sleep(mIntervalMillisecond); 108 | } catch (InterruptedException e) { 109 | e.printStackTrace(); 110 | } 111 | } 112 | if (!mStop) { 113 | Message finishMessage = mTraceHandler.obtainMessage(); 114 | finishMessage.what = TRACE_FINISH; 115 | mTraceHandler.sendMessage(finishMessage); 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/sport_motion/servicecode/RecordService.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.sport_motion.servicecode; 2 | 3 | import com.amap.api.maps.model.LatLng; 4 | 5 | /** 6 | * 上传跑步信息的Service接口 7 | */ 8 | public interface RecordService { 9 | 10 | //记录运动坐标和大概描述信息 11 | void recordSport(LatLng latLng, String location); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/sport_motion/servicecode/impl/RecordServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.sport_motion.servicecode.impl; 2 | 3 | import android.content.Context; 4 | 5 | import com.amap.api.maps.model.LatLng; 6 | import com.james.motion.commmon.utils.LogUtils; 7 | import com.james.motion.sport_motion.servicecode.RecordService; 8 | 9 | /** 10 | * 记录运动信息的实现类 11 | */ 12 | public class RecordServiceImpl implements RecordService { 13 | 14 | private Context mContext; 15 | 16 | public RecordServiceImpl(Context context) { 17 | this.mContext = context; 18 | } 19 | 20 | @Override 21 | public void recordSport(LatLng latLng, String location) { 22 | LogUtils.d("保存定位数据 = " + latLng.latitude + ":" + latLng.longitude + " " + location); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.os.Handler; 7 | import android.os.Message; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.Toast; 12 | 13 | import androidx.annotation.Nullable; 14 | import androidx.fragment.app.Fragment; 15 | 16 | import com.james.motion.commmon.utils.Utils; 17 | import com.james.motion.ui.weight.CustomProgressDialog; 18 | 19 | import butterknife.ButterKnife; 20 | import butterknife.Unbinder; 21 | 22 | /** 23 | * 描述: Fragment基类 24 | * 作者: james 25 | * 日期: 2019/2/25 15:42 26 | * 类名: BaseFragment 27 | */ 28 | public abstract class BaseFragment extends Fragment { 29 | 30 | protected String TAG = BaseFragment.class.getSimpleName(); 31 | 32 | private Unbinder bind; 33 | 34 | protected Context context; 35 | 36 | private Toast mToast = null; 37 | 38 | private static final int DISMISS = 1001; 39 | private static final int SHOW = 1002; 40 | private CustomProgressDialog progressDialog = null; 41 | 42 | @SuppressLint("HandlerLeak") 43 | private Handler mHandler = new Handler() { 44 | 45 | @Override 46 | public void handleMessage(Message msg) { 47 | switch (msg.what) { 48 | case SHOW: 49 | if (progressDialog != null) { 50 | progressDialog.setTouchAble((Boolean) msg.obj); 51 | progressDialog.show(); 52 | } 53 | break; 54 | case DISMISS: 55 | if (progressDialog != null && progressDialog.isShowing()) { 56 | progressDialog.dismiss(); 57 | } 58 | break; 59 | default: 60 | break; 61 | } 62 | } 63 | }; 64 | 65 | 66 | @Nullable 67 | @Override 68 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 69 | 70 | View inflate = inflater.inflate(getLayoutId(), container, false); 71 | 72 | bind = ButterKnife.bind(this, inflate); 73 | 74 | context = getActivity(); 75 | 76 | initData(savedInstanceState); 77 | 78 | initListener(); 79 | 80 | mToast = Toast.makeText(context, "", Toast.LENGTH_SHORT); 81 | if (progressDialog == null) { 82 | progressDialog = new CustomProgressDialog(context); 83 | } 84 | return inflate; 85 | } 86 | 87 | public abstract int getLayoutId(); 88 | 89 | public abstract void initData(Bundle savedInstanceState); 90 | 91 | public abstract void initListener(); 92 | 93 | @Override 94 | public void onResume() { 95 | super.onResume(); 96 | 97 | if (mToast == null) 98 | mToast = Toast.makeText(context, "", Toast.LENGTH_SHORT); 99 | if (progressDialog == null) { 100 | progressDialog = new CustomProgressDialog(context); 101 | } 102 | } 103 | 104 | /** 105 | * 显示加载视图 106 | * 107 | * @param isTouchAble true:可点击 false:不可点击 默认是可点击 108 | */ 109 | protected void showLoadingView(boolean isTouchAble) { 110 | if (null != mHandler && !isShowingLoadingView()) { 111 | Message m = mHandler.obtainMessage(SHOW, isTouchAble); 112 | mHandler.sendMessage(m); 113 | } 114 | } 115 | 116 | protected void showLoadingView() { 117 | if (!isShowingLoadingView()) 118 | showLoadingView(true); 119 | } 120 | 121 | /** 122 | * 关闭加载视图 123 | */ 124 | protected void dismissLoadingView() { 125 | if (null != mHandler) 126 | mHandler.sendEmptyMessage(DISMISS); 127 | } 128 | 129 | protected boolean isShowingLoadingView() { 130 | if (progressDialog != null) { 131 | return progressDialog.isShowing(); 132 | } else { 133 | return false; 134 | } 135 | } 136 | 137 | 138 | public void showToast(String s) { 139 | if (!Utils.isString(s)) 140 | return; 141 | if (mToast != null) { 142 | mToast.cancel(); 143 | mToast = null; 144 | } 145 | if (null != context) { 146 | mToast = Toast.makeText(context, s, Toast.LENGTH_SHORT); 147 | mToast.show(); 148 | } 149 | } 150 | 151 | @Override 152 | public void onPause() { 153 | 154 | if (mToast != null) { 155 | mToast.cancel(); 156 | mToast = null; 157 | } 158 | if (progressDialog != null && progressDialog.isShowing()) { 159 | progressDialog.dismiss(); 160 | } 161 | progressDialog = null; 162 | 163 | super.onPause(); 164 | } 165 | 166 | @Override 167 | public void onDestroy() { 168 | if (bind != null) { 169 | bind.unbind(); 170 | } 171 | if (progressDialog != null && progressDialog.isShowing()) { 172 | progressDialog.dismiss(); 173 | } 174 | progressDialog = null; 175 | if (null != mHandler) { 176 | mHandler.removeMessages(DISMISS); 177 | mHandler.removeMessages(SHOW); 178 | mHandler.removeCallbacksAndMessages(null); 179 | mHandler = null; 180 | } 181 | super.onDestroy(); 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/activity/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.activity; 2 | 3 | import android.Manifest; 4 | import android.content.Intent; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.view.KeyEvent; 8 | import android.widget.ImageView; 9 | import android.widget.TextView; 10 | 11 | import com.blankj.utilcode.util.SPUtils; 12 | import com.blankj.utilcode.util.ToastUtils; 13 | import com.gyf.immersionbar.ImmersionBar; 14 | import com.james.motion.MyApplication; 15 | import com.james.motion.R; 16 | import com.james.motion.commmon.utils.MySp; 17 | import com.james.motion.commmon.utils.UIHelper; 18 | import com.james.motion.ui.BaseActivity; 19 | import com.james.motion.ui.permission.PermissionHelper; 20 | import com.james.motion.ui.permission.PermissionListener; 21 | import com.james.motion.ui.weight.CountDownProgressView; 22 | 23 | import butterknife.BindView; 24 | 25 | /** 26 | * 描述: 闪屏界面 27 | * 作者: james 28 | * 日期: 2019/2/25 16:20 29 | * 类名: SplashActivity 30 | */ 31 | public class SplashActivity extends BaseActivity { 32 | 33 | @BindView(R.id.im_url) 34 | ImageView imUrl; 35 | @BindView(R.id.countdownProgressView) 36 | CountDownProgressView countDownProgressView; 37 | @BindView(R.id.versions) 38 | TextView versions; 39 | 40 | /** 41 | * 上次点击返回键的时间 42 | */ 43 | private long lastBackPressed; 44 | /** 45 | * 上次点击返回键的时间 46 | */ 47 | private static final int QUIT_INTERVAL = 3000; 48 | 49 | // 要申请的权限 50 | private static String[] PERMISSIONS_STORAGE = { 51 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 52 | Manifest.permission.READ_EXTERNAL_STORAGE, 53 | Manifest.permission.ACCESS_FINE_LOCATION, 54 | Manifest.permission.ACCESS_COARSE_LOCATION}; 55 | 56 | @Override 57 | protected void initImmersionBar() { 58 | super.initImmersionBar(); 59 | if (ImmersionBar.hasNavigationBar(this)) { 60 | ImmersionBar.with(this).transparentNavigationBar().init(); 61 | } 62 | } 63 | 64 | @Override 65 | public int getLayoutId() { 66 | return R.layout.activity_splash; 67 | } 68 | 69 | @Override 70 | public void initData(Bundle savedInstanceState) { 71 | 72 | imUrl.setImageResource(R.mipmap.splash_bg); 73 | 74 | versions.setText(UIHelper.getString(R.string.splash_appversionname, MyApplication.getAppVersionName())); 75 | 76 | countDownProgressView.setTimeMillis(2000); 77 | countDownProgressView.setProgressType(CountDownProgressView.ProgressType.COUNT_BACK); 78 | countDownProgressView.start(); 79 | } 80 | 81 | @Override 82 | public void initListener() { 83 | countDownProgressView.setOnClickListener(v -> { 84 | 85 | countDownProgressView.stop(); 86 | 87 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 88 | // 获取权限 89 | PermissionHelper.requestPermissions(this, PERMISSIONS_STORAGE, new PermissionListener() { 90 | @Override 91 | public void onPassed() { 92 | startActivity(); 93 | } 94 | }); 95 | } else { 96 | startActivity(); 97 | } 98 | }); 99 | 100 | countDownProgressView.setProgressListener(progress -> { 101 | if (progress == 0) { 102 | // 版本判断。当手机系统大于 23 时,才有必要去判断权限是否获取 103 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 104 | // 获取权限 105 | PermissionHelper.requestPermissions(this, PERMISSIONS_STORAGE, 106 | getResources().getString(R.string.app_name) + "需要获取存储、位置权限", new PermissionListener() { 107 | @Override 108 | public void onPassed() { 109 | startActivity(); 110 | } 111 | }); 112 | } else { 113 | startActivity(); 114 | } 115 | } 116 | }); 117 | } 118 | 119 | public void startActivity() { 120 | if (SPUtils.getInstance().getBoolean(MySp.ISLOGIN)) { 121 | startActivity(new Intent(SplashActivity.this, HomeActivity.class)); 122 | finish(); 123 | } else { 124 | startActivity(new Intent(SplashActivity.this, LoginActivity.class)); 125 | finish(); 126 | } 127 | countDownProgressView.stop(); 128 | } 129 | 130 | @Override 131 | public boolean onKeyDown(int keyCode, KeyEvent event) { 132 | if (event.getAction() == KeyEvent.ACTION_DOWN) { 133 | if (keyCode == KeyEvent.KEYCODE_BACK) { // 表示按返回键 时的操作 134 | long backPressed = System.currentTimeMillis(); 135 | if (backPressed - lastBackPressed > QUIT_INTERVAL) { 136 | lastBackPressed = backPressed; 137 | ToastUtils.showShort("再按一次退出"); 138 | } else { 139 | if (countDownProgressView != null) { 140 | countDownProgressView.stop(); 141 | countDownProgressView.clearAnimation(); 142 | } 143 | moveTaskToBack(false); 144 | MyApplication.closeApp(this); 145 | finish(); 146 | } 147 | } 148 | } 149 | return false; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/activity/SportRecordDetailsActivity.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.activity; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.widget.TextView; 7 | 8 | import androidx.fragment.app.Fragment; 9 | import androidx.fragment.app.FragmentManager; 10 | import androidx.fragment.app.FragmentPagerAdapter; 11 | import androidx.viewpager.widget.ViewPager; 12 | 13 | import com.blankj.utilcode.util.ToastUtils; 14 | import com.flyco.tablayout.CommonTabLayout; 15 | import com.flyco.tablayout.listener.CustomTabEntity; 16 | import com.flyco.tablayout.listener.OnTabSelectListener; 17 | import com.james.motion.R; 18 | import com.james.motion.commmon.bean.PathRecord; 19 | import com.james.motion.commmon.bean.TabEntity; 20 | import com.james.motion.ui.BaseActivity; 21 | import com.james.motion.ui.fragment.SportRecordDetailsFragment; 22 | import com.james.motion.ui.fragment.SportRecordDetailsMapFragment; 23 | import com.james.motion.ui.fragment.SportRecordDetailsSpeedFragment; 24 | import com.james.motion.ui.weight.AMapScrollViewPager; 25 | 26 | import java.util.ArrayList; 27 | 28 | import butterknife.BindView; 29 | 30 | /** 31 | * 描述: 运动记录详情 32 | * 作者: james 33 | * 日期: 2019/2/27 15:10 34 | * 类名: SportRecordDetailsActivity 35 | */ 36 | public class SportRecordDetailsActivity extends BaseActivity { 37 | 38 | @BindView(R.id.tv_title) 39 | TextView tvTitle; 40 | @BindView(R.id.commonTabLayout) 41 | CommonTabLayout commonTabLayout; 42 | @BindView(R.id.vp) 43 | AMapScrollViewPager mViewPager; 44 | 45 | private ArrayList mTabEntities = new ArrayList<>(); 46 | private String[] mTitles = {"轨迹", "详情", "配速"}; 47 | private ArrayList mFragments = new ArrayList() {{ 48 | add(new SportRecordDetailsMapFragment()); 49 | add(new SportRecordDetailsFragment()); 50 | add(new SportRecordDetailsSpeedFragment()); 51 | }}; 52 | 53 | private PathRecord pathRecord = null; 54 | 55 | public static String SPORT_DATA = "SPORT_DATA"; 56 | 57 | public static void StartActivity(Activity activity, PathRecord pathRecord) { 58 | Intent intent = new Intent(); 59 | Bundle bundle = new Bundle(); 60 | bundle.putParcelable(SPORT_DATA, pathRecord); 61 | intent.putExtras(bundle); 62 | intent.setClass(activity, SportRecordDetailsActivity.class); 63 | activity.startActivity(intent); 64 | } 65 | 66 | @Override 67 | public int getLayoutId() { 68 | return R.layout.activity_sportrecorddetails; 69 | } 70 | 71 | @Override 72 | public void initData(Bundle savedInstanceState) { 73 | tvTitle.setText("运动记录"); 74 | 75 | if (getIntent().hasExtra(SPORT_DATA)) { 76 | pathRecord = getIntent().getParcelableExtra(SPORT_DATA); 77 | } else { 78 | ToastUtils.showShort("参数错误!"); 79 | finish(); 80 | } 81 | 82 | Bundle bundle = new Bundle(); 83 | bundle.putParcelable(SPORT_DATA, pathRecord); 84 | for (int i = 0, size = mTitles.length; i < size; i++) { 85 | mTabEntities.add(new TabEntity(mTitles[i], 0, 0)); 86 | mFragments.get(i).setArguments(bundle); 87 | } 88 | 89 | commonTabLayout.setTabData(mTabEntities); 90 | 91 | //设置缓存页面数量,默认为2,防止地图显示重载丢失已设置的数据 92 | mViewPager.setOffscreenPageLimit(4); 93 | 94 | mViewPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager())); 95 | 96 | mViewPager.setCurrentItem(0); 97 | } 98 | 99 | @Override 100 | public void initListener() { 101 | commonTabLayout.setOnTabSelectListener(new OnTabSelectListener() { 102 | @Override 103 | public void onTabSelect(int position) { 104 | mViewPager.setCurrentItem(position); 105 | } 106 | 107 | @Override 108 | public void onTabReselect(int position) { 109 | 110 | } 111 | }); 112 | mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { 113 | @Override 114 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 115 | 116 | } 117 | 118 | @Override 119 | public void onPageSelected(int position) { 120 | commonTabLayout.setCurrentTab(position); 121 | } 122 | 123 | @Override 124 | public void onPageScrollStateChanged(int state) { 125 | 126 | } 127 | }); 128 | } 129 | 130 | private class MyPagerAdapter extends FragmentPagerAdapter { 131 | 132 | MyPagerAdapter(FragmentManager fm) { 133 | super(fm); 134 | } 135 | 136 | @Override 137 | public int getCount() { 138 | return mFragments.size(); 139 | } 140 | 141 | @Override 142 | public CharSequence getPageTitle(int position) { 143 | return mTitles[position]; 144 | } 145 | 146 | @Override 147 | public Fragment getItem(int position) { 148 | return mFragments.get(position); 149 | } 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/activity/SportsActivity.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.activity; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.widget.Button; 6 | import android.widget.TextView; 7 | 8 | import com.blankj.utilcode.util.SPUtils; 9 | import com.james.motion.R; 10 | import com.james.motion.commmon.bean.SportMotionRecord; 11 | import com.james.motion.commmon.utils.LogUtils; 12 | import com.james.motion.commmon.utils.MySp; 13 | import com.james.motion.db.DataManager; 14 | import com.james.motion.db.RealmHelper; 15 | import com.james.motion.ui.BaseActivity; 16 | import com.james.motion.ui.permission.PermissionHelper; 17 | import com.james.motion.ui.permission.PermissionListener; 18 | import com.james.motion.ui.permission.Permissions; 19 | 20 | import java.text.DecimalFormat; 21 | import java.util.List; 22 | 23 | import butterknife.BindView; 24 | import butterknife.OnClick; 25 | 26 | /** 27 | * 描述: 运动轨迹 28 | * 作者: james 29 | * 日期: 2019/2/27 14:50 30 | * 类名: SportsActivity 31 | */ 32 | public class SportsActivity extends BaseActivity { 33 | 34 | @BindView(R.id.tv_sport_mile) 35 | TextView tvSportMile; 36 | @BindView(R.id.tv_sport_count) 37 | TextView tvSportCount; 38 | @BindView(R.id.tv_sport_time) 39 | TextView tvSportTime; 40 | @BindView(R.id.btStart) 41 | Button btStart; 42 | 43 | private DecimalFormat decimalFormat = new DecimalFormat("0.00"); 44 | 45 | private final int SPORT = 0x0012; 46 | 47 | private DataManager dataManager = null; 48 | 49 | @Override 50 | public int getLayoutId() { 51 | return R.layout.activity_sports; 52 | } 53 | 54 | @Override 55 | public void initData(Bundle savedInstanceState) { 56 | dataManager = new DataManager(new RealmHelper()); 57 | 58 | upDateUI(); 59 | } 60 | 61 | @Override 62 | public void initListener() { 63 | 64 | } 65 | 66 | @OnClick(R.id.btStart) 67 | public void onViewClicked() { 68 | PermissionHelper.requestPermissions(this, Permissions.PERMISSIONS_LOCATION, 69 | getResources().getString(R.string.app_name) + "需要获取位置", new PermissionListener() { 70 | @Override 71 | public void onPassed() { 72 | startActivityForResult(new Intent(SportsActivity.this, SportMapActivity.class), SPORT); 73 | } 74 | }); 75 | } 76 | 77 | private void upDateUI() { 78 | try { 79 | List records = dataManager.queryRecordList(Integer.parseInt(SPUtils.getInstance().getString(MySp.USERID, "0"))); 80 | if (null != records) { 81 | 82 | double sportMile = 0; 83 | long sportTime = 0; 84 | for (SportMotionRecord record : records) { 85 | sportMile += record.getDistance(); 86 | sportTime += record.getDuration(); 87 | } 88 | tvSportMile.setText(decimalFormat.format(sportMile / 1000d)); 89 | tvSportCount.setText(String.valueOf(records.size())); 90 | tvSportTime.setText(decimalFormat.format((double) sportTime / 60d)); 91 | } 92 | } catch (Exception e) { 93 | LogUtils.e("获取运动数据失败", e); 94 | } 95 | } 96 | 97 | @Override 98 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 99 | super.onActivityResult(requestCode, resultCode, data); 100 | if (resultCode != RESULT_OK) 101 | return; 102 | switch (requestCode) { 103 | case SPORT: 104 | upDateUI(); 105 | setResult(RESULT_OK); 106 | break; 107 | default: 108 | break; 109 | } 110 | } 111 | 112 | @Override 113 | protected void onDestroy() { 114 | if (null != dataManager) 115 | dataManager.closeRealm(); 116 | super.onDestroy(); 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/adapter/SportCalendarAdapter.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.adapter; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import com.chad.library.adapter.base.BaseQuickAdapter; 6 | import com.chad.library.adapter.base.BaseViewHolder; 7 | import com.james.motion.R; 8 | import com.james.motion.commmon.bean.PathRecord; 9 | import com.james.motion.sport_motion.MotionUtils; 10 | 11 | import java.text.DecimalFormat; 12 | import java.util.List; 13 | 14 | /** 15 | * 描述: 运动日历 16 | * 作者: james 17 | * 日期: 2019/2/27 14:02 18 | * 类名: SportCalendarAdapter 19 | */ 20 | public class SportCalendarAdapter extends BaseQuickAdapter { 21 | 22 | private DecimalFormat decimalFormat = new DecimalFormat("0.00"); 23 | private DecimalFormat intFormat = new DecimalFormat("#"); 24 | 25 | public SportCalendarAdapter(int layoutResId, @Nullable List data) { 26 | super(layoutResId, data); 27 | } 28 | 29 | @Override 30 | protected void convert(BaseViewHolder helper, PathRecord item) { 31 | helper.setText(R.id.distance, decimalFormat.format(item.getDistance() / 1000d)); 32 | helper.setText(R.id.duration, MotionUtils.formatseconds(item.getDuration())); 33 | helper.setText(R.id.calorie, intFormat.format(item.getCalorie())); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/fragment/FastLoginFragment.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.fragment; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.os.SystemClock; 7 | import android.text.Editable; 8 | import android.text.TextUtils; 9 | import android.text.TextWatcher; 10 | import android.view.View; 11 | import android.view.WindowManager; 12 | import android.view.inputmethod.InputMethodManager; 13 | import android.widget.Chronometer; 14 | import android.widget.EditText; 15 | import android.widget.ImageButton; 16 | 17 | import com.blankj.utilcode.util.SPUtils; 18 | import com.blankj.utilcode.util.ToastUtils; 19 | import com.james.motion.R; 20 | import com.james.motion.commmon.utils.Conn; 21 | import com.james.motion.commmon.utils.MySp; 22 | import com.james.motion.commmon.utils.UIHelper; 23 | import com.james.motion.commmon.utils.Utils; 24 | import com.james.motion.ui.BaseFragment; 25 | 26 | import butterknife.BindView; 27 | import butterknife.OnClick; 28 | 29 | /** 30 | * 描述: 验证码快速登录 31 | * 作者: james 32 | * 日期: 2019/1/4 18:15 33 | * 类名: FastLoginFragment 34 | */ 35 | public class FastLoginFragment extends BaseFragment { 36 | 37 | @BindView(R.id.et_phone) 38 | EditText etPhone; 39 | @BindView(R.id.et_code) 40 | EditText etCode; 41 | @BindView(R.id.btClear) 42 | ImageButton btClear; 43 | @BindView(R.id.chronometer) 44 | Chronometer chronometer; 45 | 46 | private String code = "-1"; 47 | 48 | @Override 49 | public int getLayoutId() { 50 | return R.layout.fragment_fastlogin; 51 | } 52 | 53 | @Override 54 | public void initData(Bundle savedInstanceState) { 55 | 56 | String loginname = SPUtils.getInstance().getString(MySp.PHONE); 57 | String password = SPUtils.getInstance().getString(MySp.PASSWORD); 58 | if (!TextUtils.isEmpty(loginname)) { 59 | btClear.setVisibility(View.VISIBLE); 60 | etPhone.setText(loginname); 61 | } 62 | 63 | chronometer.setText("获取验证码"); 64 | } 65 | 66 | @Override 67 | public void initListener() { 68 | chronometer.setOnChronometerTickListener(chronometer -> { 69 | long time = (Long) chronometer.getTag() - SystemClock.elapsedRealtime() / 1000; 70 | if (time > 0) { 71 | chronometer.setText(UIHelper.getString(R.string.chronometer_time, time)); 72 | } else { 73 | chronometer.setText("重新获取"); 74 | chronometer.stop(); 75 | chronometer.setEnabled(true); 76 | } 77 | }); 78 | etPhone.addTextChangedListener(new TextWatcher() { 79 | @Override 80 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 81 | 82 | } 83 | 84 | @Override 85 | public void onTextChanged(CharSequence s, int start, int before, int count) { 86 | btClear.setVisibility(s.length() > 0 ? View.VISIBLE : View.GONE); 87 | } 88 | 89 | @Override 90 | public void afterTextChanged(Editable s) { 91 | 92 | } 93 | }); 94 | } 95 | 96 | @OnClick({R.id.chronometer, R.id.btClear}) 97 | public void onViewClicked(View view) { 98 | switch (view.getId()) { 99 | case R.id.chronometer: 100 | String phone = etPhone.getText().toString(); 101 | if (TextUtils.isEmpty(phone)) { 102 | ToastUtils.showShort("请输入11位手机号码"); 103 | return; 104 | } 105 | if (!Utils.isMobile(phone)) { 106 | ToastUtils.showShort("请输入正确的手机号码"); 107 | return; 108 | } 109 | // 先影藏输入法 110 | InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); 111 | imm.hideSoftInputFromWindow(etPhone.getWindowToken(), 0); 112 | 113 | yanZhengMa(); 114 | 115 | break; 116 | case R.id.btClear: 117 | etPhone.setText(""); 118 | break; 119 | default: 120 | break; 121 | } 122 | } 123 | 124 | public void yzmStart() { 125 | chronometer.setTag(SystemClock.elapsedRealtime() / 1000 + 60); 126 | chronometer.setText("(60)重新获取"); 127 | chronometer.start(); 128 | chronometer.setEnabled(false); 129 | 130 | getActivity().getWindow() 131 | .setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); 132 | } 133 | 134 | /** 135 | * 获取验证码 136 | */ 137 | public void yanZhengMa() { 138 | showLoadingView(); 139 | new Handler().postDelayed(() -> { 140 | dismissLoadingView(); 141 | int numcode = (int) ((Math.random() * 9 + 1) * 100000); 142 | code = numcode + ""; 143 | yzmStart(); 144 | ToastUtils.showShort("验证获取成功!"); 145 | etCode.setText(code); 146 | }, Conn.Delayed); 147 | } 148 | 149 | public void checkAccount(CallBack callBack) { 150 | if (null != etPhone && null != etCode) { 151 | if (isInput(etPhone, etCode)) { 152 | String account = etPhone.getText().toString(); 153 | String codes = etCode.getText().toString(); 154 | if (!Utils.isMobile(account)) { 155 | ToastUtils.showShort("请输入正确的手机号码"); 156 | } else if (!TextUtils.equals(codes, code)) { 157 | ToastUtils.showShort("请输入正确的验证码!"); 158 | } else { 159 | callBack.getResult(account, codes); 160 | } 161 | } 162 | } 163 | } 164 | 165 | public interface CallBack { 166 | void getResult(String account, String psd); 167 | } 168 | 169 | private boolean isInput(EditText... e) { 170 | for (EditText anE : e) { 171 | if (TextUtils.isEmpty(anE.getText())) { 172 | ToastUtils.showShort(anE.getHint().toString()); 173 | return false; 174 | } 175 | } 176 | return true; 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/fragment/PsdLoginFragment.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.text.Editable; 5 | import android.text.InputType; 6 | import android.text.TextUtils; 7 | import android.text.TextWatcher; 8 | import android.view.View; 9 | import android.widget.EditText; 10 | import android.widget.ImageButton; 11 | 12 | import com.blankj.utilcode.util.SPUtils; 13 | import com.blankj.utilcode.util.ToastUtils; 14 | import com.james.motion.R; 15 | import com.james.motion.commmon.utils.MySp; 16 | import com.james.motion.ui.BaseFragment; 17 | 18 | import butterknife.BindView; 19 | import butterknife.OnClick; 20 | 21 | /** 22 | * 描述: 密码登录 23 | * 作者: james 24 | * 日期: 2019/1/4 18:15 25 | * 类名: PsdLoginFragment 26 | */ 27 | public class PsdLoginFragment extends BaseFragment { 28 | 29 | @BindView(R.id.et_username) 30 | EditText etUsername; 31 | @BindView(R.id.et_psd) 32 | EditText etPsd; 33 | @BindView(R.id.btClear) 34 | ImageButton btClear; 35 | @BindView(R.id.btPsd) 36 | ImageButton btPsd; 37 | 38 | private boolean iscleartext = false; 39 | 40 | @Override 41 | public int getLayoutId() { 42 | return R.layout.fragment_psdlogin; 43 | } 44 | 45 | @Override 46 | public void initData(Bundle savedInstanceState) { 47 | String loginname = SPUtils.getInstance().getString(MySp.PHONE); 48 | String password = SPUtils.getInstance().getString(MySp.PASSWORD); 49 | if (!TextUtils.isEmpty(loginname)) { 50 | btClear.setVisibility(View.VISIBLE); 51 | etUsername.setText(loginname); 52 | } 53 | } 54 | 55 | @Override 56 | public void initListener() { 57 | etUsername.addTextChangedListener(new TextWatcher() { 58 | @Override 59 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 60 | 61 | } 62 | 63 | @Override 64 | public void onTextChanged(CharSequence s, int start, int before, int count) { 65 | btClear.setVisibility(s.length() > 0 ? View.VISIBLE : View.GONE); 66 | } 67 | 68 | @Override 69 | public void afterTextChanged(Editable s) { 70 | 71 | } 72 | }); 73 | } 74 | 75 | @OnClick({R.id.btPsd, R.id.btClear, R.id.tvForget}) 76 | public void onViewClicked(View view) { 77 | switch (view.getId()) { 78 | case R.id.btPsd: 79 | if (TextUtils.isEmpty(etPsd.getText())) { 80 | ToastUtils.showShort("请先输入密码!"); 81 | return; 82 | } 83 | if (!iscleartext) { 84 | etPsd.setInputType(InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 85 | btPsd.setImageResource(R.mipmap.icon_psd_h); 86 | } else { 87 | etPsd.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); 88 | btPsd.setImageResource(R.mipmap.icon_psd_s); 89 | } 90 | iscleartext = !iscleartext; 91 | etPsd.setSelection(etPsd.getText().length()); 92 | break; 93 | case R.id.btClear: 94 | etUsername.setText(""); 95 | break; 96 | case R.id.tvForget: 97 | ToastUtils.showShort("功能开发中..."); 98 | break; 99 | default: 100 | break; 101 | } 102 | } 103 | 104 | public void checkAccount(CallBack callBack) { 105 | if (isInput(etUsername, etPsd)) { 106 | String account = etUsername.getText().toString(); 107 | String psd = etPsd.getText().toString(); 108 | if (psd.length() < 4) { 109 | ToastUtils.showShort("请输入正确的密码!"); 110 | } else { 111 | callBack.getResult(account, psd); 112 | } 113 | } 114 | } 115 | 116 | 117 | public interface CallBack { 118 | void getResult(String account, String psd); 119 | } 120 | 121 | private boolean isInput(EditText... e) { 122 | for (EditText anE : e) { 123 | if (TextUtils.isEmpty(anE.getText())) { 124 | ToastUtils.showShort(anE.getHint().toString()); 125 | return false; 126 | } 127 | } 128 | return true; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/fragment/SportRecordDetailsFragment.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.widget.TextView; 5 | 6 | import com.james.motion.R; 7 | import com.james.motion.commmon.bean.PathRecord; 8 | import com.james.motion.sport_motion.MotionUtils; 9 | import com.james.motion.ui.BaseFragment; 10 | import com.james.motion.ui.activity.SportRecordDetailsActivity; 11 | 12 | import java.text.DecimalFormat; 13 | 14 | import butterknife.BindView; 15 | 16 | /** 17 | * 描述: 运动记录详情-详情 18 | * 作者: james 19 | * 日期: 2019/2/27 15:25 20 | * 类名: SportRecordDetailsFragment 21 | */ 22 | public class SportRecordDetailsFragment extends BaseFragment { 23 | 24 | @BindView(R.id.tvDistance) 25 | TextView tvDistance; 26 | @BindView(R.id.tvDuration) 27 | TextView tvDuration; 28 | @BindView(R.id.tvSpeed) 29 | TextView tvSpeed; 30 | @BindView(R.id.tvDistribution) 31 | TextView tvDistribution; 32 | @BindView(R.id.tvCalorie) 33 | TextView tvCalorie; 34 | 35 | private PathRecord pathRecord = null; 36 | 37 | private DecimalFormat decimalFormat = new DecimalFormat("0.00"); 38 | private DecimalFormat intFormat = new DecimalFormat("#"); 39 | 40 | @Override 41 | public int getLayoutId() { 42 | return R.layout.fragment_sportrecorddetails; 43 | } 44 | 45 | @Override 46 | public void initData(Bundle savedInstanceState) { 47 | Bundle bundle = getArguments(); 48 | if (bundle != null) { 49 | pathRecord = bundle.getParcelable(SportRecordDetailsActivity.SPORT_DATA); 50 | } 51 | 52 | if (null != pathRecord) { 53 | tvDistance.setText(decimalFormat.format(pathRecord.getDistance() / 1000d)); 54 | tvDuration.setText(MotionUtils.formatseconds(pathRecord.getDuration())); 55 | tvSpeed.setText(decimalFormat.format(pathRecord.getSpeed())); 56 | tvDistribution.setText(decimalFormat.format(pathRecord.getDistribution())); 57 | tvCalorie.setText(intFormat.format(pathRecord.getCalorie())); 58 | } 59 | 60 | } 61 | 62 | @Override 63 | public void initListener() { 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/fragment/SportRecordDetailsSpeedFragment.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.widget.TextView; 5 | 6 | import com.james.motion.R; 7 | import com.james.motion.commmon.bean.PathRecord; 8 | import com.james.motion.ui.BaseFragment; 9 | import com.james.motion.ui.activity.SportRecordDetailsActivity; 10 | 11 | import java.text.DecimalFormat; 12 | 13 | import butterknife.BindView; 14 | 15 | /** 16 | * 描述: 运动记录详情-配速 17 | * 作者: james 18 | * 日期: 2019/2/27 15:25 19 | * 类名: SportRecordDetailsSpeedFragment 20 | */ 21 | public class SportRecordDetailsSpeedFragment extends BaseFragment { 22 | 23 | @BindView(R.id.tvDistribution) 24 | TextView tvDistribution; 25 | 26 | private PathRecord pathRecord = null; 27 | 28 | private DecimalFormat decimalFormat = new DecimalFormat("0.00"); 29 | 30 | @Override 31 | public int getLayoutId() { 32 | return R.layout.fragment_sportrecorddetailsspeed; 33 | } 34 | 35 | @Override 36 | public void initData(Bundle savedInstanceState) { 37 | Bundle bundle = getArguments(); 38 | if (bundle != null) { 39 | pathRecord = bundle.getParcelable(SportRecordDetailsActivity.SPORT_DATA); 40 | } 41 | 42 | if (null != pathRecord) 43 | tvDistribution.setText(decimalFormat.format(pathRecord.getDistribution())); 44 | } 45 | 46 | @Override 47 | public void initListener() { 48 | 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/permission/APermission.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.permission; 2 | 3 | import android.app.Activity; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.core.app.ActivityCompat; 7 | import androidx.fragment.app.FragmentActivity; 8 | 9 | import com.blankj.utilcode.util.ToastUtils; 10 | import com.james.motion.commmon.utils.DeviceHelper; 11 | import com.james.motion.commmon.utils.LogUtils; 12 | 13 | import java.util.Arrays; 14 | 15 | public class APermission { 16 | 17 | private Builder builder; 18 | 19 | public static Builder builder(FragmentActivity mActivity) { 20 | return new Builder(mActivity); 21 | } 22 | 23 | public static Builder builder(Activity mActivity) { 24 | return new Builder(mActivity); 25 | } 26 | 27 | public static class Builder { 28 | private String[] permissions; 29 | private FragmentActivity mActivity; 30 | private PermissionListener permissionListener; 31 | private String tipContent = null; 32 | private int code = Permissions.PERMISSION_REQUEST_CODE; 33 | 34 | private Builder(FragmentActivity activity) { 35 | this.mActivity = activity; 36 | } 37 | 38 | private Builder(Activity activity) { 39 | this.mActivity = (FragmentActivity) activity; 40 | } 41 | 42 | public Builder setCode(int code) { 43 | this.code = code; 44 | return this; 45 | } 46 | 47 | public Builder setTipContent(String tipContent) { 48 | this.tipContent = tipContent; 49 | return this; 50 | } 51 | 52 | public Builder callBack(PermissionListener permissionListener) { 53 | 54 | this.permissionListener = permissionListener; 55 | return this; 56 | } 57 | 58 | public Builder setPermissions(String[] permissions) { 59 | LogUtils.d("permissionListener--" + "setPermissions" + Arrays.toString(permissions)); 60 | this.permissions = permissions; 61 | return this; 62 | } 63 | 64 | public APermission request() { 65 | APermission axdPermission = new APermission(); 66 | axdPermission.builder = this; 67 | if (!(permissions == null || permissions.length == 0)) { 68 | axdPermission.requestPermission(); 69 | } 70 | if (mActivity instanceof PermissionActivity) { 71 | ((PermissionActivity) mActivity).setAxdPermission(axdPermission); 72 | } 73 | return axdPermission; 74 | } 75 | } 76 | 77 | PermissionListener getPermissionListener() { 78 | if (builder == null) { 79 | return null; 80 | } 81 | return builder.permissionListener; 82 | } 83 | 84 | private void requestPermission() { 85 | if (PermissionUtil.hasSelfPermissions(builder.mActivity, builder.permissions) || !DeviceHelper.isOverMarshmallow()) { 86 | builder.permissionListener.onPassed(); 87 | } else { 88 | String[] unPassPermissions = PermissionUtil.cutPassPermissions(builder.mActivity, builder.permissions); 89 | if (unPassPermissions.length == 0) { 90 | builder.permissionListener.onPassed(); 91 | return; 92 | } 93 | ActivityCompat.requestPermissions(builder.mActivity, unPassPermissions, builder.code); 94 | } 95 | } 96 | 97 | void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 98 | if ((requestCode == builder.code && PermissionUtil.verifyPermissions(grantResults)) 99 | || PermissionUtil.hasSelfPermissions(builder.mActivity, builder.permissions)) { 100 | builder.permissionListener.onPassed(); 101 | } else { 102 | boolean neverAsk = !PermissionUtil.shouldShowRequestPermissionRationale(builder.mActivity, permissions); 103 | if (!builder.permissionListener.onDenied(neverAsk)) { 104 | if (neverAsk) { 105 | ToastUtils.showShort("权限缺失"); 106 | } else { 107 | ToastUtils.showShort("权限缺失"); 108 | } 109 | } 110 | 111 | } 112 | } 113 | 114 | /** 115 | * 从设置页面返回后 116 | */ 117 | public void onSettingResult(@NonNull APermission axdPermission) { 118 | axdPermission.requestPermission(); 119 | } 120 | 121 | public void destroy() { 122 | builder = null; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/permission/PermissionActivity.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.permission; 2 | 3 | import android.content.Intent; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.appcompat.app.AppCompatActivity; 7 | 8 | import com.james.motion.commmon.utils.LogUtils; 9 | 10 | public abstract class PermissionActivity extends AppCompatActivity { 11 | 12 | private APermission axdPermission; 13 | 14 | public void setAxdPermission(APermission axdPermission) { 15 | this.axdPermission = axdPermission; 16 | } 17 | 18 | @Override 19 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 20 | super.onActivityResult(requestCode, resultCode, data); 21 | if (requestCode == Permissions.PERMISSION_SETTING_CODE) { 22 | onSettingResult(requestCode); 23 | } 24 | } 25 | 26 | 27 | protected void onSettingResult(int requestCode) { 28 | LogUtils.d("permission--" + "setting" + requestCode); 29 | if (axdPermission != null) { 30 | axdPermission.onSettingResult(axdPermission); 31 | } 32 | } 33 | 34 | /** 35 | * 权限请求结果 36 | */ 37 | @Override 38 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 39 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 40 | if (axdPermission != null && axdPermission.getPermissionListener() != null) { 41 | axdPermission.onRequestPermissionsResult(requestCode, permissions, grantResults); 42 | } 43 | } 44 | 45 | 46 | @Override 47 | protected void onDestroy() { 48 | if (axdPermission != null) { 49 | axdPermission.destroy(); 50 | axdPermission = null; 51 | } 52 | super.onDestroy(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/permission/PermissionHelper.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.permission; 2 | 3 | import android.app.Activity; 4 | 5 | public class PermissionHelper { 6 | /** 7 | * 申请权限,固定重试提示 8 | */ 9 | public static void requestPermissions(Activity mActivity, String[] permission, final PermissionListener callback) { 10 | APermission.builder(mActivity) 11 | .setPermissions(permission) 12 | .callBack(callback).request(); 13 | } 14 | /** 15 | * 申请权限,自定义重试提示文字 16 | */ 17 | public static void requestPermissions(Activity mActivity, String[] permission, String tipContent, final PermissionListener callback) { 18 | APermission.builder(mActivity) 19 | .setPermissions(permission) 20 | .setTipContent(tipContent) 21 | .callBack(callback).request(); 22 | } 23 | 24 | public static boolean hasMustPermission(Activity activity) { 25 | return PermissionUtil.hasSelfPermissions(activity, Permissions.PERMISSIONS_FIRST); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/permission/PermissionListener.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.permission; 2 | 3 | public abstract class PermissionListener { 4 | /** 5 | * 权限通过 6 | */ 7 | public abstract void onPassed(); 8 | 9 | /** 10 | * 权限拒绝 11 | * neverAsk: 不再询问 12 | * 13 | * @return 如果要覆盖原有提示则返回true 14 | */ 15 | public boolean onDenied(boolean neverAsk) { 16 | return false; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/permission/PermissionRetryCallBack.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.permission; 2 | 3 | public interface PermissionRetryCallBack { 4 | void onClick(); 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/permission/PermissionUtil.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.permission; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.pm.PackageManager; 7 | import android.net.Uri; 8 | import android.os.Build; 9 | import android.provider.Settings; 10 | 11 | import androidx.collection.SimpleArrayMap; 12 | import androidx.core.app.ActivityCompat; 13 | import androidx.core.content.ContextCompat; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | /** 19 | * 权限工具类 20 | */ 21 | public final class PermissionUtil { 22 | 23 | 24 | private static final SimpleArrayMap MIN_SDK_PERMISSIONS; 25 | 26 | static { 27 | MIN_SDK_PERMISSIONS = new SimpleArrayMap<>(8); 28 | MIN_SDK_PERMISSIONS.put("com.android.voicemail.permission.ADD_VOICEMAIL", 14); 29 | MIN_SDK_PERMISSIONS.put("android.permission.BODY_SENSORS", 20); 30 | MIN_SDK_PERMISSIONS.put("android.permission.READ_CALL_LOG", 16); 31 | MIN_SDK_PERMISSIONS.put("android.permission.READ_EXTERNAL_STORAGE", 16); 32 | MIN_SDK_PERMISSIONS.put("android.permission.USE_SIP", 9); 33 | MIN_SDK_PERMISSIONS.put("android.permission.WRITE_CALL_LOG", 16); 34 | MIN_SDK_PERMISSIONS.put("android.permission.SYSTEM_ALERT_WINDOW", 23); 35 | MIN_SDK_PERMISSIONS.put("android.permission.WRITE_SETTINGS", 23); 36 | } 37 | 38 | /** 39 | * 检测所有权限是否通过 40 | */ 41 | public static boolean verifyPermissions(int... grantResults) { 42 | if (grantResults.length == 0) { 43 | return false; 44 | } 45 | for (int result : grantResults) { 46 | if (result != PackageManager.PERMISSION_GRANTED) { 47 | return false; 48 | } 49 | } 50 | return true; 51 | } 52 | 53 | /** 54 | * 检测是否拥有全部权限 55 | */ 56 | public static boolean hasSelfPermissions(Context context, String... permissions) { 57 | for (String permission : permissions) { 58 | if (!hasSelfPermission(context, permission)) { 59 | return false; 60 | } 61 | } 62 | return true; 63 | } 64 | 65 | /** 66 | * 检测在某版本中是否存在该权限 67 | */ 68 | private static boolean permissionExists(String permission) { 69 | Integer minVersion = MIN_SDK_PERMISSIONS.get(permission); 70 | 71 | return minVersion == null || Build.VERSION.SDK_INT >= minVersion; 72 | } 73 | 74 | /** 75 | * 是否拥有该权限 76 | */ 77 | public static boolean hasSelfPermission(Context context, String permission) { 78 | try { 79 | return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; 80 | } catch (RuntimeException t) { 81 | return false; 82 | } 83 | } 84 | 85 | /** 86 | * 检测是否有权限一直被拒绝 87 | */ 88 | public static boolean shouldShowRequestPermissionRationale(Activity activity, String... permissions) { 89 | for (String permission : permissions) { 90 | if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) { 91 | return true; 92 | } 93 | } 94 | return false; 95 | } 96 | 97 | /** 98 | * 过滤掉已经申请通过的权限 99 | */ 100 | public static String[] cutPassPermissions(Activity activity, String[] permissions) { 101 | List stringList = new ArrayList<>(); 102 | for (String permission : permissions) { 103 | if (!hasSelfPermission(activity, permission)) { 104 | stringList.add(permission); 105 | } 106 | } 107 | String strings[] = new String[stringList.size()]; 108 | for (int i = 0, j = stringList.size(); i < j; i++) { 109 | strings[i] = stringList.get(i); 110 | } 111 | return strings; 112 | } 113 | 114 | /** 115 | * 启动应用的设置 116 | */ 117 | public static void startAppSettings(Activity activity, int requestCode) { 118 | Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 119 | Uri uri = Uri.fromParts("package", activity.getPackageName(), null); 120 | intent.setData(uri); 121 | activity.startActivityForResult(intent, requestCode); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/permission/Permissions.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.permission; 2 | 3 | import android.Manifest; 4 | 5 | public interface Permissions { 6 | /** 7 | * 权限申请CODE 8 | */ 9 | int PERMISSION_REQUEST_CODE = 400; 10 | 11 | int PERMISSION_SETTING_CODE = 401; 12 | 13 | /** 14 | * 进入app请求的权限 15 | */ 16 | String[] PERMISSIONS_FIRST = new String[]{ 17 | Manifest.permission.READ_PHONE_STATE, 18 | Manifest.permission.READ_EXTERNAL_STORAGE, 19 | }; 20 | String[] PERMISSIONS_HOME = new String[]{ 21 | Manifest.permission.READ_CONTACTS, 22 | Manifest.permission.ACCESS_FINE_LOCATION 23 | }; 24 | String[] PERMISSIONS_BIOSPSY_AUDIO = new String[]{ 25 | Manifest.permission.CAMERA, 26 | Manifest.permission.RECORD_AUDIO, 27 | Manifest.permission.READ_EXTERNAL_STORAGE 28 | }; 29 | String[] PERMISSIONS_BIOSPSY = new String[]{ 30 | Manifest.permission.CAMERA, 31 | Manifest.permission.READ_EXTERNAL_STORAGE 32 | }; 33 | String[] PERMISSIONS_PHONE_STATE = new String[]{ 34 | Manifest.permission.READ_PHONE_STATE 35 | }; 36 | String[] PERMISSIONS_CAMERA = new String[]{ 37 | Manifest.permission.CAMERA, 38 | Manifest.permission.RECORD_AUDIO, 39 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 40 | Manifest.permission.READ_EXTERNAL_STORAGE 41 | }; 42 | String[] PERMISSIONS_GALLERY = new String[]{ 43 | Manifest.permission.READ_EXTERNAL_STORAGE, 44 | Manifest.permission.WRITE_EXTERNAL_STORAGE 45 | }; 46 | String[] PERMISSIONS_RECORD_AUDIO = new String[]{ 47 | Manifest.permission.RECORD_AUDIO, 48 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 49 | Manifest.permission.WAKE_LOCK, 50 | Manifest.permission.READ_EXTERNAL_STORAGE 51 | }; 52 | String[] PERMISSIONS_SMS = new String[]{ 53 | Manifest.permission.READ_SMS, 54 | Manifest.permission.SEND_SMS 55 | }; 56 | String[] PERMISSIONS_CONTACTS = { 57 | Manifest.permission.READ_CONTACTS, 58 | Manifest.permission.WRITE_CONTACTS}; 59 | String[] PERMISSIONS_LOCATION = new String[]{ 60 | Manifest.permission.ACCESS_FINE_LOCATION, 61 | Manifest.permission.ACCESS_COARSE_LOCATION 62 | }; 63 | 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/weight/AMapScrollViewPager.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.weight; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | 7 | import androidx.viewpager.widget.ViewPager; 8 | 9 | /** 10 | * 描述: 重写ViewPager canScroll方法,解决ViewPager和地图横向滑动冲突 11 | * 作者: james 12 | * 日期: 2019/2/27 15:16 13 | * 类名: AMapScrollViewPager 14 | */ 15 | public class AMapScrollViewPager extends ViewPager { 16 | 17 | public AMapScrollViewPager(Context context) { 18 | super(context); 19 | } 20 | 21 | public AMapScrollViewPager(Context context, AttributeSet attrs) { 22 | super(context, attrs); 23 | } 24 | 25 | @Override 26 | public boolean canScroll(View v, boolean checkV, int dx, int x, int y) { 27 | if (Math.abs(dx) > 50) { 28 | return super.canScroll(v, checkV, dx, x, y); 29 | } else { 30 | return true; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/weight/CustomProgressDialog.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.weight; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.WindowManager; 9 | 10 | import com.james.motion.R; 11 | 12 | /** 13 | * @describe 自定义进度条 14 | */ 15 | public class CustomProgressDialog extends Dialog { 16 | 17 | private Context mContext; 18 | 19 | public CustomProgressDialog(final Context context) { 20 | // TODO Auto-generated constructor stub 21 | super(context, R.style.customProgressDialog); 22 | this.mContext = context; 23 | 24 | // 设置点击进度条外部取消 25 | this.setCanceledOnTouchOutside(false); 26 | // 设置点击进度可以取消 27 | this.setCancelable(false); 28 | 29 | setTouchAble(true); 30 | } 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 36 | 37 | View rootView = LayoutInflater.from(mContext).inflate(R.layout.custom_progress_dialog_layout, null); 38 | 39 | setContentView(rootView); 40 | } 41 | 42 | /** 43 | * 设置是否可点击下一层 true:可点击 false:不可点击 默认是可点击 44 | * 45 | * @param isTouchAble 46 | */ 47 | public void setTouchAble(boolean isTouchAble) { 48 | 49 | if (isTouchAble) { 50 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, 51 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); 52 | 53 | //显示阴影 54 | WindowManager.LayoutParams lp = getWindow().getAttributes(); 55 | lp.dimAmount = 0.3f; 56 | getWindow().setAttributes(lp); 57 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 58 | 59 | //不显示阴影 60 | // getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 61 | 62 | } else { 63 | getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); 64 | WindowManager.LayoutParams lp = getWindow().getAttributes(); 65 | lp.dimAmount = 0.3f; 66 | getWindow().setAttributes(lp); 67 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/weight/calendarview/custom/CustomWeekBar.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.weight.calendarview.custom; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.view.LayoutInflater; 6 | import android.widget.TextView; 7 | 8 | import com.haibin.calendarview.Calendar; 9 | import com.haibin.calendarview.WeekBar; 10 | import com.james.motion.R; 11 | 12 | /** 13 | * 自定义英文栏 14 | */ 15 | public class CustomWeekBar extends WeekBar { 16 | 17 | private int mPreSelectedIndex; 18 | 19 | public CustomWeekBar(Context context) { 20 | super(context); 21 | LayoutInflater.from(context).inflate(R.layout.custom_week_bar, this, true); 22 | setBackgroundColor(Color.WHITE); 23 | } 24 | 25 | @Override 26 | protected void onDateSelected(Calendar calendar, int weekStart, boolean isClick) { 27 | getChildAt(mPreSelectedIndex).setSelected(false); 28 | int viewIndex = getViewIndexByCalendar(calendar, weekStart); 29 | getChildAt(viewIndex).setSelected(true); 30 | mPreSelectedIndex = viewIndex; 31 | } 32 | 33 | /** 34 | * 当周起始发生变化,使用自定义布局需要重写这个方法,避免出问题 35 | * 36 | * @param weekStart 周起始 37 | */ 38 | @Override 39 | protected void onWeekStartChange(int weekStart) { 40 | for (int i = 0; i < getChildCount(); i++) { 41 | ((TextView) getChildAt(i)).setText(getWeekString(i, weekStart)); 42 | } 43 | } 44 | 45 | /** 46 | * 或者周文本,这个方法仅供父类使用 47 | * @param index index 48 | * @param weekStart weekStart 49 | * @return 或者周文本 50 | */ 51 | private String getWeekString(int index, int weekStart) { 52 | String[] weeks = getContext().getResources().getStringArray(R.array.chinese_week_string_array); 53 | 54 | if (weekStart == 1) { 55 | return weeks[index]; 56 | } 57 | if (weekStart == 2) { 58 | return weeks[index == 6 ? 0 : index + 1]; 59 | } 60 | return weeks[index == 0 ? 6 : index - 1]; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/james/motion/ui/weight/calendarview/custom/CustomYearView.java: -------------------------------------------------------------------------------- 1 | package com.james.motion.ui.weight.calendarview.custom; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | 7 | import com.haibin.calendarview.Calendar; 8 | import com.haibin.calendarview.YearView; 9 | import com.james.motion.R; 10 | 11 | /** 12 | * 自定义年视图 13 | */ 14 | public class CustomYearView extends YearView { 15 | 16 | private int mTextPadding; 17 | /** 18 | * 闰年字体 19 | */ 20 | private Paint mLeapYearTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 21 | 22 | public CustomYearView(Context context) { 23 | super(context); 24 | mTextPadding = dipToPx(context, 3); 25 | 26 | mLeapYearTextPaint.setTextSize(dipToPx(context, 12)); 27 | mLeapYearTextPaint.setColor(0xffd1d1d1); 28 | mLeapYearTextPaint.setAntiAlias(true); 29 | mLeapYearTextPaint.setFakeBoldText(true); 30 | 31 | } 32 | 33 | @Override 34 | protected void onDrawMonth(Canvas canvas, int year, int month, int x, int y, int width, int height) { 35 | 36 | String text = getContext() 37 | .getResources() 38 | .getStringArray(R.array.month_string_array)[month - 1]; 39 | canvas.drawText(text, 40 | x + mItemWidth / 2 - mTextPadding, 41 | y + mMonthTextBaseLine, 42 | mMonthTextPaint); 43 | if (month == 2 && isLeapYear(year)) { 44 | float w = getTextWidth(mMonthTextPaint, text); 45 | 46 | canvas.drawText("闰年", 47 | x + mItemWidth / 2 - mTextPadding + w + dipToPx(getContext(), 6), 48 | y + mMonthTextBaseLine, 49 | mLeapYearTextPaint); 50 | } 51 | } 52 | 53 | private float getTextWidth(Paint paint, String text) { 54 | return paint.measureText(text); 55 | } 56 | 57 | /** 58 | * 是否是闰年 59 | * 60 | * @param year year 61 | * @return 是否是闰年 62 | */ 63 | private static boolean isLeapYear(int year) { 64 | return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); 65 | } 66 | 67 | 68 | @Override 69 | protected void onDrawWeek(Canvas canvas, int week, int x, int y, int width, int height) { 70 | String text = getContext().getResources().getStringArray(R.array.year_view_week_string_array)[week]; 71 | canvas.drawText(text, 72 | x + width / 2, 73 | y + mWeekTextBaseLine, 74 | mWeekTextPaint); 75 | } 76 | 77 | 78 | @Override 79 | protected boolean onDrawSelected(Canvas canvas, Calendar calendar, int x, int y, boolean hasScheme) { 80 | int cx = x + mItemWidth / 2; 81 | int cy = y + mItemHeight / 2; 82 | int radius = Math.min(mItemWidth, mItemHeight) / 8 * 5; 83 | canvas.drawCircle(cx, cy, radius, mSelectedPaint); 84 | return true; 85 | } 86 | 87 | @Override 88 | protected void onDrawScheme(Canvas canvas, Calendar calendar, int x, int y) { 89 | 90 | } 91 | 92 | @Override 93 | protected void onDrawText(Canvas canvas, Calendar calendar, int x, int y, boolean hasScheme, boolean isSelected) { 94 | float baselineY = mTextBaseLine + y; 95 | int cx = x + mItemWidth / 2; 96 | 97 | if (isSelected) { 98 | canvas.drawText(String.valueOf(calendar.getDay()), 99 | cx, 100 | baselineY, 101 | hasScheme ? mSchemeTextPaint : mSelectTextPaint); 102 | } else if (hasScheme) { 103 | canvas.drawText(String.valueOf(calendar.getDay()), 104 | cx, 105 | baselineY, 106 | calendar.isCurrentDay() ? mCurDayTextPaint : mSchemeTextPaint); 107 | 108 | } else { 109 | canvas.drawText(String.valueOf(calendar.getDay()), cx, baselineY, 110 | calendar.isCurrentDay() ? mCurDayTextPaint : mCurMonthTextPaint); 111 | } 112 | } 113 | 114 | /** 115 | * dp转px 116 | * 117 | * @param context context 118 | * @param dpValue dp 119 | * @return px 120 | */ 121 | private static int dipToPx(Context context, float dpValue) { 122 | final float scale = context.getResources().getDisplayMetrics().density; 123 | return (int) (dpValue * scale + 0.5f); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_left_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_left_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_right_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_right_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/mylocation_point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aqx1991/Motion/0368b18db1b63425a9139e89320075100ac7cc59/app/src/main/res/drawable-hdpi/mylocation_point.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/bg_ripple.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/back_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/back_selector_n.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/back_selector_p.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_edittext.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_gray_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_green_box_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_green_box_selector_n.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_green_box_selector_p.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_green_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_green_selector_n.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_green_selector_p.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_nor_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_white_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_white_selector_n.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_white_selector_p.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/et_coner_box_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/et_coner_white_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/et_underline.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/green_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/layer_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/loadind_dialog_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sport_change_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sport_end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aqx1991/Motion/0368b18db1b63425a9139e89320075100ac7cc59/app/src/main/res/drawable/sport_end.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/sport_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aqx1991/Motion/0368b18db1b63425a9139e89320075100ac7cc59/app/src/main/res/drawable/sport_start.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/sport_walk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aqx1991/Motion/0368b18db1b63425a9139e89320075100ac7cc59/app/src/main/res/drawable/sport_walk.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/sport_white_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 21 | 22 | 34 | 35 | 36 | 37 | 42 | 43 |