实现菜谱二级联动导航

本篇案例主要介绍如何基于List组件实现一个导航和内容的二级联动效果。样例主要包含以下功能:

  • 切换左侧导航,右侧滚动到对应的内容。
  • 滚动右侧的内容,左侧会切换对应的导航。

1. 案例效果截图

2. 案例运用到的知识点

2.1. 核心知识点

  • List:列表包含一系列相同宽度的列表项。适合连续、多行呈现同类数据,例如图片和文本。
  • ListItemGroup:该组件用来展示列表item分组,宽度默认充满List组件,必须配合List组件来使用。

2.2. 其他知识点

  • ArkTS 语言基础
  • 自定义组件和组件生命周期
  • V1状态管理:@State、@Prop
  • 自定义构建函数:@Builder
  • 渲染控制:ForEach
  • 内置组件:Column/Text/Row/
  • 常量与资源分类的访问
  • MVVM模式

3. 代码结构

├──entry/src/main/ets                // 代码区
│  ├──common
│  │  └──constants
│  │     └──Constants.ets            // 常量类
│  ├──entryability
│  │  └──EntryAbility.ets            // 程序入口类
│  ├──pages
│  │  └──IndexPage.ets               // 二级联动页面入口
│  ├──view
│  │  ├──ClassityItem.ets            // 课程分类组件
│  │  └──CourseItem.ets              // 课程信息组件
│  └──viewmodel                          
│     ├──ClassifyModel.ets           // 导航Model
│     ├──ClassifyViewModel.ets       // 导航ViewModel
│     ├──CourseModel.ets             // 课程内容model
│     └──LinkDataModel.ets           // 数据源model
└──entry/src/main/resources          // 资源文件

4. 公共文件与资源

本案例涉及到的常量类和工具类代码如下:

4.1. 通用常量类

// entry/src/main/ets/common/constants/Constant.ets
export default class Constants {
  static readonly LABEL_FONT_WEIGHT: number = 400
  static readonly TITLE_FONT_WEIGHT: number = 500
  static readonly COURSE_ITEM_PADDING: number = 12
  static readonly LOADING_DURATION: number = 2000
  static readonly TITLE_LINE_NUMBER: number = 2
  static readonly FULL_PERCENT: string = '100%'
  static readonly CLASSIFY_TITLE_PERCENT: string = '55%'
}

本案例涉及到的资源文件如下:

4.2. string.json

// entry/src/main/resources/base/element/string.json
{
  "string": [
    {
      "name": "module_desc",
      "value": "模块描述"
    },
    {
      "name": "EntryAbility_desc",
      "value": "description"
    },
    {
      "name": "EntryAbility_label",
      "value": "二级联动"
    },
    {
      "name": "loading",
      "value": "加载数据中..."
    },
    {
      "name": "free_price",
      "value": "免费"
    },
    {
      "name": "price_str",
      "value": "¥%d"
    },
    {
      "name": "hei_ti_medium",
      "value": "HarmonyHeiTi-Medium"
    },
    {
      "name": "hei_ti",
      "value": "HarmonyHeiTi"
    }
  ]
}

4.3. float.json

// entry/src/main/resources/base/element/float.json
{
  "float": [
    {
      "name": "classify_item_width",
      "value": "100vp"
    },
    {
      "name": "dish_item_height",
      "value": "96vp"
    },
    {
      "name": "classify_item_height",
      "value": "56vp"
    },
    {
      "name": "title_line_height",
      "value": "20vp"
    },
    {
      "name": "dish_item_padding",
      "value": "12vp"
    },
    {
      "name": "item_padding_left",
      "value": "8vp"
    },
    {
      "name": "normal_border_radius",
      "value": "18vp"
    },
    {
      "name": "header_font_size",
      "value": "18fp"
    },
    {
      "name": "normal_font_size",
      "value": "14fp"
    }
  ]
}

4.4. color.json

// entry/src/main/resources/base/element/color.json
{
  "color": [
    {
      "name": "start_window_background",
      "value": "#FFFFFF"
    },
    {
      "name": "classify_background",
      "value": "#0D182431"
    },
    {
      "name": "base_background",
      "value": "#F1F3F5"
    },
    {
      "name": "base_font_color",
      "value": "#182431"
    },
    {
      "name": "normal_font_color",
      "value": "#99182431"
    },
    {
      "name": "price_color",
      "value": "#FA2A2D"
    }
  ]
}

其他资源请到源码中获取。

5. 定义数据结构和数据初始化

5.1. 菜品模型:DishModel

// entry/src/main/ets/viewModel/DishModel.ets
export default class DishModel {
  classifyId: number
  dishId: number
  dishName: string
  imageUrl: string
  views: string

  constructor(
    classifyId: number, dishId: number, dishName: string, 
    imageUrl: string, views: string
  ) {
    this.classifyId = classifyId
    this.dishId = dishId
    this.dishName = dishName
    this.imageUrl = imageUrl
    this.views = views
  }
}

5.2. 分类模型:ClassifyModel

// entry/src/main/ets/viewModel/ClassifyModel.ets
import DishModel from './DishModel'

