黑马健康期末项目4

目录

 前言

一、项目介绍

二、应用运行

1.一次开发多端部署

(1)页面整体架构分析

(2)运行效果

(3)详细代码及分析

2.数据模型-记录项

(1)页面整体架构分析

(2)运行效果

(3)详细代码及分析

3.数据模型——饮食记录

(1)页面整体架构分析

(2)代码及分析(部分)

总结


前言

综合运用本学期所学内容及个人自学知识,使用HarmonyOS 4.0及以上版本开发一款具有实用性和创新性的移动应用软件。


一、项目介绍

黑马健康应用程序是一个综合性的健康监控和管理工具,它具备一系列定制化功能,旨在帮助用户轻松地跟踪和管理自己的健康状况。通过个性化的饮食追踪记录和全面的健康评估机制,该应用能够根据用户的具体情况提供针对性的建议和指导。无论是对于追求体型改善和体重管理的用户,还是那些希望优化日常饮食以满足营养需求的用户,黑马健康都能提供一套量身打造的解决方案。它通过简化健康管理流程,使用户能够以一种更加直观和易于操作的方式,实现健康目标的达成。此外,黑马健康应用程序还强调了用户日常生活习惯的改善,通过持续的健康教育和行为指导,鼓励用户形成更加健康的生活方式。通过这种方式,该应用程序不仅帮助用户在短期内实现健康目标,更致力于长期促进用户的身体健康和生活质量的提升。

二、应用运行

1.一次开发多端部署

(1)页面整体架构分析

(2)运行效果

(3)详细代码及分析

BreakpointSystem.ets

// 导入mediaQuery模块,用于媒体查询功能
import mediaQuery from '@ohos.mediaquery'
// 导入断点常量配置
import BreakpointConstants from '../constants/BreakpointConstants'

// 定义BreakpointSystem类
export default class BreakpointSystem {
  // 定义小屏设备的媒体查询监听器
  private smListener: mediaQuery.MediaQueryListener = mediaQuery.matchMediaSync(BreakpointConstants.RANGE_SM)
  // 定义中屏设备的媒体查询监听器
  private mdListener: mediaQuery.MediaQueryListener = mediaQuery.matchMediaSync(BreakpointConstants.RANGE_MD)
  // 定义大屏设备的媒体查询监听器
  private lgListener: mediaQuery.MediaQueryListener = mediaQuery.matchMediaSync(BreakpointConstants.RANGE_LG)

  // 定义小屏设备媒体查询结果的回调函数
  smListenerCallback(result: mediaQuery.MediaQueryResult) {
    if (result.matches) {
      // 如果匹配小屏设备断点,则更新当前断点
      this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_SM)
    }
  }

  // 定义中屏设备媒体查询结果的回调函数
  mdListenerCallback(result: mediaQuery.MediaQueryResult) {
    if (result.matches) {
      // 如果匹配中屏设备断点,则更新当前断点
      this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_MD)
    }
  }

  // 定义大屏设备媒体查询结果的回调函数
  lgListenerCallback(result: mediaQuery.MediaQueryResult) {
    if (result.matches) {
      // 如果匹配大屏设备断点,则更新当前断点
      this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_LG)
    }
  }

  // 定义更新当前断点的函数
  updateCurrentBreakpoint(breakpoint: string) {
    // 将断点信息存储到应用存储中
    AppStorage.SetOrCreate(BreakpointConstants.CURRENT_BREAKPOINT, breakpoint)
  }

  // 定义注册监听器的函数
  register() {
    // 为小屏、中屏和大屏的监听器注册变化事件的回调
    this.smListener.on('change', this.smListenerCallback.bind(this))
    this.mdListener.on('change', this.mdListenerCallback.bind(this))
    this.lgListener.on('change', this.lgListenerCallback.bind(this))
  }

  // 定义注销监听器的函数
  unregister() {
    // 为小屏、中屏和大屏的监听器注销变化事件的回调
    this.smListener.off('change', this.smListenerCallback.bind(this))
    this.mdListener.off('change', this.mdListenerCallback.bind(this))
    this.lgListener.off('change', this.lgListenerCallback.bind(this))
  }
}

Index.ets:

