Android大作业—乐道步走(HappyRunning)一款计步器和跑步运动轨迹记录Android APP

Android大作业——乐道步走(HappyRunning)

(一款计步器和跑步运动轨迹记录Android APP)

(作业要求体现四大组件Activity、Service、BroadCast Recevicer、Content provider,所以有些功能略显多余)

前言

这是一款轻量、简易的、采用高德地图SDK记录轨迹和三轴加速度传感器的跑步、计步软件

简要功能介绍

跑步模块
通过高德地图SDK记录每一次跑步的轨迹,并将每一天跑步的里程、时间、记录在数据库里,支持查看历史跑步记录。

计步模块
利用三轴加速度传感器,记录一天走过的步数、允许设置每天的锻炼计划,以及提供历史记录起到监督反省自己的作用
(奇奇怪怪我上传不了图片)
1~7图片链接

  1. 首次启动获取用户相关权限后进入应用
  2. 可以使用用户账号密码或者获取验证码登录的方式进入应用
  3. 通过手机号,获取验证码进行用户注册
  4. 查看跑步总次数和总时长,并且可以进入新一次跑步记录
  5. 跑步轨迹记录,可切换地图模式或普通模式
  6. 查看今日步数和设置锻炼计划,查看历史记录
  7. 设置每日步数计划和设置提醒时间

相关代码
全局样式,设置沉浸式和透明状态栏

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="windowNoTitle">true</item>
    </style>
<style name="splash" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="windowNoTitle">true</item>
    <item name="android:windowTranslucentStatus">true</item>
</style>

    <style name="NoActionBar" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowContentOverlay">@null</item>
        <item name="colorPrimary">@color/basecolor</item>
        <item name="colorPrimaryDark">@color/basecolorDeep</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="android:windowAnimationStyle">@style/activityAnimation</item>
    </style>

    <!-- animation 样式 -->
    <style name="activityAnimation" parent="@android:style/Animation">
        <item name="android:activityOpenEnterAnimation">@anim/slide_right_in</item>
        <item name="android:activityOpenExitAnimation">@anim/slide_left_out</item>
        <item name="android:activityCloseEnterAnimation">@anim/slide_left_in</item>
        <item name="android:activityCloseExitAnimation">@anim/slide_right_out</item>
    </style>

    <!--解决启动时白屏问题-->
    <style name="Theme.Start" parent="NoActionBar">
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowBackground">@color/white</item>
        <!--<item name="android:windowDisablePreview">true</item>-->
        <!--<item name="android:windowIsTranslucent">true</item>-->
    </style>
    <style name="TabRadioButton">
        <item name="android:layout_width">0dp</item>
        <item name="android:layout_weight">1</item>
        <item name="android:layout_height">match_parent</item>
        <item name="android:padding">5dp</item>
        <item name="android:gravity">center</item>
        <item name="android:button">@null</item>
        <item name="android:textSize">10sp</item>
        <item name="android:textColor">@color/tab_selector_text</item>
    </style>

    <style name="style_smile">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:gravity">center</item>
        <item name="android:layout_gravity">center</item>
    </style>
    <style name="style_text_large" parent="style_smile">
        <item name="android:textSize">@dimen/text_size_large</item>
    </style>
</resources>

启动、注册、登录(部分)

package com.example.happyrunning.ui.activity;

import android.Manifest;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import com.blankj.utilcode.util.SPUtils;
import com.example.happyrunning.MyApplication;
import com.example.happyrunning.R;
import com.example.happyrunning.commons.utils.Status_sp;
import com.example.happyrunning.commons.utils.UIhelper;
import com.example.happyrunning.commons.utils.Utils;
import com.example.happyrunning.ui.BaseActivity;
import com.example.happyrunning.ui.permission.PermissionHelper;
import com.example.happyrunning.ui.permission.PermissionListener;
import com.example.happyrunning.ui.weight.CountDownProgress;
import com.gyf.barlibrary.ImmersionBar;

import butterknife.BindView;

public class Splash extends BaseActivity {
    @BindView(R.id.img_url)
    ImageView img_url;
    @BindView(R.id.countDownProgressView)
    CountDownProgress countDownProgress;
    @BindView(R.id.versions)
    TextView versions;

    /**
     * 上一次点击返回键时间
     */
    private long lastBackPressed;

    /*
    上次点击返回键时间
     */
    private  static final int QUIT_INTERVAL=3000;

//    申请权限
    private static String[] PERMISSIONS_STORAGE=
        {
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.ACCESS_COARSE_LOCATION
        };