export default class ClassifyModel {
  classifyId: number
  classifyName: string
  dishList: Array<DishModel>

  constructor(
    classifyId: number, classifyName: string, dishList: Array<DishModel>
  ) {
    this.classifyId = classifyId
    this.classifyName = classifyName
    this.dishList = dishList
  }
}

5.3. 数据初始化模型:LinkDataModel

// entry/src/main/ets/viewModel/LinkDataModel.ets
export default class LinkDataModel {
  superId: number
  superName: string
  id: number
  dishName: string
  imageUrl: string
  views: string

  constructor(superId: number, superName: string, id: number, 
              dishName: string, imageUrl: string, views: string) {
    this.superId = superId
    this.superName = superName
    this.id = id
    this.dishName = dishName
    this.imageUrl = imageUrl
    this.views = views
  }
}

5.4. 数据初始化:ClassifyViewModel

// entry/src/main/ets/viewModel/ClassifyViewModel.ets
import ClassifyModel from './ClassifyModel'
import DishModel from './DishModel'
import LinkDataModel from './LinkDataModel'

class ClassifyViewModel {
  getLinkData(): Array<ClassifyModel> {
    let linkDataList: Array<ClassifyModel> = []
    let superId: number = 0
    LINK_DATA.forEach((item: LinkDataModel) => {
      let result: ClassifyModel | undefined = linkDataList.find(
        (value: ClassifyModel) => value.classifyId === item.superId)
      if (result !== undefined) {
        let dishItem: DishModel = new DishModel(
          superId, item.id, item.dishName, item.imageUrl, item.views)
        result.dishList.push(dishItem)
      } else {
        let classifyItem: ClassifyModel 
          = new ClassifyModel(item.superId, item.superName, [])
        linkDataList.push(classifyItem)
      }
    })
    return linkDataList
  }
}

let classifyViewModel = new ClassifyViewModel()

export default classifyViewModel as ClassifyViewModel