// 导入BreakpointType类型,用于处理断点相关的类型定义
import BreakpointType from '../common/bean/BreakpointType'
// 导入断点常量配置
import BreakpointConstants from '../common/constants/BreakpointConstants'
// 导入通用常量
import { CommonConstants } from '../common/constants/CommonConstants'
// 导入断点系统工具类
import BreakpointSystem from '../common/utils/BreakpointSystem'
// 导入记录索引页面组件
import RecordIndex from '../view/record/RecordIndex'
// 使用@Entry和@Component装饰器标记这个结构体是一个页面组件的入口点
@Entry
@Component
struct Index {
  // 使用@State装饰器定义当前选中的索引,初始值为0
  @State currentIndex: number = 0
  // 定义一个BreakpointSystem实例,用于响应断点变化
  private breakpointSystem: BreakpointSystem = new BreakpointSystem()
  // 使用@StorageProp装饰器从存储中获取当前断点,如果没有则默认为小屏断点
  @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointConstants.BREAKPOINT_SM

  // 定义TabBarBuilder函数,用于构建标签栏的每个项
  @Builder TabBarBuilder(title: ResourceStr, image: ResourceStr, index: number) {
    Column({ space: CommonConstants.SPACE_8 }) {
      Image(image)
        .width(22)
        .fillColor(this.selectColor(index)) // 使用selectColor函数设置颜色
      Text(title)
        .fontSize(14)
        .fontColor(this.selectColor(index)) // 使用selectColor函数设置颜色
    }
  }

  // 页面即将显示时调用的生命周期函数,注册断点变化监听
  aboutToAppear() {
    this.breakpointSystem.register()
  }

  // 页面即将消失时调用的生命周期函数,注销断点变化监听
  aboutToDisappear() {
    this.breakpointSystem.unregister()
  }

  // selectColor函数,根据当前选中的索引返回不同的颜色
  selectColor(index: number) {
    return this.currentIndex == index ? $r('app.color.primary_color') : $r('app.color.gray')
  }

  // build函数,用于构建页面的UI结构
  build() {
    // 创建Tabs组件,根据断点设置标签栏的位置
    Tabs({
      barPosition: BreakpointConstants.BAR_POSITION.getValue(this.currentBreakpoint)
    }) {
      // 添加标签内容和标签栏项
      TabContent() {
        RecordIndex() // 第一个标签页内容为RecordIndex组件
      }
      .tabBar(this.TabBarBuilder($r('app.string.tab_record'), $r('app.media.ic_calendar'), 0))

      TabContent() {
        Text('发现页面') // 第二个标签页内容为文本
      }
      .tabBar(this.TabBarBuilder($r('app.string.tab_discover'), $r('app.media.discover'), 1))

      TabContent() {
        Text('我的主页') // 第三个标签页内容为文本
      }
      .tabBar(this.TabBarBuilder($r('app.string.tab_user'), $r('app.media.ic_user_portrait'), 2))
    }
    // 设置Tabs组件的宽度和高度
    .width('100%')
    .height('100%')
    // 设置选项卡变化时的回调函数
    .onChange(index => this.currentIndex = index)
    // 根据断点设置Tabs组件的垂直或水平布局
    .vertical(new BreakpointType({
      sm: false,
      md: true,
      lg: true
    }).getValue(this.currentBreakpoint))
  }
}

2.数据模型-记录项

(1)页面整体架构分析

(2)运行效果

(3)详细代码及分析

ItemCategoryModel:
这段代码通过枚举`FoodCategoryEnum`和`WorkoutCategoryEnum`分别定义了五种食物类型和七种运动类型,同时创建了`FoodCategories`和`WorkoutCategories`数组来存储这些类别的具体实例。每个实例使用`ItemCategory`类创建,包含一个编号和通过`$r`函数引用的资源字符串。最后,代码将这些枚举和数组导出,供其他模块使用。

import ItemCategory from '../viewmodel/ItemCategory'

/**
 * 食物类型的枚举
 */
enum FoodCategoryEnum{//食物类型枚举
  /**
   * 主食
   */
  STAPLE,
  /**
   * 蔬果
   */
  FRUIT,
  /**
   * 肉蛋奶
   */
  MEAT,
  /**
   * 坚果
   */
  NUT,
  /**
   * 其它
   */
  OTHER,
}

/**
 * 食物类型数组
 */
let FoodCategories = [//食物类型数组
  new ItemCategory(0, $r('app.string.staple')),
  new ItemCategory(1, $r('app.string.fruit')),
  new ItemCategory(2, $r('app.string.meat')),
  new ItemCategory(3, $r('app.string.nut')),
  new ItemCategory(4, $r('app.string.other_type')),
]

