女朋友想減肥,程序員花了三天寫了個卡路里計數器


 “女朋友說想減肥了,該怎么回答她?”

 

有的男同胞饒有信心,認為這是一道送分題——當然是毫不遲疑地告訴女友:”不用減,你一點也不胖,仔細琢磨還有點瘦……”

No!還是太年輕,你們可能在一段感情的炙熱里,但一看就不懂生活。答案早就更新n個版本了!我廠阿強近期也被提了此問,他給出了一個有技術含量的暖心答案——女友關心體態和體重器上的數量,阿強更關心她的身體健康。於是阿強發揮自己看家本事,寫了個卡路里計數器,把對關懷女友精確到數字,用數字告訴她日常應該保持怎樣的健康飲食和運動量,同時陪她一起踐行健康生活。

 

阿強把整個開發過程分享出來,供廣大男同胞學習和借鑒。以下便是如何通過Android Studio的Kotlin開發一個簡單的卡路里計數器應用的全過程。

 

實現原理:

華為運動健康服務允許用戶存儲智能手機或其他設備收集的運動健康數據,如智能手表、智能手環和計步器上的數據。這些數據可以在生態系統中安全共享。

主要功能

·       數據存儲:輕松存儲你的運動與健康數據。

·       數據開放:除了提供許多運動和保健數據接口外,它還支持共享各種運動與健康數據,包括步數、體重和心率。

·       數據訪問授權管理:用戶可以管理開發人員對其運動與健康數據的訪問,保障自身的數據隱私和合法權利。

·       設備訪問:可以通過藍牙測量硬件設備的數據,並允許我們上傳這些數據。

 

應用功能

這個卡路里計數器應用包含兩個界面。通過華為Account Kit的首頁,點擊“登錄華為賬號”按鈕登錄應用。登陸后進入下一個界面,在這個頁面上,可添加“卡路里”和“體重”信息。信息以圖形的方式顯示,借助了MPAndroidChart的免費庫。示例代碼已在相關社區進行開源,歡迎開發者關注、下載並提供寶貴意見:
Github官方地址:https://github.com/HMS-Core/hms-health-demo-kotlin

Gitee官方地址:https://gitee.com/hms-core/hms-health-demo-kotlin

 

 

1.   集成HUAWEI HMS Core

首先,我們需要在Console上創建一個帳戶,然后創建一個項目,並將其集成到應用中。可以按照此鏈接中概述的步驟快速完成此操作,也可以借助官方codelab來完成操作。

 

2.   集成華為運動健康服務

申請獲取運動健康服務(Health Kit)。通過此鏈接登錄Console后,單擊下圖中顯示的“Health Kit”。

 

然后,單擊“申請Health Kit”即可完成申請。

 

接下來需要請求應用使用數據的許可,包括“體重”和“卡路里”數據。只申請必要的數據權限,即“訪問和添加身高和體重數據”和“訪問和添加卡路里(包括基礎代謝率BMR)數據”

 

然后單擊“提交”按鈕,完成所有流程。

 

需要注意的是,你會看到下圖中的某些選項被鎖定,因為它們是敏感數據。如果你想在應用中使用敏感數據,還需要發送電子郵件至hihealth@huawei.com郵箱,標題命名為“申請Health Kit開放權限”。對方會盡快回復。你可以從點擊此鏈接獲得更多詳細信息。

 

在Console獲得必要的權限后,打開Android Studio繼續開發應用。

 

點擊“build.gradle (工程級)”,然后將所需的依賴添加到項目級的“build.gradle”文件中。

 

注意:我們為圖形庫添加了jitpack鏈接。

 

 