const LINK_DATA: LinkDataModel[] = [
  new LinkDataModel(1, '荤菜', 785855, '小炒牛肉', 'https://s1.cdn.jiaonizuocai.com/videoImg/201510/1313/561c9a314c8bb.jpg/OTAweDYwMA', '961.2万'),
  new LinkDataModel(1, '荤菜', 785834, '红烧肉', 'https://s1.cdn.jiaonizuocai.com/videoImg/201510/1311/561c79f4d4e14.jpg/OTAweDYwMA', '3672.3万'),
  new LinkDataModel(1, '荤菜', 786082, '糖醋排骨', 'https://s1.cdn.jiaonizuocai.com/videoImg/201509/0722/55ed97982b6fc.JPG/OTAweDYwMA', '3334.9万'),
  new LinkDataModel(2, '鱼类', 778816, '清蒸鱼', 'https://s1.cdn.jiaonizuocai.com/caipu/201508/3113/311354095180.jpg/OTAweDYwMA', '1358.8万'),
  new LinkDataModel(3, '家常菜', 801048, '西红柿炒鸡蛋', 'https://s1.cdn.jiaonizuocai.com/caipu/201601/1914/191406289.jpg/OTAweDYwMA', '624.1万'),
  new LinkDataModel(1, '荤菜', 780819, '蒜香排骨', 'https://s1.cdn.jiaonizuocai.com/videoImg/201509/0722/55eda07f5974c.JPG/OTAweDYwMA', '949.4万'),
  new LinkDataModel(4, '素菜', 775734, '鱼香茄子', 'https://s1.cdn.jiaonizuocai.com/caipu/201508/1115/042148249114.jpg/OTAweDYwMA', '1106.6万'),
  new LinkDataModel(1, '荤菜', 808716, '土豆炖牛肉', 'https://s1.cdn.jiaonizuocai.com/caipu/201603/0818/081841111186.jpg/OTAweDYwMA', '552.7万'),
  new LinkDataModel(2, '鱼类', 778785, '红烧鲫鱼', 'https://s1.cdn.jiaonizuocai.com/caipu/201508/3111/311153367572.jpg/OTAweDYwMA', '919.6万'),
  new LinkDataModel(1, '荤菜', 216633, '红烧肉', 'https://s1.cdn.jiaonizuocai.com/caipu/201502/0914/091427111222.jpg/OTAweDYwMA', '611.6万'),
  new LinkDataModel(5, '烘焙', 835381, '可可坚果曲奇饼干', 'https://s1.cdn.jiaonizuocai.com/caipu/201605/1515/151554197122.jpg/OTAweDYwMA', '13.5万'),
  new LinkDataModel(5, '烘焙', 773496, '轻乳酪蛋糕', 'https://s1.cdn.jiaonizuocai.com/caipu/201507/2722/272226259107.jpg/OTAweDYwMA', '108.1万'),
  new LinkDataModel(5, '烘焙', 789140, '玛格丽特饼干', 'https://s1.cdn.jiaonizuocai.com/caipu/201511/1414/141539449397.jpg/OTAweDYwMA', '79.0万'),
  new LinkDataModel(5, '烘焙', 769836, '黑森林蛋糕', 'https://s1.cdn.jiaonizuocai.com/caipu/201506/2714/271419323579.jpg/OTAweDYwMA', '65.2万'),
  new LinkDataModel(5, '烘焙', 837068, '古典巧克力蛋糕', 'https://s1.cdn.jiaonizuocai.com/caipu/201605/1718/171804353178.jpg/OTAweDYwMA', '5.9万'),
  new LinkDataModel(5, '烘焙', 798225, '抹茶蛋糕卷', 'https://s1.cdn.jiaonizuocai.com/caipu/201601/0811/081154448687.jpg/OTAweDYwMA', '35.4万'),
  new LinkDataModel(5, '烘焙', 837729, '巧克力豆饼干', 'https://s1.cdn.jiaonizuocai.com/caipu/201605/1816/181618262807.jpg/OTAweDYwMA', '27.1万'),
  new LinkDataModel(5, '烘焙', 776491, '菠萝包', 'https://s1.cdn.jiaonizuocai.com/caipu/201508/1617/200105387287.jpg/OTAweDYwMA', '71.0万'),
  new LinkDataModel(5, '烘焙', 818315, '磨牙棒', 'https://s1.cdn.jiaonizuocai.com/caipu/201604/1710/171031541292.jpg/OTAweDYwMA', '21.7万'),
  new LinkDataModel(5, '烘焙', 217520, '港式吐司', 'https://s1.cdn.jiaonizuocai.com/caipu/201503/2020/202041142728.jpg/OTAweDYwMA', '11.1万'),
  new LinkDataModel(4, '素菜', 799859, '青椒炒香干', 'https://s1.cdn.jiaonizuocai.com/caipu/201601/1423/211412215476.jpg/OTAweDYwMA', '73.5万'),
  new LinkDataModel(7, '凉菜', 793956, '凉拌黄豆芽', 'https://s1.cdn.jiaonizuocai.com/caipu/201512/1409/170911571133.jpg/OTAweDYwMA', '151.2万'),
  new LinkDataModel(4, '素菜', 813538, '虎皮青椒', 'https://s1.cdn.jiaonizuocai.com/caipu/201603/3010/301011441374.jpg/OTAweDYwMA', '739.1万'),
  new LinkDataModel(4, '素菜', 787825, '番茄炒西兰花', 'https://s1.cdn.jiaonizuocai.com/videoImg/201511/0314/56384dbb6eccc.jpg/OTAweDYwMA', '421.9万'),
  new LinkDataModel(4, '素菜', 838447, '清炒茭白', 'https://s1.cdn.jiaonizuocai.com/caipu/201605/1915/191526528108.jpg/OTAweDYwMA', '184.5万'),
  new LinkDataModel(10, '汤类', 833605, '红豆薏米水', 'https://s1.cdn.jiaonizuocai.com/caipu/201605/1309/130946355952.jpg/OTAweDYwMA', '48.1万'),
  new LinkDataModel(4, '素菜', 834648, '清炒山药西兰花', 'https://s1.cdn.jiaonizuocai.com/caipu/201605/1415/141509466883.jpg/OTAweDYwMA', '99.4万'),
  new LinkDataModel(9, '主食', 834271, '泡菜饼', 'https://s1.cdn.jiaonizuocai.com/caipu/201605/1405/140546102661.jpg/OTAweDYwMA', '34.3万'),
  new LinkDataModel(4, '素菜', 834306, '莴笋炒鸡蛋', 'https://s1.cdn.jiaonizuocai.com/caipu/201605/1408/140812295301.jpg/OTAweDYwMA', '74.2万'),
  new LinkDataModel(7, '凉菜', 837294, '葱油爽脆南瓜', 'https://s1.cdn.jiaonizuocai.com/caipu/201605/1722/172215217867.jpg/OTAweDYwMA', '48.4万'),
  new LinkDataModel(7, '凉菜', 827955, '皮蛋拌豆腐', 'https://s1.cdn.jiaonizuocai.com/caipu/201604/2121/212305195442.jpg/OTAweDYwMA', '388.8万'),
  new LinkDataModel(7, '凉菜', 778444, '凉拌海带丝', 'https://s1.cdn.jiaonizuocai.com/videoImg/201508/2722/55df165f2fa58.JPG/OTAweDYwMA', '617.5万'),
  new LinkDataModel(7, '凉菜', 805657, '凉拌芹菜', 'https://s1.cdn.jiaonizuocai.com/caipu/201602/1820/182006369191.jpg/OTAweDYwMA', '194.7万'),
  new LinkDataModel(7, '凉菜', 793956, '凉拌黄豆芽', 'https://s1.cdn.jiaonizuocai.com/caipu/201512/1409/170911571133.jpg/OTAweDYwMA', '151.2万'),
  new LinkDataModel(7, '凉菜', 790528, '凉拌藕片', 'https://s1.cdn.jiaonizuocai.com/caipu/201511/2314/231418172139.jpg/OTAweDYwMA', '283.5万'),
  new LinkDataModel(7, '凉菜', 835028, '玫瑰山药', 'https://s1.cdn.jiaonizuocai.com/caipu/201605/1507/150729137308.jpg/OTAweDYwMA', '44.9万'),
  new LinkDataModel(7, '凉菜', 832891, '凉拌黑木耳', 'https://s1.cdn.jiaonizuocai.com/caipu/201605/1120/112055191758.jpg/OTAweDYwMA', '168.4万'),
  new LinkDataModel(7, '凉菜', 772022, '生椒凉拌黑木耳', 'https://s1.cdn.jiaonizuocai.com/caipu/201507/1715/171548454593.jpg/OTAweDYwMA', '71.7万'),
  new LinkDataModel(7, '凉菜', 805785, '凉拌豆角', 'https://s1.cdn.jiaonizuocai.com/caipu/201602/1917/191726567021.jpg/OTAweDYwMA', '307.7万'),
  new LinkDataModel(1, '荤菜', 786082, '糖醋排骨', 'https://s1.cdn.jiaonizuocai.com/videoImg/201509/0722/55ed97982b6fc.JPG/OTAweDYwMA', '3334.9万'),
  new LinkDataModel(1, '荤菜', 778097, '香辣牛肉', 'https://s1.cdn.jiaonizuocai.com/caipu/201508/2613/261328491342.jpg/OTAweDYwMA', '140.0万'),
  new LinkDataModel(4, '素菜', 780102, '麻婆豆腐', 'https://s1.cdn.jiaonizuocai.com/videoImg/201509/0721/55ed95c20615a.JPG/OTAweDYwMA', '1706.6万'),
  new LinkDataModel(1, '荤菜', 785822, '宫保鸡丁', 'https://s1.cdn.jiaonizuocai.com/videoImg/201510/1310/561c71166b43e.jpg/OTAweDYwMA', '1442.2万'),
  new LinkDataModel(1, '荤菜', 816684, '土豆炖排骨', 'https://s1.cdn.jiaonizuocai.com/caipu/201604/1115/111530241183.jpg/OTAweDYwMA', '812.7万'),
  new LinkDataModel(4, '素菜', 813538, '虎皮青椒', 'https://s1.cdn.jiaonizuocai.com/caipu/201603/3010/301011441374.jpg/OTAweDYwMA', '739.1万'),
  new LinkDataModel(1, '荤菜', 777356, '菠萝咕噜肉', 'https://s1.cdn.jiaonizuocai.com/caipu/201508/2119/081045069894.jpg/OTAweDYwMA', '333.6万'),
  new LinkDataModel(1, '荤菜', 805787, '秘制红焖羊肉', 'https://s1.cdn.jiaonizuocai.com/caipu/201602/1917/191735075144.jpg/OTAweDYwMA', '200.6万'),
  new LinkDataModel(3, '家常菜', 785839, '蒜苔炒腊肉', 'https://s1.cdn.jiaonizuocai.com/videoImg/201510/1313/561c91d3a7653.jpg/OTAweDYwMA', '304.5万'),
  new LinkDataModel(2, '鱼类', 778786, '红烧鱼块', 'https://s1.cdn.jiaonizuocai.com/caipu/201508/3112/311201167492.jpg/OTAweDYwMA', '865.1万'),
  new LinkDataModel(1, '荤菜', 884945, '四川辣子鸡', 'https://s1.cdn.jiaonizuocai.com/caipu/201609/0820/082033178610.jpg/OTAweDYwMA', '119.0万'),
  new LinkDataModel(1, '荤菜', 869093, '水煮鱼', 'https://s1.cdn.jiaonizuocai.com/caipu/201608/1201/12105013291.jpg/OTAweDYwMA', '576.8万'),
  new LinkDataModel(9, '主食', 841128, '四川担担面', 'https://s1.cdn.jiaonizuocai.com/caipu/201605/2321/241506061981.jpg/OTAweDYwMA', '100.9万'),
  new LinkDataModel(1, '荤菜', 891830, '四川椒麻鸡', 'https://s1.cdn.jiaonizuocai.com/caipu/201609/2010/201007216534.jpg/OTAweDYwMA', '89.3万'),
  new LinkDataModel(1, '荤菜', 832801, '豉汁蒸排骨', 'https://s1.cdn.jiaonizuocai.com/caipu/201605/1118/112329444160.jpg/OTAweDYwMA', '139.6万'),
  new LinkDataModel(2, '鱼类', 887384, '川味水煮鱼', 'https://s1.cdn.jiaonizuocai.com/caipu/201609/1220/122045335648.jpg/OTAweDYwMA', '64.4万'),
  new LinkDataModel(1, '荤菜', 848610, '干煸牛肉丝', 'https://s1.cdn.jiaonizuocai.com/caipu/201606/1821/182152287631.jpg/OTAweDYwMA', '150.0万'),
  new LinkDataModel(1, '荤菜', 867697, '重庆辣子鸡', 'https://s1.cdn.jiaonizuocai.com/caipu/201608/0804/080441068530.jpg/OTAweDYwMA', '115.1万'),
  new LinkDataModel(3, '家常菜', 775083, ' 鱼香肉丝', 'https://s1.cdn.jiaonizuocai.com/caipu/201508/0715/071505539312.jpg/OTAweDYwMA', '200.2万'),
  new LinkDataModel(1, '荤菜', 876373, '辣子鸡丁', 'https://s1.cdn.jiaonizuocai.com/caipu/201608/2723/272304117232.jpg/OTAweDYwMA', '220.7万'),
  new LinkDataModel(9, '主食', 778426, '豆角焖面', 'https://s1.cdn.jiaonizuocai.com/videoImg/201510/2013/5625d476b1d48.jpg/OTAweDYwMA', '716.6万'),
  new LinkDataModel(9, '主食', 785494, '葱油饼', 'https://s1.cdn.jiaonizuocai.com/caipu/201510/1811/181119578074.jpg/OTAweDYwMA', '348.7万'),
  new LinkDataModel(9, '主食', 778826, '油泼面', 'https://s1.cdn.jiaonizuocai.com/caipu/201606/0322/032223367102.jpg/OTAweDYwMA', '904.2万'),
  new LinkDataModel(9, '主食', 814477, '韭菜盒子', 'https://s1.cdn.jiaonizuocai.com/caipu/201604/0300/030002493912.jpg/OTAweDYwMA', '174.6万'),
  new LinkDataModel(9, '主食', 782948, '阳春面', 'https://s1.cdn.jiaonizuocai.com/caipu/201510/0110/011059237019.jpg/OTAweDYwMA', '82.4万'),
  new LinkDataModel(9, '主食', 769530, '海鲜意大利面', 'https://s1.cdn.jiaonizuocai.com/caipu/201506/2416/071619394874.jpg/OTAweDYwMA', '154.6万'),
  new LinkDataModel(9, '主食', 797708, '南瓜馒头', 'https://s1.cdn.jiaonizuocai.com/caipu/201601/0609/06090112749.jpg/OTAweDYwMA', '146.1万'),
  new LinkDataModel(9, '主食', 807546, '旺仔小馒头', 'https://s1.cdn.jiaonizuocai.com/caipu/201603/0215/021509133265.jpg/OTAweDYwMA', '55.5万'),
  new LinkDataModel(9, '主食', 791681, '红糖馒头', 'https://s1.cdn.jiaonizuocai.com/caipu/201512/0108/010831371861.jpg/OTAweDYwMA', '159.0万'),
  new LinkDataModel(9, '主食', 832780, '牛奶刀切馒头', 'https://s1.cdn.jiaonizuocai.com/caipu/201605/1117/111715571754.jpg/OTAweDYwMA', '34.3万'),
  new LinkDataModel(10, '汤类', 876125, '牛肉汤', 'https://s1.cdn.jiaonizuocai.com/caipu/201608/2718/271828041490.jpg/OTAweDYwMA', '100.9万'),
  new LinkDataModel(10, '汤类', 771608, '西班牙蔬菜冷汤', 'https://s1.cdn.jiaonizuocai.com/caipu/201507/1413/141314488556.jpg/OTAweDYwMA', '2.2万'),
  new LinkDataModel(10, '汤类', 780219, '奶油蘑菇汤', 'https://s1.cdn.jiaonizuocai.com/caipu/201509/1116/111618148851.jpg/OTAweDYwMA', '112.5万'),
  new LinkDataModel(10, '汤类', 878722, '奶油南瓜浓汤', 'https://s1.cdn.jiaonizuocai.com/caipu/201608/3017/071527326993.jpg/OTAweDYwMA', '28.4万'),
  new LinkDataModel(10, '汤类', 791832, '奶油蘑菇汤', 'https://s1.cdn.jiaonizuocai.com/caipu/201512/0206/020647208391.jpg/OTAweDYwMA', '21.4万'),
  new LinkDataModel(10, '汤类', 882037, '蘑菇奶油浓汤', 'https://s1.cdn.jiaonizuocai.com/caipu/201609/0414/041445309000.jpg/OTAweDYwMA', '58.0万'),
  new LinkDataModel(10, '汤类', 834075, '法式奶油蘑菇汤', 'https://s1.cdn.jiaonizuocai.com/caipu/201605/1320/132010025630.jpg/OTAweDYwMA', '20.3万'),
  new LinkDataModel(10, '汤类', 893258, '奶油蘑菇汤', 'https://s1.cdn.jiaonizuocai.com/caipu/201609/2214/221448417863.jpg/OTAweDYwMA', '12.6万'),
  new LinkDataModel(10, '汤类', 817885, '双孢菇奶油浓汤', 'https://s1.cdn.jiaonizuocai.com/caipu/201604/1522/162208419460.jpg/OTAweDYwMA', '4.5万'),
  new LinkDataModel(10, '汤类', 805209, '奶油南瓜浓汤', 'https://s1.cdn.jiaonizuocai.com/caipu/201602/1515/151510239140.jpg/OTAweDYwMA', '8.7万'),
  new LinkDataModel(4, '素菜', 778804, '黄豆芽炒粉条', 'https://s1.cdn.jiaonizuocai.com/caipu/201508/3113/311309175276.jpg/OTAweDYwMA', '399.3万'),
  new LinkDataModel(3, '家常菜', 778433, '木耳炒鸡蛋', 'https://s1.cdn.jiaonizuocai.com/videoImg/201508/2720/55defe33a9f40.JPG/OTAweDYwMA', '415.9万'),
  new LinkDataModel(9, '主食', 780078, '疙瘩汤', 'https://s1.cdn.jiaonizuocai.com/videoImg/201509/0722/55ed9d661ae37.JPG/OTAweDYwMA', '745.0万'),
  new LinkDataModel(11, '虾蟹类', 782253, '清蒸大闸蟹', 'https://s1.cdn.jiaonizuocai.com/caipu/201509/2600/26001924238.jpg/OTAweDYwMA', '122.5万'),
  new LinkDataModel(2, '粥类', 825833, '皮蛋瘦肉粥', 'https://s1.cdn.jiaonizuocai.com/caipu/201604/1820/182014224139.jpg/OTAweDYwMA', '278.8万'),
  new LinkDataModel(4, '素菜', 787717, '红烧冬瓜', 'https://s1.cdn.jiaonizuocai.com/videoImg/201510/3013/5632fdfbd19c5.jpg/OTAweDYwMA', '752.5万'),
  new LinkDataModel(3, '家常菜', 801048, '西红柿炒鸡蛋', 'https://s1.cdn.jiaonizuocai.com/caipu/201601/1914/191406289.jpg/OTAweDYwMA', '624.1万'),
  new LinkDataModel(7, '凉菜', 808259, '凉拌金针菇', 'https://s1.cdn.jiaonizuocai.com/caipu/201603/0613/061327511225.jpg/OTAweDYwMA', '198.8万'),
  new LinkDataModel(2, '粥类', 800443, '腊八粥', 'https://s1.cdn.jiaonizuocai.com/caipu/201601/1709/170957578030.jpg/OTAweDYwMA', '139.0万'),
  new LinkDataModel(3, '家常菜', 847345, '肉末芥兰', 'https://s1.cdn.jiaonizuocai.com/caipu/201606/1512/151228169151.jpg/OTAweDYwMA', '13.6万'),
  new LinkDataModel(1, '荤菜', 847174, '猪肉脯', 'https://s1.cdn.jiaonizuocai.com/caipu/201606/1419/141923517247.jpg/OTAweDYwMA', '57.4万'),
  new LinkDataModel(7, '凉菜', 845554, '三文鱼蔬菜色拉', 'https://s1.cdn.jiaonizuocai.com/caipu/201606/0900/090038146021.jpg/OTAweDYwMA', '17.7万'),
  new LinkDataModel(7, '凉菜', 847037, '咸蛋黄酿五花肉', 'https://s1.cdn.jiaonizuocai.com/caipu/201606/1413/141306187943.jpg/OTAweDYwMA', '49.3万'),
  new LinkDataModel(7, '凉菜', 847222, '凉拌空心菜', 'https://s1.cdn.jiaonizuocai.com/caipu/201606/1422/142204442229.jpg/OTAweDYwMA', '52.8万'),
  new LinkDataModel(11, '虾蟹类', 846778, '黄金蝴蝶虾', 'https://s1.cdn.jiaonizuocai.com/caipu/201606/1315/131802164544.jpg/OTAweDYwMA', '38.5万'),
  new LinkDataModel(5, '烘焙', 886037, '软面包', 'https://s1.cdn.jiaonizuocai.com/caipu/201609/1016/101601068769.jpg/OTAweDYwMA', '36.9万'),
  new LinkDataModel(5, '烘焙', 930245, '软面包', 'https://s1.cdn.jiaonizuocai.com/caipu/201703/2308/230800452523.jpg/OTAweDYwMA', '9.7万'),
  new LinkDataModel(5, '烘焙', 787748, '香甜软面包', 'https://s1.cdn.jiaonizuocai.com/caipu/201511/0320/032019467036.jpg/OTAweDYwMA', '18.0万'),
  new LinkDataModel(5, '烘焙', 889806, '松软小面包', 'https://s1.cdn.jiaonizuocai.com/caipu/201609/1623/171115558624.jpg/OTAweDYwMA', '8.8万'),
  new LinkDataModel(5, '烘焙', 849358, '香甜软面包', 'https://s1.cdn.jiaonizuocai.com/caipu/201606/2100/210017219022.jpg/OTAweDYwMA', '5.4万'),
  new LinkDataModel(5, '烘焙', 786799, '全麦软面包', 'https://s1.cdn.jiaonizuocai.com/caipu/201510/2718/081946527521.jpg/OTAweDYwMA', '5.8万'),
  new LinkDataModel(5, '烘焙', 892020, '超软全麦面包', 'https://s1.cdn.jiaonizuocai.com/caipu/201609/2014/201441546363.jpg/OTAweDYwMA', '6.2万'),
  new LinkDataModel(5, '烘焙', 811829, '免揉超软小面包', 'https://s1.cdn.jiaonizuocai.com/caipu/201603/2218/221853559099.jpg/OTAweDYwMA', '7.0万'),
  new LinkDataModel(5, '烘焙', 940790, '蛋糕软面包', 'https://s1.cdn.jiaonizuocai.com/caipu/201705/0822/082240456329.jpg/OTAweDYwMA', '1.9万'),
  new LinkDataModel(5, '烘焙', 798476, '椰香软面包', 'https://s1.cdn.jiaonizuocai.com/caipu/201601/0915/091541485253.jpg/OTAweDYwMA', '1.3万'),
  new LinkDataModel(2, '粥类', 889627, '鸡肉青菜粥', 'https://s1.cdn.jiaonizuocai.com/caipu/201609/1617/161750058722.jpg/OTAweDYwMA', '15.2万'),
  new LinkDataModel(2, '粥类', 898289, '南瓜小米粥', 'https://s1.cdn.jiaonizuocai.com/caipu/201610/1219/121934468146.jpg/OTAweDYwMA', '3.5万'),
  new LinkDataModel(2, '粥类', 926909, '八宝粥营养改良版', 'https://s1.cdn.jiaonizuocai.com/caipu/201703/0817/081716469456.jpg/OTAweDYwMA', '9.4万'),
  new LinkDataModel(2, '粥类', 892051, '小米红豆粥', 'https://s1.cdn.jiaonizuocai.com/caipu/201609/2015/201530034501.jpg/OTAweDYwMA', '11.3万'),
  new LinkDataModel(2, '粥类', 874927, '香菇青菜粥', 'https://s1.cdn.jiaonizuocai.com/caipu/201608/2615/261531387841.jpg/OTAweDYwMA', '22.4万'),
  new LinkDataModel(2, '粥类', 899937, '红枣山药豆粥', 'https://s1.cdn.jiaonizuocai.com/caipu/201610/1917/19173957277.jpg/OTAweDYwMA', '25.0万'),
  new LinkDataModel(2, '粥类', 877626, '小米玉米粥', 'https://s1.cdn.jiaonizuocai.com/caipu/201608/2913/291350153179.jpg/OTAweDYwMA', '8.4万'),
  new LinkDataModel(2, '粥类', 951982, '潮汕海鲜粥', 'https://s1.cdn.jiaonizuocai.com/caipu/201707/1010/101030575958.jpg/OTAweDYwMA', '17.7万'),
  new LinkDataModel(2, '粥类', 876215, '砂锅皮蛋瘦肉粥', 'https://s1.cdn.jiaonizuocai.com/caipu/201608/2719/271951393632.jpg/OTAweDYwMA', '14.2万')
]