/**
 * 运动类型枚举
 */
enum WorkoutCategoryEnum {
  /**
   * 走路
   */
  WALKING,
  /**
   * 跑步
   */
  RUNNING,
  /**
   * 骑行
   */
  RIDING,
  /**
   * 跳操
   */
  AEROBICS,
  /**
   * 游泳
   */
  SWIMMING,
  /**
   * 打球
   */
  BALLGAME,
  /**
   * 力量训练
   */
  STRENGTH
}

/**
 * 运动类型数组
 */
let WorkoutCategories = [
  new ItemCategory(0, $r('app.string.walking_type')),
  new ItemCategory(1, $r('app.string.running')),
  new ItemCategory(2, $r('app.string.riding')),
  new ItemCategory(3, $r('app.string.aerobics')),
  new ItemCategory(4, $r('app.string.swimming')),
  new ItemCategory(5, $r('app.string.ballgame')),
  new ItemCategory(6, $r('app.string.strength')),
]

export {FoodCategories , WorkoutCategories , FoodCategoryEnum, WorkoutCategoryEnum}

ItemModel部分:

 定义了食物、运动的相关信息和一些相关查询方法。

import RecordItem from '../viewmodel/RecordItem'
import ItemCategory from '../viewmodel/ItemCategory'
import { FoodCategories, FoodCategoryEnum, WorkoutCategories, WorkoutCategoryEnum } from './ItemCategoryModel'
import GroupInfo from '../viewmodel/GroupInfo'

const foods: RecordItem[] = [
  new RecordItem(0, '米饭',$r('app.media.rice'),FoodCategoryEnum.STAPLE, '碗',  209, 46.6, 4.7, 0.5),
  new RecordItem(1, '馒头',$r('app.media.steamed_bun'),FoodCategoryEnum.STAPLE, '个',  114, 24.0, 3.6, 0.6),
  new RecordItem(2, '面包',$r('app.media.bun'),FoodCategoryEnum.STAPLE, '个',  188, 35.2, 5.0, 3.1),
  new RecordItem(3, '全麦吐司',$r('app.media.toast'),FoodCategoryEnum.STAPLE, '片',  91, 15.5, 4.4, 1.3),
  new RecordItem(4, '紫薯',$r('app.media.purple_potato'),FoodCategoryEnum.STAPLE, '个',  163, 42.0, 1.6, 0.4),
  new RecordItem(5, '煮玉米',$r('app.media.corn'),FoodCategoryEnum.STAPLE, '根',  111, 22.6, 4.0, 1.2),
  new RecordItem(6, '黄瓜',$r('app.media.cucumber'),FoodCategoryEnum.FRUIT, '根',  29, 5.3, 1.5, 0.4),
  new RecordItem(7, '蓝莓',$r('app.media.blueberry'),FoodCategoryEnum.FRUIT, '盒',  71, 18.1, 0.9, 0.4),
  new RecordItem(8, '草莓',$r('app.media.strawberry'),FoodCategoryEnum.FRUIT, '颗',  14, 3.1, 0.4, 0.1),
  new RecordItem(9, '火龙果',$r('app.media.pitaya'),FoodCategoryEnum.FRUIT, '个',  100, 24.6, 2.2, 0.5),
  new RecordItem(10, '奇异果',$r('app.media.kiwi'),FoodCategoryEnum.FRUIT, '个',  25, 8.4, 0.5, 0.3),
  new RecordItem(11, '煮鸡蛋',$r('app.media.egg'),FoodCategoryEnum.MEAT, '个',  74, 0.1, 6.2, 5.4),
  new RecordItem(12, '煮鸡胸肉',$r('app.media.chicken_breast'),FoodCategoryEnum.MEAT, '克',  1.15, 0.011, 0.236, 0.018),
  new RecordItem(13, '煮鸡腿肉',$r('app.media.chicken_leg'),FoodCategoryEnum.MEAT, '克',  1.87, 0.0, 0.243, 0.092),
  new RecordItem(14, '牛肉',$r('app.media.beef'),FoodCategoryEnum.MEAT, '克',  1.22, 0.0, 0.23, 0.033),
  new RecordItem(15, '鱼肉',$r("app.media.fish"),FoodCategoryEnum.MEAT, '克',  1.04, 0.0, 0.206, 0.024),
  new RecordItem(16, '牛奶',$r("app.media.milk"),FoodCategoryEnum.MEAT, '毫升',  0.66, 0.05, 0.03, 0.038),
  new RecordItem(17, '酸奶',$r("app.media.yogurt"),FoodCategoryEnum.MEAT, '毫升',  0.7, 0.10, 0.032, 0.019),
  new RecordItem(18, '核桃',$r("app.media.walnut"),FoodCategoryEnum.NUT, '颗',  42, 1.2, 1.0, 3.8),
  new RecordItem(19, '花生',$r("app.media.peanut"),FoodCategoryEnum.NUT, '克',  3.13, 0.13, 0.12, 0.254),
  new RecordItem(20, '腰果',$r("app.media.cashew"),FoodCategoryEnum.NUT, '克',  5.59, 0.416, 0.173, 0.367),
  new RecordItem(21, '无糖拿铁',$r("app.media.coffee"),FoodCategoryEnum.OTHER, '毫升',  0.43, 0.044, 0.028, 0.016),
  new RecordItem(22, '豆浆',$r("app.media.soybean_milk"),FoodCategoryEnum.OTHER, '毫升',  0.31, 0.012, 0.030, 0.016),
]