    @Override
    protected void initImmersionBar() {
        super.initImmersionBar();
        if (ImmersionBar.hasNavigationBar(this)) {
            ImmersionBar.with(this).transparentNavigationBar().init();
        }
    }

    @Override
    public int getLayoutId() {
        return R.layout.activity_splash;
    }

    @Override
    public void initData(Bundle savedInstanceState) {
        img_url.setImageResource(R.mipmap.splash_bg);

        versions.setText(UIhelper.getString(R.string.splash_appversionname, MyApplication.getAppVersionName()));
        showToast("初始化中,请稍后...");
        countDownProgress.setTimeMillis(2000);
        countDownProgress.setProgressType(CountDownProgress.ProgressType.COUNT_BACK);
        countDownProgress.start();
    }

    @Override
    public void initListener() {
        countDownProgress.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                countDownProgress.stop();

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    // 获取权限
                    PermissionHelper.requestPermissions(Splash.this, PERMISSIONS_STORAGE, new PermissionListener() {
                        @Override
                        public void onPassed() {
                            startActivity();
                        }
                    });
                } else {
                    Splash.this.startActivity();
                }
            }
        });

        countDownProgress.setProgressListener(new CountDownProgress.OnProgressListener() {
            @Override
            public void onProgress(int progress) {
                if (progress==0) {
                    // 版本判断。当手机系统大于 23 时,才有必要去判断权限是否获取
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        // 获取权限
                        PermissionHelper.requestPermissions(Splash.this, PERMISSIONS_STORAGE, Splash.this.getResources().getString(R.string.app_name) + "需要获取存储、位置权限", new PermissionListener() {
                            @Override
                            public void onPassed() {
                                startActivity();
                            }
                        });
                    } else {
                        Splash.this.startActivity();
                    }
                }
            }
        });
    }

    public void startActivity() {
        if (SPUtils.getInstance().getBoolean(Status_sp.ISLOGIN)) {
            startActivity(new Intent(Splash.this, MainActivity.class));
            finish();
        } else {
            startActivity(new Intent(Splash.this, Login.class));
            finish();
        }
        countDownProgress.stop();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            if (keyCode == KeyEvent.KEYCODE_BACK) { // 表示按返回键 时的操作
                long backPressed = System.currentTimeMillis();
                if (backPressed - lastBackPressed > QUIT_INTERVAL) {
                    lastBackPressed = backPressed;
                    Utils.showToast(Splash.this, "再按一次退出");
                } else {
                    if (countDownProgress != null) {
                        countDownProgress.stop();
                        countDownProgress.clearAnimation();
                    }
                    moveTaskToBack(false);
                    MyApplication.closeApp(this);
                    finish();
                }
            }
        }
        return false;
    }


}

package com.example.happyrunning.ui.activity;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;

import com.blankj.utilcode.util.SPUtils;
import com.example.happyrunning.MyApplication;
import com.example.happyrunning.R;
import com.example.happyrunning.commons.utils.Conn;
import com.example.happyrunning.commons.utils.Status_sp;
import com.example.happyrunning.commons.utils.Utils;
import com.example.happyrunning.db.DataManager;
import com.example.happyrunning.db.RealmHelper;
import com.example.happyrunning.ui.BaseActivity;
import com.example.happyrunning.ui.fragment.FastLoginFragment;
import com.example.happyrunning.ui.fragment.PwdLoginFragment;
import com.flyco.tablayout.SlidingTabLayout;

import java.util.ArrayList;

import butterknife.BindView;
import butterknife.OnClick;


public class Login extends BaseActivity {

    @BindView(R.id.slidingTabLayout)
    SlidingTabLayout slidingTabLayout;
    @BindView(R.id.vp)
    ViewPager vp;
    @BindView(R.id.btLogin)
    Button btLogin;
    @BindView(R.id.btReg)
    Button btReg;

    /**
     * 上次点击返回键的时间
     */
    private long lastBackPressed;

    //上次点击返回键的时间
    public static final int QUIT_INTERVAL = 2500;

    private final String[] mTitles = {"普通登录", "快速登录"};

    private ArrayList<Fragment> mFragments = new ArrayList<>();

    private boolean isPsd = true;//是否是密码登录

    private PwdLoginFragment psdLoginFragment = new PwdLoginFragment();
    private FastLoginFragment fastLoginFragment = new FastLoginFragment();

    private DataManager dataManager = null;

    @Override
    public int getLayoutId() {
        return R.layout.activity_login;
    }