maven {url ‘https://developer.huawei.com/repo/'}
maven { url ‘https://jitpack.io' }

 

點擊打開“build.gradle(應用級)”文件。以下依賴關系對於運行Health Kit來說就足夠了,但我們還將添加Account Kit和圖形庫的依賴關系。

 

   

 implementation 'com.huawei.agconnect:agconnect-core:1.4.2.301'
    implementation 'com.huawei.hms:hwid:5.1.0.301'
    implementation 'com.huawei.hms:health:5.1.0.301'
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'

 

 

最后,打開“AndroidManifest.xml”文件,將App ID作為元數據信息添加到“Application”標簽中。可以通過以下兩種方法獲取我們的應用ID:1. 進入Console,單擊開發部分的“華為ID”,然后選擇項目,查看應用ID。2. 在“agconnect-services.json”文件中找到應用ID。

 

 

        <meta-data
            android:name="com.huawei.hms.client.appid"
            android:value="您的應用 Id"/>

 

3.   開發應用

Health Kit為我們提供了3個API:

·       DataController:添加、更新、刪除和讀取運動與健康數據。

·       ActivityRecordsController:將活動記錄寫入平台並更新記錄。

·       AutoRecordController:讀取實時運動與健康數據。

 

我們使用DataController在應用中處理卡路里和體重數據。Health Kit提供了安全可靠的數據服務,所以我們會請求用戶允許使用他們的健康數據。

 

 “activity_main.xml”文件包含logo、應用名、輸入按鈕等信息。

 

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

 

 

 

   <TextView
        android:id="@+id/tvAppName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="@string/app_name"
        android:textColor="@color/colorPrimary"
        android:textSize="24sp"
        android:textStyle="bold"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/ivLogo" />

 

 

 

    

<com.huawei.hms.support.hwid.ui.HuaweiIdAuthButton
        android:id="@+id/btnLogin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.7" />

 

 

    

<ImageView
        android:id="@+id/ivLogo"
        android:layout_width="172dp"
        android:layout_height="113dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.497"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.3"
        app:srcCompat="@drawable/ic_logo" />
        </androidx.constraintlayout.widget.ConstraintLayout>

 

MainActivity.kt包含登錄過程所需的代碼。

 

 

package com.huawei.healthtracker
 
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.huawei.hms.common.ApiException
import com.huawei.hms.hihealth.data.Scopes
import com.huawei.hms.support.api.entity.auth.Scope
import com.huawei.hms.support.hwid.HuaweiIdAuthManager
import com.huawei.hms.support.hwid.request.HuaweiIdAuthParams
import com.huawei.hms.support.hwid.request.HuaweiIdAuthParamsHelper
import com.huawei.hms.support.hwid.service.HuaweiIdAuthService
import com.huawei.hms.support.hwid.ui.HuaweiIdAuthButton
 
class MainActivity : AppCompatActivity() {
 
    private val TAG = "MainActivity"
 
    private lateinit var btnLogin: HuaweiIdAuthButton
    private lateinit var mAuthParam: HuaweiIdAuthParams
    private lateinit var mAuthService: HuaweiIdAuthService
 
    private val REQUEST_SIGN_IN_LOGIN = 1001
 
 
    override fun onCreate(savedInstanceState: Bundle?) {
 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        btnLogin = findViewById(R.id.btnLogin)
 
        btnLogin.setOnClickListener {
            signIn()
        }
 
    }
 
    private fun signIn() {
 
        val scopeList = listOf(
            Scope(Scopes.HEALTHKIT_CALORIES_BOTH),
            Scope(Scopes.HEALTHKIT_HEIGHTWEIGHT_BOTH),
        )
 
        mAuthParam =
            HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM).apply {
                setIdToken()
                    .setAccessToken()
                    .setScopeList(scopeList)
            }.createParams()
 
        mAuthService = HuaweiIdAuthManager.getService(this, mAuthParam)
 
        val authHuaweiIdTask = mAuthService.silentSignIn()
 
        authHuaweiIdTask.addOnSuccessListener {
            val intent = Intent(this, CalorieTrackerActivity::class.java)
            startActivity(intent)
        }
            .addOnFailureListener {
                startActivityForResult(mAuthService.signInIntent, REQUEST_SIGN_IN_LOGIN)
            }
    }
 
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
 
        when (requestCode) {
            REQUEST_SIGN_IN_LOGIN -> {
 
                val authHuaweiIdTask = HuaweiIdAuthManager.parseAuthResultFromIntent(data)
                if (authHuaweiIdTask.isSuccessful) {
 
                    val intent = Intent(this, CalorieTrackerActivity::class.java)
                    startActivity(intent)
 
                } else {
                    Log.i(
                        TAG,
                        "signIn failed: ${(authHuaweiIdTask.exception as ApiException).statusCode}"
                    )
                }
            }
        }
    }
 
}

 

確保你已將數據的權限設置為“范圍”。單擊登錄按鈕時,用戶將看到授權頁面。並且,授權頁面在“范圍”字段中顯示權限。默認情況下不標記這些權限,因此用戶應標記它們。