6. 功能实现

6.1. 首页面

// entry/src/main/ets/pages/Index.ets
import Constants from '../common/constants/Constants'
import ClassifyModel from '../viewModel/ClassifyModel'
import DishModel from '../viewModel/DishModel'
import DishItem from '../views/DishItem'
import ClassifyItem from '../views/ClassifyItem'
import ClassifyViewModel from '../viewModel/ClassifyViewModel'

@Entry
@Component
struct IndexPage {
  // 2. 定义模拟延迟加载的变量
  @State requestSuccess: boolean = false
  
  @State currentClassify: number = 0

  // 5. 初始化数据-2
  private classifyList: Array<ClassifyModel> = []
  
  private classifyScroller: Scroller = new Scroller()
  private scroller: Scroller = new Scroller()

  aboutToAppear() {
    setTimeout(() => {
      // 5. 初始化数据-3
      this.classifyList = ClassifyViewModel.getLinkData()

      // 2. 模拟延迟加载-2
      this.requestSuccess = true
    }, Constants.LOADING_DURATION)
  }

  // 8. 定义 ClassifyHeader @Builder -2
  @Builder ClassifyHeader(classifyName: string) {
    Row() {
      Text(classifyName)
        .fontSize($r('app.float.header_font_size'))
        .fontColor($r('app.color.base_font_color'))
        .fontFamily($r('app.string.hei_ti_medium'))
        .fontWeight(Constants.TITLE_FONT_WEIGHT)
    }
    .padding({ left: $r('app.float.item_padding_left') })
    .height($r('app.float.classify_item_height'))
    .width(Constants.FULL_PERCENT)
    .backgroundColor($r('app.color.base_background'))
  }