const workouts: RecordItem[] = [
  new RecordItem(10000, '散步',$r('app.media.ic_walk'), WorkoutCategoryEnum.WALKING, '小时', 111),
  new RecordItem(10001, '快走',$r('app.media.ic_walk'), WorkoutCategoryEnum.WALKING, '小时',  343),
  new RecordItem(10002, '慢跑',$r('app.media.ic_running'), WorkoutCategoryEnum.RUNNING, '小时',  472),
  new RecordItem(10003, '快跑',$r('app.media.ic_running'), WorkoutCategoryEnum.RUNNING, '小时',  652),
  new RecordItem(10004, '自行车',$r('app.media.ic_ridding'), WorkoutCategoryEnum.RIDING, '小时',  497),
  new RecordItem(10005, '动感单车',$r('app.media.ic_ridding'), WorkoutCategoryEnum.RIDING, '小时',  587),
  new RecordItem(10006, '瑜伽',$r('app.media.ic_aerobics'), WorkoutCategoryEnum.AEROBICS, '小时',  172),
  new RecordItem(10007, '健身操',$r('app.media.ic_aerobics'), WorkoutCategoryEnum.AEROBICS, '小时',  429),
  new RecordItem(10008, '游泳',$r('app.media.ic_swimming'), WorkoutCategoryEnum.SWIMMING, '小时',  472),
  new RecordItem(10009, '冲浪',$r('app.media.ic_swimming'), WorkoutCategoryEnum.SWIMMING, '小时',  429),
  new RecordItem(10010, '篮球',$r('app.media.ic_basketball'), WorkoutCategoryEnum.BALLGAME, '小时',  472),
  new RecordItem(10011, '足球',$r('app.media.ic_football'), WorkoutCategoryEnum.BALLGAME, '小时',  515),
  new RecordItem(10012, '排球',$r("app.media.ic_volleyball"), WorkoutCategoryEnum.BALLGAME, '小时',  403),
  new RecordItem(10013, '羽毛球',$r("app.media.ic_badminton"), WorkoutCategoryEnum.BALLGAME, '小时',  386),
  new RecordItem(10014, '乒乓球',$r("app.media.ic_table_tennis"), WorkoutCategoryEnum.BALLGAME, '小时',  257),
  new RecordItem(10015, '哑铃飞鸟',$r("app.media.ic_dumbbell"), WorkoutCategoryEnum.STRENGTH, '小时',  343),
  new RecordItem(10016, '哑铃卧推',$r("app.media.ic_dumbbell"), WorkoutCategoryEnum.STRENGTH, '小时',  429),
  new RecordItem(10017, '仰卧起坐',$r("app.media.ic_sit_up"), WorkoutCategoryEnum.STRENGTH, '小时',  515),
]

class ItemModel{

  getById(id: number, isFood: boolean = true){
    return isFood ? foods[id] : workouts[id - 10000]//判断是食物还是运动,食物就去食物里面找,运动就去运动里面找  从0开始
  }

  //查所有
  list(isFood: boolean = true): RecordItem[]{//默认查食物
    return isFood ? foods : workouts//true返回foods,否则返回workouts
  }