在“CalorieTrackerActivity”頁面上,可以添加和查看卡路里和體重信息。

 

“activity_calorie_tracker.xml”包含卡路里計數器頁面的設計代碼。

 

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".CalorieTrackerActivity">

 

   

 <Button
        android:id="@+id/btnShowConsCal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="Show Cons. Cal."
        android:textAllCaps="false"
        app:layout_constraintEnd_toStartOf="@+id/guideline"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnAddConsumedCal" />

 

 

 

   <Button
        android:id="@+id/btnShowWeight"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="Show Weight"
        android:textAllCaps="false"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="@+id/guideline"
        app:layout_constraintTop_toBottomOf="@+id/btnAddWeight" />

 

 

    

<Button
        android:id="@+id/btnAddConsumedCal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Add"
        android:textAllCaps="false"
        app:layout_constraintEnd_toStartOf="@+id/guideline"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/etConsumedCal" />

 

 

 

    <Button
        android:id="@+id/btnAddWeight"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Add"
        android:textAllCaps="false"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="@+id/guideline"
        app:layout_constraintTop_toBottomOf="@+id/etBurntCal" />

 

 

  

  <TextView
        android:id="@+id/textView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="Consumed Calorie"
        android:textColor="@color/black"
        android:textSize="18sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toStartOf="@+id/guideline"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

 

 

    

<TextView
        android:id="@+id/textView2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="Weight"
        android:textColor="@color/black"
        android:textSize="18sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline"
        app:layout_constraintTop_toTopOf="parent" />

 

 

  

  <EditText
        android:id="@+id/etConsumedCal"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="32dp"
        android:ems="10"
        android:hint="Kcal"
        android:inputType="number"
        android:maxLength="4"
        app:layout_constraintEnd_toStartOf="@+id/guideline"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

 

 

  

  <EditText
        android:id="@+id/etBurntCal"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="32dp"
        android:ems="10"
        android:hint="Kg"
        android:inputType="number"
        android:maxLength="3"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline"
        app:layout_constraintTop_toBottomOf="@+id/textView2" />

 

 

  

  <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.5"
        app:layout_constraintTop_toTopOf="parent" />

 

 

 

   

 <View
        android:id="@+id/view_vertical"
        android:layout_width="1dp"
        android:layout_height="0dp"
        android:layout_marginTop="16dp"
        android:layout_marginBottom="16dp"
        android:background="@android:color/darker_gray"
        app:layout_constraintBottom_toBottomOf="@+id/btnAddConsumedCal"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

 

 

   

 <androidx.cardview.widget.CardView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="32dp"
        android:background="@color/colorCardBackground"
        app:cardCornerRadius="4dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnShowWeight">

 

 

        

<LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

 

 

          

  <TextView
                android:id="@+id/tvChartHead"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:gravity="center_horizontal"
                android:text="Weekly Consumed Calories"
                android:textColor="@color/black"
                android:textSize="18sp"
                android:textStyle="bold" />

 

 

         

   <com.github.mikephil.charting.charts.BarChart
                android:id="@+id/barchartWeeklyCal"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_margin="16dp"
                android:background="@android:color/white" />
        </LinearLayout>
    </androidx.cardview.widget.CardView>

 

 

</androidx.constraintlayout.widget.ConstraintLayout>

 

剛剛我們介紹了Data Controllers,現在就來創建一個Data Controller,然后將要使用的數據寫入權限。

 

 

class CalorieTrackerActivity : AppCompatActivity() {
  // ...
    private lateinit var dataController: DataController
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_calorie_tracker)
 
        initDataController()
 
        //...
    }
       
     private fun initDataController() {
        val hiHealthOptions = HiHealthOptions.builder()
            .addDataType(DataType.DT_CONTINUOUS_CALORIES_CONSUMED, HiHealthOptions.ACCESS_READ)
            .addDataType(DataType.DT_CONTINUOUS_CALORIES_CONSUMED, HiHealthOptions.ACCESS_WRITE)
            .addDataType(DataType.DT_INSTANTANEOUS_BODY_WEIGHT, HiHealthOptions.ACCESS_READ)
            .addDataType(DataType.DT_INSTANTANEOUS_BODY_WEIGHT, HiHealthOptions.ACCESS_WRITE)
            .build()
 
        val signInHuaweiId = HuaweiIdAuthManager.getExtendedAuthResult(hiHealthOptions)
        dataController = HuaweiHiHealth.getDataController(this, signInHuaweiId)
    }
 
 
}

 