  classifyChangeAction(index: number, isClassify: boolean): void {
    if (this.currentClassify !== index) {
      this.currentClassify = index;
      if (isClassify) {
        this.scroller.scrollToIndex(index);
      } else {
        this.classifyScroller.scrollToIndex(index);
      }
    }
  }

  build() {
    // 1. 定义容器
    Row() {
      // 2. 模拟延迟加载-1
      if (this.requestSuccess) {

        // 4. 渲染左边的分类列表
        List({ scroller: this.classifyScroller }) {
          // 5. 初始化数据-1
          ForEach(this.classifyList, (item: ClassifyModel, index?: number) => {
            ListItem() {
              
              ClassifyItem({
                // 6. TODO:定义ClassifyItem,只要一个classifyName
                classifyName: item.classifyName,

                
                isSelected: this.currentClassify === index,
                onClickAction: () => {
                  if (index !== undefined) {
                    this.classifyChangeAction(index, true);
                  }
                }
              })
            }
          }, (item: ClassifyModel) => item.classifyName + this.currentClassify)
        }
        .height(Constants.FULL_PERCENT)
        .width($r('app.float.classify_item_width'))
        .backgroundColor($r('app.color.classify_background'))
        .scrollBar(BarState.Off)

        // 7. 定义右边菜品列表
        List({ scroller: this.scroller }) {
          ForEach(this.classifyList, (classifyItem: ClassifyModel) => {
            ListItemGroup({
              // 8. 定义 ClassifyHeader @Builder
              header: this.ClassifyHeader(classifyItem.classifyName),
              space: Constants.COURSE_ITEM_PADDING
            }) {
              ForEach(classifyItem.dishList, (dishItem: DishModel) => {
                ListItem() {
                  // 9. TODO: 引用 DishItem,等待细化
                  DishItem({ itemStr: JSON.stringify(dishItem) })
                }
              }, (dishItem: DishModel) => `${dishItem.dishId}`)
            }
          }, (item: ClassifyModel) => `${item.classifyId}`)
        }
        .height(Constants.FULL_PERCENT)
        .width(Constants.FULL_PERCENT)
        .padding({ 
          left: $r('app.float.item_padding_left'), 
          right: $r('app.float.dish_item_padding')
        })
        .sticky(StickyStyle.Header)
        .layoutWeight(1)
        .edgeEffect(EdgeEffect.None)
        .onScrollIndex((start: number) 
          => this.classifyChangeAction(start, false))
        
      } else {
        // 3. 延迟加载的话,显示一个loading
        Text($r('app.string.loading'))
          .fontFamily($r('app.string.hei_ti_medium'))
          .textAlign(TextAlign.Center)
          .height(Constants.FULL_PERCENT)
          .width(Constants.FULL_PERCENT)
      }
    }
    .backgroundColor($r('app.color.base_background'))
  }
}