  //按组查询
  listItemGroupByCategory(isFood: boolean = true){
    // 1.判断要处理的是食物还是运动
    let categories = isFood ? FoodCategories : WorkoutCategories
    let items = isFood ? foods: workouts
    // 2.创建空的分组
    let groups = categories.map(itemCategory => new GroupInfo(itemCategory, []))//itemCategory转成GroupInfo  []食物列表
    // 3.遍历记录项列表,将食物添加到对应的分组
    items.forEach(item => groups[item.categoryId].items.push(item))
    // 4.返回结果
    return groups
  }
}

let itemModel = new ItemModel()

export default itemModel as ItemModel

ItemList.ets

import { CommonConstants } from '../../common/constants/CommonConstants'
import ItemModel from '../../model/ItemModel'
import GroupInfo from '../../viewmodel/GroupInfo'
import ItemCategory from '../../viewmodel/ItemCategory'
import RecordItem from '../../viewmodel/RecordItem'
@Component
export default struct ItemList {//导出
  //函数 先无参无返回值 将来点击传入
  showPanel:(item:RecordItem)=>void
  @Prop isFood : boolean


  build() {
    Tabs() { //Tabs本身就是个容器 Tab组件可以实现页面内视图内容快速切换,包含Tabar和TabContent两个部分
      TabContent() {
        this.TabContentBuilder(ItemModel.list(this.isFood))
      }
      .tabBar('全部')

      ForEach(ItemModel.listItemGroupByCategory(this.isFood),
        (group:GroupInfo<ItemCategory,RecordItem>) => {
          TabContent() {
            this.TabContentBuilder(group.items)
          }
          .tabBar(group.type.name)
        })

    }
    .width(CommonConstants.THOUSANDTH_940)
    .height('100%')
    .barMode(BarMode.Scrollable)
  }

  @Builder TabContentBuilder(items:RecordItem[]){
    List({space:CommonConstants.SPACE_10}) {
      ForEach(items, (item:RecordItem) => {
        ListItem() {
          Row({ space: CommonConstants.SPACE_4 }) {
            Image(item.image).width(50)
            Column({ space: CommonConstants.SPACE_4 }) {
              Text(item.name).fontWeight(CommonConstants.FONT_WEIGHT_500)
              Text(`${item.calorie}千卡/${item.unit}`).fontSize(14).fontColor($r('app.color.light_gray'))
            }

            Blank()
            Image($r('app.media.ic_public_add_norm_filled'))
              .width(18)
              .fillColor($r('app.color.primary_color'))
          }
          .width('100%')
          .padding(CommonConstants.SPACE_6)//内边距
        } //饮食不需要删除按钮
        .onClick(()=>this.showPanel(item))

      })
    }

    .width('100%')
    .height('100%')

  }
}

 ItemIndex.ets

import router from '@ohos.router'
import { CommonConstants } from '../common/constants/CommonConstants'
import { RecordTypeEnum, RecordTypes } from '../model/RecordTypeModel'
import RecordService from '../service/RecordService'
import ItemCard from '../view/item/ItemCard'
import ItemList from '../view/item/ItemList'
import ItemPanelHeader from '../view/item/ItemPanelHeader'
import NumberKeyboard from '../view/item/NumberKeyboard'
import RecordItem from '../viewmodel/RecordItem'
import RecordType from '../viewmodel/RecordType'
@Entry
@Component
struct ItemIndex {
  @State amount:number=1//状态变量
  @State value:string=''//用户按键的内容记录在value内 amount最终结果
  @State showPanel:boolean=false//默认不展示
  @State item:RecordItem=null
  @State type:RecordType=RecordTypes[0]
  @State isFood:boolean=true


  onPanelShow(item:RecordItem){
    this.amount=1//每次弹出,需要初始化为原始状态
    this.value=''//每次弹出,初始化为原始状态
    this.item = item//底部面板一弹出 就赋值
    this.showPanel=true
  }

  onPageShow(){
    //1.获取跳转时的参数
    let params:any=router.getParams()
    //2.获取点击的饮食记录类型
    this.type=params.type
    this.isFood=this.type.id !== RecordTypeEnum.WORKOUT
  }