通過addConsumedCalorie方法來記錄數據。此前,需要設置一個時間間隔:將當前時間作為結束時間,並將其之前的一秒作為開始時間。

 

  

      private fun addConsumedCalorie(calorie: Float) {
      val dataCollector: DataCollector = DataCollector.Builder().setPackageName(this)
            .setDataType(DataType.DT_CONTINUOUS_CALORIES_CONSUMED)
            .setDataGenerateType(DataCollector.DATA_TYPE_RAW)
            .build()
 
        val sampleSet = SampleSet.create(dataCollector)
 
        val currentTime = System.currentTimeMillis()
 
        val samplePoint = sampleSet.createSamplePoint()
            .setTimeInterval(currentTime - 1, currentTime, TimeUnit.MILLISECONDS)
        samplePoint.getFieldValue(Field.FIELD_CALORIES).setFloatValue(calorie)
        sampleSet.addSample(samplePoint)
       
        val insertTask: Task<Void> = dataController.insert(sampleSet)
 
        insertTask.addOnSuccessListener {
            Toast.makeText(this, "Calorie added successfully", Toast.LENGTH_SHORT).show()
        }.addOnFailureListener { e ->
            Toast.makeText(this, e.message.toString(), Toast.LENGTH_LONG).show()
        }
    }

 

我們創建了一個名為addWightData的方法,類似於addConsumedCalori方法。但這一次,輸入的開始時間和結束時間的值必須相同。否則,當輸入重量信息時,應用將出現崩潰。我們還更改了數據類型。

 

 

    private fun addWeightData(weight: Float) {
        val dataCollector: DataCollector = DataCollector.Builder().setPackageName(this)
            .setDataType(DataType.DT_INSTANTANEOUS_BODY_WEIGHT)
            .setDataGenerateType(DataCollector.DATA_TYPE_RAW)
            .build()
 
        val sampleSet = SampleSet.create(dataCollector)
 
        val currentTime = System.currentTimeMillis()
 
        val samplePoint = sampleSet.createSamplePoint()
            .setTimeInterval(currentTime, currentTime, TimeUnit.MILLISECONDS)
        samplePoint.getFieldValue(Field.FIELD_BODY_WEIGHT).setFloatValue(weight)
        sampleSet.addSample(samplePoint)
 
        val insertTask: Task<Void> = dataController.insert(sampleSet)
 
        insertTask.addOnSuccessListener {
            Toast.makeText(this, "Weight added successfully", Toast.LENGTH_SHORT).show()
        }.addOnFailureListener { e ->
            Toast.makeText(this, e.message.toString(), Toast.LENGTH_SHORT).show()
        }
    }

 

接下來使用readConsumedData方法來讀取消耗的卡路里數據。我們選擇了一周前到當前的時間范圍,然后檢索了這個時間范圍內的所有數據,並將其作為時間值放在Map上。最后,調用showCaloriesWeekly方法在條形圖中顯示這些數據。

 

 

   private fun readConsumedData() {
 
        val caloriesMap = mutableMapOf<Long, Float>()
 
        val endDate = System.currentTimeMillis()
        val startDate = endDate - SIX_DAY_MILLIS
 
        val readOptions = ReadOptions.Builder()
            .read(DataType.DT_CONTINUOUS_CALORIES_CONSUMED)
            .setTimeRange(startDate, endDate, TimeUnit.MILLISECONDS).build()
 
        val readReplyTask = dataController.read(readOptions)
 
        readReplyTask.addOnSuccessListener { readReply ->
            for (sampleSet in readReply.sampleSets) {
                if (sampleSet.isEmpty.not()) {
                    sampleSet.samplePoints.forEach {
                        caloriesMap.put(
                            it.getStartTime(TimeUnit.MILLISECONDS),
                            it.getFieldValue(Field.FIELD_CALORIES).asFloatValue()
                        )
                    }
                } else {
                    Toast.makeText(this, "No data to show", Toast.LENGTH_SHORT).show()
                }
            }
 
            showCaloriesWeekly(caloriesMap)
 
        }.addOnFailureListener {
            Log.i(TAG, it.message.toString())
        }
    }

 