    @Override
    public void initData(Bundle savedInstanceState) {

        dataManager = new DataManager(new RealmHelper());

        MyPagerAdapter mAdapter = new MyPagerAdapter(getSupportFragmentManager());
        vp.setAdapter(mAdapter);

        mFragments.add(psdLoginFragment);
        mFragments.add(fastLoginFragment);

        slidingTabLayout.setViewPager(vp, mTitles, this, mFragments);

        isPsd = true;
    }

    @Override
    public void initListener() {
        vp.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int i, float v, int i1) {

            }

            @Override
            public void onPageSelected(int i) {
                isPsd = i == 0;
            }

            @Override
            public void onPageScrollStateChanged(int i) {

            }
        });
    }

    @OnClick({R.id.container, R.id.btLogin, R.id.btReg})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.container:
                hideSoftKeyBoard();
                break;
            case R.id.btLogin:

                if (isPsd) {
                    psdLoginFragment.checkAccount(this::login);
                } else {
                    fastLoginFragment.checkAccount(this::login);
                }
                break;
            case R.id.btReg:
                startActivity(new Intent(Login.this, Regist.class));
                break;
            default:
                break;
        }
    }

    /**
     * 登录
     */
    public void login(String account, String psd) {
        btLogin.setEnabled(false);
        showLoadingView();
        new Handler().postDelayed(() -> {
            dismissLoadingView();
            btLogin.setEnabled(true);
            if (isPsd) {
                if (dataManager.checkAccount(account, psd))
                    loginSuccess(account, psd);
                else
                    showToast("账号或密码错误!");
            } else {
                if (dataManager.checkAccount(account))
                    loginSuccess(account, "");
                else
                    showToast("账号不存在!");
            }
        }, Conn.Delayed);
    }

    private void loginSuccess(String account, String psd) {
        SPUtils.getInstance().put(Status_sp.ISLOGIN, true);

        SPUtils.getInstance().put(Status_sp.USERID, account.substring(8));

        SPUtils.getInstance().put(Status_sp.PHONE, account);
        SPUtils.getInstance().put(Status_sp.PASSWORD, psd);

        startActivity(new Intent(Login.this, MainActivity.class));
        Utils.showToast(Login.this, "恭喜您,登录成功...");

        finish();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            if (keyCode == KeyEvent.KEYCODE_BACK) { // 表示按返回键 时的操作
                long backPressed = System.currentTimeMillis();
                if (backPressed - lastBackPressed > QUIT_INTERVAL) {
                    lastBackPressed = backPressed;
                    showToast("再按一次退出");
                } else {
                    moveTaskToBack(false);
                    MyApplication.closeApp(this);
                    finish();
                }
            }
        }
        return super.onKeyDown(keyCode, event);
    }

    private class MyPagerAdapter extends FragmentPagerAdapter {
        MyPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public int getCount() {
            return mFragments.size();
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return mTitles[position];
        }

        @Override
        public Fragment getItem(int position) {
            return mFragments.get(position);
        }
    }

    @Override
    protected void onDestroy() {
        if (null != dataManager)
            dataManager.closeRealm();
        super.onDestroy();
    }
}

具体代码看项目https://github.com/Aristochi/HappyRun

各页面采用RadioButton+Fragment的方式实现页面之间的滑动
SDK采用高德地图

package com.example.happyrunning.sport_motion;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;

import com.amap.api.location.AMapLocation;
import com.amap.api.location.AMapLocationClient;
import com.amap.api.location.AMapLocationClientOption;
import com.amap.api.location.AMapLocationListener;
import com.amap.api.maps.model.LatLng;
import com.example.happyrunning.commons.utils.LogUtils;
import com.example.happyrunning.sport_motion.servicecode.RecordService;
import com.example.happyrunning.sport_motion.servicecode.impl.RecordServiceImpl;

/**
 * 定位的Service类,用户在运动时此服务会在后台进行定位。
 */
public class LocationService extends Service {

    private InterfaceLocationed interfaceLocationed = null;

    public static final String TAG = "LocationService";

    public final IBinder mBinder = new LocalBinder();

    public class LocalBinder extends Binder {
        // 在Binder中定义一个自定义的接口用于数据交互
        // 这里直接把当前的服务传回给宿主
        public LocationService getService() {
            return LocationService.this;
        }
    }

    //定位的时间间隔,单位是毫秒
    private static final int LOCATION_SPAN = 10 * 1000;

    //高德地图中定位的类
    public AMapLocationClient mLocationClient = null;
    //记录着运动中移动的坐标位置
//    private List<LatLng> mSportLatLngs = new LinkedList<>();

    //记录运动信息的Service
    private RecordService mRecordService = null;