6.2. 分类组件

// entry/src/main/ets/views/ClassifyItem.ets
import Constants from '../common/constants/Constants'

@Component
export default struct ClassifyItem {
  classifyName?: string
  @Prop isSelected: boolean = false
  onClickAction = (): void => {}

  build() {
    Text(this.classifyName)
      .fontSize($r('app.float.normal_font_size'))
      .fontColor(this.isSelected 
                 ? $r('app.color.base_font_color') 
                 : $r('app.color.normal_font_color'))
      .fontFamily(this.isSelected 
                  ? $r('app.string.hei_ti_medium') : $r('app.string.hei_ti'))
      .fontWeight(this.isSelected 
                  ? Constants.TITLE_FONT_WEIGHT : Constants.LABEL_FONT_WEIGHT)
      .textAlign(TextAlign.Center)
      .backgroundColor(this.isSelected 
                       ? $r('app.color.base_background') : '')
      .width(Constants.FULL_PERCENT)
      .height($r('app.float.classify_item_height'))
      .onClick(this.onClickAction)
  }
}

6.3. 菜品组件

// entry/src/main/ets/views/DishItem.ets
import Constants from '../common/constants/Constants'
import DishModel from '../viewModel/DishModel'

@Component
export default struct DishItem {
  @Prop itemStr: string = ''
  item?: DishModel