使用readWightData方法來檢索記錄的體重信息。

 

 

    private fun readWeightData() {
 
        val weightsMap = mutableMapOf<Long, Float>()
 
        val endDate = System.currentTimeMillis()
        val startDate = endDate - SIX_DAY_MILLIS
 
        val readOptions = ReadOptions.Builder().read(DataType.DT_INSTANTANEOUS_BODY_WEIGHT)
            .setTimeRange(startDate, endDate, TimeUnit.MILLISECONDS).build()
 
        val readReplyTask = dataController.read(readOptions)
 
        readReplyTask.addOnSuccessListener { readReply ->
 
            for (sampleSet in readReply.sampleSets) {
                if (sampleSet.isEmpty.not()) {
                    sampleSet.samplePoints.forEach {
                        weightsMap.put(
                            it.getStartTime(TimeUnit.MILLISECONDS),
                            it.getFieldValue(Field.FIELD_BODY_WEIGHT).asFloatValue()
                        )
                    }
                } else {
                    Toast.makeText(this, "No data to show", Toast.LENGTH_SHORT).show()
                }
            }
 
            showWeightsWeekly(weightsMap)
 
        }.addOnFailureListener {
            Log.i(TAG, it.message.toString())
        }
    }

 

使用showCaloriesWeekly方法獲取上周的數據作為時間值。在獲取值后,將上周每天的數據相加。最后調用initBarChart方法在條形圖上顯示每日數據。

 

 

    private fun showCaloriesWeekly(dataList: Map<Long, Float>) {
 
        val arrangedValuesAsMap = mutableMapOf<Long, Float>()
        val currentTimeMillis = System.currentTimeMillis()
 
        var firstDayCal = 0f
        var secondDayCal = 0f
        var thirdDayCal = 0f
        var fourthDayCal = 0f
        var fifthDayCal = 0f
        var sixthDayCal = 0f
        var seventhDayCal = 0f
 
        dataList.forEach { (time, value) ->
            when (time) {
                in getTodayStartInMillis()..currentTimeMillis -> {
                    seventhDayCal += value
                }
                in getTodayStartInMillis() - ONE_DAY_MILLIS until getTodayStartInMillis() -> {
                    sixthDayCal += value
                }
                in getTodayStartInMillis() - ONE_DAY_MILLIS * 2 until
                        getTodayStartInMillis() - ONE_DAY_MILLIS -> {
                    fifthDayCal += value
                }
                in getTodayStartInMillis() - ONE_DAY_MILLIS * 3 until
                        getTodayStartInMillis() - ONE_DAY_MILLIS * 2 -> {
                    fourthDayCal += value
                }
                in getTodayStartInMillis() - ONE_DAY_MILLIS * 4 until
                        getTodayStartInMillis() - ONE_DAY_MILLIS * 3 -> {
                    thirdDayCal += value
                }
                in getTodayStartInMillis() - ONE_DAY_MILLIS * 5 until
                        getTodayStartInMillis() - ONE_DAY_MILLIS * 4 -> {
                    secondDayCal += value
                }
                in getTodayStartInMillis() - ONE_DAY_MILLIS * 6 until
                        getTodayStartInMillis() - ONE_DAY_MILLIS * 5 -> {
                    firstDayCal += value
                }
            }
        }
 
        arrangedValuesAsMap.put(getTodayStartInMillis() - ONE_DAY_MILLIS * 6, firstDayCal)
        arrangedValuesAsMap.put(getTodayStartInMillis() - ONE_DAY_MILLIS * 5, secondDayCal)
        arrangedValuesAsMap.put(getTodayStartInMillis() - ONE_DAY_MILLIS * 4, thirdDayCal)
        arrangedValuesAsMap.put(getTodayStartInMillis() - ONE_DAY_MILLIS * 3, fourthDayCal)
        arrangedValuesAsMap.put(getTodayStartInMillis() - ONE_DAY_MILLIS * 2, fifthDayCal)
        arrangedValuesAsMap.put(getTodayStartInMillis() - ONE_DAY_MILLIS, sixthDayCal)
        arrangedValuesAsMap.put(getTodayStartInMillis(), seventhDayCal)
 
        initBarChart(arrangedValuesAsMap)
 
    }

 