  build() {
    Column() {
      //1.头部导航
      this.Header()
      //2.列表
      ItemList({showPanel:this.onPanelShow.bind(this),isFood:this.isFood})
      .layoutWeight(1)//除头部外 剩下都被列表占用 这样高度固定
      //3.底部面板
      /*Panel 可滑动面板,提供一种轻量的内容展示窗口,方便在不同尺寸中切换。
       需要传入布尔类型的参数 上方有一个状态变量*/
      Panel(this.showPanel){
        //3.1.顶部日期
        ItemPanelHeader()
        //3.2.记录顶卡片
        if(this.item){
          ItemCard({amount:this.amount,item:$item})//item必须用Link传 因为是对象

        }
        //3.3.数字键盘
        NumberKeyboard({amount:$amount,value:$value})

        //3.4.按钮
        Row({space:CommonConstants.SPACE_6}){
          Button('取消')
            .width(120)
            .backgroundColor($r('app.color.light_gray'))
            .type(ButtonType.Normal)
            .borderRadius(6)
            .onClick(()=>this.showPanel=false)//先关闭窗口
          Button('提交')
            .width(120)
            .backgroundColor($r('app.color.primary_color'))
            .type(ButtonType.Normal)
            .borderRadius(6)
            .onClick(()=>{
              //1.持久化保存
              RecordService.insert(this.type.id,this.item.id,this.amount)
                .then(()=>{
                  //2.关闭弹窗
                  this.showPanel=false
                })
            })
        }
        .margin({top:10})
      }
      .mode(PanelMode.Full)//mode是一个枚举 Full默认展现全部
      .dragBar(false)//不能调整高度
      .backgroundMask($r('app.color.light_gray'))//背后颜色
      .backgroundColor(Color.White)
    }
    .width('100%')
    .height('100%')
  }
  @Builder Header(){
    Row(){
        Image($r('app.media.ic_public_back'))
          .width(24)
          .onClick(() => router.back())
      Blank()
      Text(this.type.name).fontWeight(CommonConstants.FONT_WEIGHT_500)
    }
    .width(CommonConstants.THOUSANDTH_940)
    .height(32)
  }
}

3.数据模型——饮食记录

(1)页面整体架构分析

(2)代码及分析(部分)

RecordModel.ets

//RecordModel.ets
/**
 * 数据库建表语句
 */
import relationalStore from '@ohos.data.relationalStore'
import { ColumnInfo, ColumnType } from '../common/bean/ColumnInfo'
import RecordPO from '../common/bean/RecordPO'
import DbUtil from '../common/utils/DbUtil'
const CREATE_TABLE_SQL: string = `
 CREATE TABLE IF NOT EXISTS record (
   id INTEGER PRIMARY KEY AUTOINCREMENT,
   type_id INTEGER NOT NULL,
   item_id INTEGER NOT NULL,
   amount DOUBLE NOT NULL,
   create_time INTEGER NOT NULL
 )
 `
class RecordModel {

}

let recordModel=new RecordModel()

export default  recordModel as RecordModel

RecordType.ets

//RecordType.ets
export default class RecordType{
  /**
   * 类型id
   */
  id: number
  /**
   * 类型名称
   */
  name: ResourceStr
  /**
   * 类型图标
   */
  icon: ResourceStr
  /**
   * 类型推荐最小卡路里
   */
  min: number
  /**
   * 类型推荐最大卡路里
   */
  max: number

  constructor(id: number, name: ResourceStr, icon: ResourceStr, min: number = 0, max: number = 0) {
    this.id = id
    this.name = name
    this.icon = icon
    this.min = min
    this.max = max
  }
}

总结

一次开发多端部署:本次内容为将屏幕面板变为多设备响应式布局。如果使窄版手机屏幕,则按照小屏幕去渲染,如果是宽屏设备,则调整布局模式,将底部调整到侧面,将滑动卡片变为平铺,使其整体更加美观。这种动态的、根据不同屏幕进行不同渲染,得出不同效果的系统为多设备响应式布局。

数据模型——记录项:根据用户需求,本程序增加了食物列表页面和运动列表页面。
食物列表页面中展示了不同种类的食物,分为全部、主食、果蔬、肉蛋奶、坚果、其他六个模块。
运动列表页面中展示了不同的运动方式,分为全部、走路、跑步、骑行、跳操、游泳、打球等模块。两部分列表页面都有名字、图片和单位,数据结构一致,可以把它们出具成一个数据模型。这样一来只需写一套页面,然后渲染数据即可。

数据模型——饮食记录:在前面已经有了饮食和运动数据,本次我们需要对饮食记录进行添加。饮食记录模型的首页中,上半部分是对饮食记录的统计信息,下半部分是饮食记录内的列表信息。列表信息内分为早餐、午餐、晚餐、加餐和运动几部分。所以每一个饮食记录都需要自己的一个分组的信息。

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值