  aboutToAppear() {
    this.item = JSON.parse(this.itemStr) as DishModel;
  }

  build() {
    Row() {
      Image(this.item !== undefined ? this.item?.imageUrl : '')
        .height(Constants.FULL_PERCENT)
        .aspectRatio(1)
      Column() {
        Text(this.item?.dishName)
          .fontSize($r('app.float.header_font_size'))
          .fontColor($r('app.color.base_font_color'))
          .fontFamily($r('app.string.hei_ti_medium'))
          .maxLines(Constants.TITLE_LINE_NUMBER)
          .textOverflow({ overflow: TextOverflow.Clip })
          .lineHeight($r('app.float.title_line_height'))
          .width(Constants.FULL_PERCENT)
        Text() {
          Span($r('app.string.views_str'))
          Span(`: ${this.item?.views}`)
        }
          .fontSize($r('app.float.normal_font_size'))
          .fontColor($r('app.color.price_color'))
          .fontFamily($r('app.string.hei_ti_medium'))
      }
      .padding($r('app.float.dish_item_padding'))
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Start)
      .justifyContent(FlexAlign.SpaceBetween)
      .height(Constants.FULL_PERCENT)
    }
    .clip(true)
    .borderRadius($r('app.float.normal_border_radius'))
    .backgroundColor($r('app.color.start_window_background'))
    .width('100%')
    .height($r('app.float.dish_item_height'))
  }
}

6.4. 开启网络访问权限

// entry/src/main/module.json5
{
  "module": {
    // ...
    "requestPermissions": [
      {
        "name" : "ohos.permission.INTERNET"
      }
    ]
  }
}

7. 代码与视频教程

完整案例代码与视频教程请参见:

代码:Code-05-03.zip。

视频:《实现菜谱二级联动导航》。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值