showWightWeekly的工作原理幾乎與showCaloriesWeekly方法相似。它們之間唯一的區別是,我們不在showWightWeekly方法中求和每日值,而是只獲得每日的一個最新值。

 

 

    private fun showWeightsWeekly(dataList: Map<Long, Float>) {
 
        val arrangedValuesAsMap = mutableMapOf<Long, Float>()
        val currentTimeMillis = System.currentTimeMillis()
 
        var firstDayWeight = 0f
        var secondDayWeight = 0f
        var thirdDayWeight = 0f
        var fourthDayWeight = 0f
        var fifthDayWeight = 0f
        var sixthDayWeight = 0f
        var seventhDayWeight = 0f
 
        dataList.forEach { (time, value) ->
            when (time) {
                in getTodayStartInMillis()..currentTimeMillis -> {
                    seventhDayWeight = value
                }
                in getTodayStartInMillis() - ONE_DAY_MILLIS until getTodayStartInMillis() -> {
                    sixthDayWeight = value
                }
                in getTodayStartInMillis() - ONE_DAY_MILLIS * 2 until
                        getTodayStartInMillis() - ONE_DAY_MILLIS -> {
                    fifthDayWeight = value
                }
                in getTodayStartInMillis() - ONE_DAY_MILLIS * 3 until
                        getTodayStartInMillis() - ONE_DAY_MILLIS * 2 -> {
                    fourthDayWeight = value
                }
                in getTodayStartInMillis() - ONE_DAY_MILLIS * 4 until
                        getTodayStartInMillis() - ONE_DAY_MILLIS * 3 -> {
                    thirdDayWeight = value
                }
                in getTodayStartInMillis() - ONE_DAY_MILLIS * 5 until
                        getTodayStartInMillis() - ONE_DAY_MILLIS * 4 -> {
                    secondDayWeight = value
                }
                in getTodayStartInMillis() - ONE_DAY_MILLIS * 6 until
                        getTodayStartInMillis() - ONE_DAY_MILLIS * 5 -> {
                    firstDayWeight = value
                }
            }
        }
 
        arrangedValuesAsMap.put(getTodayStartInMillis() - ONE_DAY_MILLIS * 6, firstDayWeight)
        arrangedValuesAsMap.put(getTodayStartInMillis() - ONE_DAY_MILLIS * 5, secondDayWeight)
        arrangedValuesAsMap.put(getTodayStartInMillis() - ONE_DAY_MILLIS * 4, thirdDayWeight)
        arrangedValuesAsMap.put(getTodayStartInMillis() - ONE_DAY_MILLIS * 3, fourthDayWeight)
        arrangedValuesAsMap.put(getTodayStartInMillis() - ONE_DAY_MILLIS * 2, fifthDayWeight)
        arrangedValuesAsMap.put(getTodayStartInMillis() - ONE_DAY_MILLIS, sixthDayWeight)
        arrangedValuesAsMap.put(getTodayStartInMillis(), seventhDayWeight)
 
        initBarChart(arrangedValuesAsMap)
 
    }

 

InitBarChart以圖形形式顯示數據。

 

 

    private fun initBarChart(values: MutableMap<Long, Float>) {
 
        var barIndex = 0f
        val labelWeekdayNames = arrayListOf<String>()
        val entries = ArrayList<BarEntry>()
 
        val simpleDateFormat = SimpleDateFormat("E", Locale.US)
 
        values.forEach { (time, value) ->
            labelWeekdayNames.add(simpleDateFormat.format(time))
            entries.add(BarEntry(barIndex, value))
            barIndex++
        }
 
        barChart.apply {
            setDrawBarShadow(false)
            setDrawValueAboveBar(false)
            description.isEnabled = false
            setDrawGridBackground(false)
            isDoubleTapToZoomEnabled = false
        }
 
        barChart.xAxis.apply {
            setDrawGridLines(false)
            position = XAxis.XAxisPosition.BOTTOM
            granularity = 1f
            setDrawLabels(true)
            setDrawAxisLine(false)
            valueFormatter = IndexAxisValueFormatter(labelWeekdayNames)
            axisMaximum = labelWeekdayNames.size.toFloat()
        }
 
        barChart.axisRight.isEnabled = false
 
        val legend = barChart.legend
        legend.isEnabled = false
 
        val dataSets = arrayListOf<IBarDataSet>()
        val barDataSet = BarDataSet(entries, " ")
        barDataSet.color = Color.parseColor("#76C33A")
        barDataSet.setDrawValues(false)
        dataSets.add(barDataSet)
 
        val data = BarData(dataSets)
        barChart.data = data
        barChart.invalidate()
        barChart.animateY(1500)
 
    }

 