    @Override
    public void onCreate() {
        super.onCreate();

        //声明LocationClient类
        mLocationClient = new AMapLocationClient(this);
        //给定位类加入自定义的配置
        initLocationOption();
        //注册监听函数
        mLocationClient.setLocationListener(MyAMapLocationListener);

        //初始化信息记录类
        mRecordService = new RecordServiceImpl(this);

        //启动定位
        mLocationClient.startLocation();
    }

    //初始化定位的配置
    private void initLocationOption() {
        AMapLocationClientOption mOption = new AMapLocationClientOption();
        mOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);//可选,设置定位模式,可选的模式有高精度、仅设备、仅网络。默认为高精度模式
        mOption.setGpsFirst(true);//可选,设置是否gps优先,只在高精度模式下有效。默认关闭
        mOption.setHttpTimeOut(30000);//可选,设置网络请求超时时间。默认为30秒。在仅设备模式下无效
        mOption.setInterval(4000);//可选,设置定位间隔。默认为2秒
        mOption.setNeedAddress(true);//可选,设置是否返回逆地理地址信息。默认是true
        mOption.setOnceLocation(false);//可选,设置是否单次定位。默认是false
        mOption.setOnceLocationLatest(false);//可选,设置是否等待wifi刷新,默认为false.如果设置为true,会自动变为单次定位,持续定位时不要使用
        AMapLocationClientOption.setLocationProtocol(AMapLocationClientOption.AMapLocationProtocol.HTTP);//可选, 设置网络请求的协议。可选HTTP或者HTTPS。默认为HTTP
        mOption.setSensorEnable(true);//可选,设置是否使用传感器。默认是false
        mOption.setWifiScan(true); //可选,设置是否开启wifi扫描。默认为true,如果设置为false会同时停止主动刷新,停止以后完全依赖于系统刷新,定位位置可能存在误差
        mOption.setLocationCacheEnable(false); //可选,设置是否使用缓存定位,默认为true
        mOption.setGeoLanguage(AMapLocationClientOption.GeoLanguage.DEFAULT);//可选,设置逆地理信息的语言,默认值为默认语言(根据所在地区选择语言)
        mLocationClient.setLocationOption(mOption);
    }

    //定位回调
    private AMapLocationListener MyAMapLocationListener = aMapLocation -> {

        if (null == aMapLocation)
            return;

        if (aMapLocation.getErrorCode() == 0) {
            //先暂时获得经纬度信息,并将其记录在List中
            LogUtils.d("纬度信息为" + aMapLocation.getLatitude() + "\n经度信息为" + aMapLocation.getLongitude());
            LatLng locationValue = new LatLng(aMapLocation.getLatitude(), aMapLocation.getLongitude());
//                mSportLatLngs.add(locationValue);

            //将运动信息上传至服务器
            recordLocation(locationValue, aMapLocation.getLocationDetail());

            //定位成功,发送通知
            if (null != interfaceLocationed)
                interfaceLocationed.locationed(aMapLocation);

        } else {
            String errText = "定位失败," + aMapLocation.getErrorCode() + ": " + aMapLocation.getErrorInfo();
            LogUtils.e("AmapErr", errText);
        }
    };

    private void recordLocation(LatLng latLng, String location) {
        if (mRecordService != null) {
            mRecordService.recordSport(latLng, location);
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        LogUtils.i(TAG, "绑定服务 The service is binding!");
        // 绑定服务,把当前服务的IBinder对象的引用传递给宿主
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        LogUtils.i(TAG, "解除绑定服务 The service is unbinding!");
        //解除绑定后销毁服务
        stopSelf();
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (null != mLocationClient) {
            mLocationClient.stopLocation();
            mLocationClient.unRegisterLocationListener(MyAMapLocationListener);
            mLocationClient.onDestroy();
            mLocationClient = null;
        }
    }

    public void setInterfaceLocationed(InterfaceLocationed interfaceLocationed) {
        this.interfaceLocationed = null;
        this.interfaceLocationed = interfaceLocationed;
    }

    public interface InterfaceLocationed {
        void locationed(AMapLocation aMapLocation);
    }
}

数据库使用了Realm记录运动,存储个人信息,注册登录判断,本项目没有使用服务器,所有数据存储在本地,故注册验证使用的是随机数,如果要在线验证可以使用mob的SDK实现号码短信验证。云数据库测试用可以使用bmob比目云的数据库。

发布了3 篇原创文章 · 获赞 0 · 访问量 66

猜你喜欢

转载自blog.csdn.net/Aristochi/article/details/104094704