此外

除了添加和讀取運動與健康數據外,Health Kit還提供更新數據、刪除數據和清除所有數據的功能。雖然我們沒有在應用中使用這些功能,但也可以快速了解一下。

 

updateWight:可以在指定的時間范圍內更新數據。如果想使用體重信息,那就需要將開始時間和結束時間設置為相同的時間值。但如果我們想更新卡路里值,就需要設置為一個稍長的時間范圍。如果指定時間范圍內沒有要更新的值時,它將自動添加新的重量或卡路里值。

 

 

    private fun updateWeight(weight: Float, startTimeInMillis: Long, endTimeInMillis: Long) {
 
        val dataCollector: DataCollector = DataCollector.Builder().setPackageName(this)
            .setDataType(DataType.DT_INSTANTANEOUS_BODY_WEIGHT)
            .setDataGenerateType(DataCollector.DATA_TYPE_RAW)
            .build()
 
        val sampleSet = SampleSet.create(dataCollector)
 
        val samplePoint = sampleSet.createSamplePoint()
            .setTimeInterval(startTimeInMillis, endTimeInMillis, TimeUnit.MILLISECONDS)
        samplePoint.getFieldValue(Field.FIELD_BODY_WEIGHT).setFloatValue(weight)
 
        sampleSet.addSample(samplePoint)
 
        val updateOptions = UpdateOptions.Builder()
            .setTimeInterval(startTimeInMillis, endTimeInMillis, TimeUnit.MILLISECONDS)
            .setSampleSet(sampleSet)
            .build()
 
        dataController.update(updateOptions)
            .addOnSuccessListener {
                Toast.makeText(this, "Weight has been updated successfully", Toast.LENGTH_SHORT)
                    .show()
            }
            .addOnFailureListener { e ->
                Toast.makeText(this, e.message.toString(), Toast.LENGTH_SHORT).show()
            }
       
    }

 

deleteWight:刪除指定范圍內的值。

 

 

    private fun deleteWeight(startTimeInMillis: Long, endTimeInMillis: Long) {
 
        val dataCollector: DataCollector = DataCollector.Builder().setPackageName(this)
            .setDataType(DataType.DT_INSTANTANEOUS_BODY_WEIGHT)
            .setDataGenerateType(DataCollector.DATA_TYPE_RAW)
            .build()
 
        val deleteOptions = DeleteOptions.Builder()
            .addDataCollector(dataCollector)
            .setTimeInterval(startTimeInMillis, endTimeInMillis, TimeUnit.MILLISECONDS)
            .build()
 
        dataController.delete(deleteOptions).addOnSuccessListener {
            Toast.makeText(this, "Weight has been deleted successfully", Toast.LENGTH_SHORT)
                .show()
        }
            .addOnFailureListener { e ->
                Toast.makeText(this, e.message.toString(), Toast.LENGTH_SHORT).show()
            }
 
    }

 

clearHealthData:刪除Health Kit中的所有數據。

 

 

   private fun clearHealthData() {
 
        dataController.clearAll()
            .addOnSuccessListener {
                Toast.makeText(this, "All Health Kit data has been deleted.", Toast.LENGTH_SHORT)
                    .show()
            }
            .addOnFailureListener { e ->
                Toast.makeText(this, e.message.toString(), Toast.LENGTH_SHORT).show()
            }
 
    }

 

提示和技巧

 

⚠️確保已將數據的權限設置為范圍。否則,將返回錯誤代碼50005。

 

⚠️使用data controller寫入數據時,請確保使用正確的時間間隔。否則,當你嘗試寫入數據時,應用將發生崩潰。

 

 

欲了解HMS Core更多詳情,請參閱:
>>華為開發者聯盟官網

>>獲取開發指導文檔
>>參與開發者討論請到CSDN社區或者Reddit社區
>>下載demo和示例代碼請到Github或者Gitee
>>解決集成問題請到Stack Overflow

 
原文鏈接: https://developer.huawei.com/...
原作者:胡椒


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM