HarmonyOS 5 双向滚动课程表:技术实现与交互设计解析(附:源代码)

2025-06-29 08:52:04
107次阅读
0个评论

screenshots (4).gif

在移动应用开发中,复杂数据的可视化展示一直是用户体验的关键环节。本文将围绕鸿蒙OS平台的双向滚动课程表展开,深入解析其技术实现原理、交互设计逻辑以及双向滚动功能的应用场景,为开发者提供从原理到实践的完整技术指南。

鸿蒙OS双向滚动课程表的核心架构

鸿蒙OS的课程表应用采用了双向滚动技术,实现了时间维度与课程维度的高效展示。整个系统由以下几个核心部分构成:

数据模型设计

课程表的基础数据模型定义了课程的基本属性,包括课程名称和背景颜色,这些属性不仅用于展示课程信息,还通过不同颜色区分不同课程类型,提升视觉辨识度:

export class Course {
  public name: ResourceStr = '-'
  public backColor: ResourceColor = '#EDC7FF'

  constructor(name: ResourceStr, backgroundColor: ResourceColor) {
    this.name = name;
    this.backColor = backgroundColor;
  }
}

课程数据数组COURSE_MODEL预定义了各类课程实例,包括空课程和不同科目的课程,每种课程分配了独特的背景色以增强可视化效果:

export const COURSE_MODEL: Course[] = [new Course($r('app.string.empty'), Color.White),
  new Course($r('app.string.course8'), '#EDC7FF'), new Course($r('app.string.course5_1'), '#FFE1E8'),
  // 更多课程定义...
  new Course($r('app.string.course9'), '#ECDC96')]

双向滚动核心实现

双向滚动功能的实现依赖于鸿蒙OS的滚动控制器机制,通过多个Scroller实例协同工作,实现水平与垂直方向的滚动同步:

@Entry
@Component
export struct Index {
  // 滚动控制器定义
  classScroller = new Scroller();
  timeScroller = new Scroller();
  weekdaysScroller = new Scroller();
  horizontalScroller = new Scroller();
  verticalScroller = new Scroller();
  
  // 课程数据矩阵,实现二维数据展示
  data: Course[][] = [
    [COURSE_MODEL[0], COURSE_MODEL[1], COURSE_MODEL[4], COURSE_MODEL[1],
      COURSE_MODEL[10], COURSE_MODEL[2], COURSE_MODEL[0]],
    // 更多课程数据...
  ]
  // 其他组件状态定义...
}

核心布局部分通过嵌套的Scroll组件实现双向滚动功能,水平滚动控制器与垂直滚动控制器通过事件监听实现联动:

Column() {
  // 表头部分,包含星期和课时信息
  Row() {
    // 课时序号列
    Column() {
      Text('节')
        .fontSize(14)
        .fontWeight(400)
        .textAlign(TextAlign.Center)
    }
    .width(50)
    .height(42)
    
    // 时间列
    Column() {
      Text('上课时间')
        .fontSize(14)
        .fontWeight(400)
        .textAlign(TextAlign.Center)
    }
    .width(60)
    .height(42)
    
    // 星期水平滚动列
    Column() {
      Scroll(this.weekdaysScroller) {
        List() {
          ForEach(this.classificationNames, (item: Resource, index: number) => {
            ListItem() {
              Text(item)
                .fontSize(14)
                .textAlign(TextAlign.Center)
            }
          })
        }
      }
      .onScrollFrameBegin((offset: number) => {
        this.horizontalScroller.scrollBy(offset, 0);
        return { offsetRemain: offset };
      })
    }
    .width('70%')
  }
  
  // 内容区双向滚动实现
  Row() {
    // 左侧课时和时间垂直滚动区
    Column() {
      Scroll(this.classScroller) { /* 课时序号垂直滚动 */ }
      Scroll(this.timeScroller) { /* 时间垂直滚动 */ }
    }
    
    // 右侧课程内容双向滚动区
    Column() {
      Scroll(this.horizontalScroller) {
        Scroll(this.verticalScroller) {
          // 课程内容矩阵渲染
          ForEach(this.arr, (_temp: number, index: number) => {
            Row() {
              ForEach(this.data[this.arr[_temp]], (item: Course) => {
                this.itemBuilder(item.name, 100, 120, item.backColor, Color.Black)
              })
            }
          })
        }
        .onScrollFrameBegin((offset: number) => {
          this.classScroller.scrollBy(0, offset);
          this.timeScroller.scrollBy(0, offset)
        })
      }
      .onScrollFrameBegin((offset: number) => {
        this.weekdaysScroller.scrollBy(offset, 0);
      })
    }
  }
}

双向滚动功能的技术原理

双向滚动技术在鸿蒙OS中的实现,突破了传统列表只能单向滚动的限制,通过多个滚动控制器的协同工作,实现了二维数据的高效展示。其核心技术要点包括:

滚动控制器的协同机制

鸿蒙OS的Scroller类提供了精确控制滚动位置的能力,通过scrollByscrollTo方法可以实现程序化滚动,而onScrollFrameBegin事件则允许在滚动过程中实时同步多个控制器的位置:

// 水平滚动同步逻辑
.onScrollFrameBegin((offset: number) => {
  this.horizontalScroller.scrollBy(offset, 0);
  return { offsetRemain: offset };
})

// 垂直滚动同步逻辑
.onScrollFrameBegin((offset: number) => {
  this.verticalScroller.scrollBy(0, offset);
  this.timeScroller.scrollBy(0, offset);
  return { offsetRemain: offset };
})

这种同步机制确保了用户在水平滚动查看不同星期的课程时,左侧的课时和时间列能够保持固定位置,反之亦然,从而实现类似Excel表格的冻结表头效果。

二维数据的渲染优化

课程表采用了二维数组data: Course[][]存储课程数据,通过双重ForEach循环渲染课程卡片,实现了行(课时)与列(星期)的矩阵式展示:

ForEach(this.arr, (_temp: number, index: number) => {
  Row() {
    ForEach(this.data[this.arr[_temp]], (item: Course) => {
      this.itemBuilder(item.name, 100, 120, item.backColor, Color.Black)
    }, (item: Course) => getResourceID().toString())
  }
}, (_temp: number) => _temp + new Date().toString())

这种渲染方式结合滚动控制器,使得大规模课程数据能够按需加载,避免了一次性渲染大量组件带来的性能问题。

交互体验优化细节

课程表在交互细节上做了多项优化,包括:

  1. 初始位置自动定位:根据当前星期自动滚动到对应的日期列
.onAppear(() => {
  if (this.weekDay > 1) {
    this.weekdaysScroller.scrollTo({ xOffset: (this.weekDay - 1) * 120, yOffset: 0 })
    this.horizontalScroller.scrollTo({ xOffset: (this.weekDay - 1) * 120, yOffset: 0 })
  }
})
  1. 卡片式UI设计:每个课程使用独立卡片展示,通过背景色区分不同课程,提升视觉层次感
@Builder
itemBuilder(msg: ResourceStr, curHeight: Length = 100, curWidth: Length = 120,
  curBackgroundColor: ResourceColor = Color.White, curFontColor: ResourceColor = Color.Black) {
  Row() {
    Column() {
      Stack() {
        Column() { }
        .backgroundColor(curBackgroundColor)
        .width('100%')
        .height('100%')
        .opacity(0.7)

        Text(msg)
          .fontSize(12)
          .textAlign(TextAlign.Center)
          .fontColor(curFontColor)
          .opacity(0.9)
          .padding(6)
      }
    }
    .padding(6)
    .width(curWidth)
    .height(curHeight)
    .backgroundColor(Color.White)
  }
}
  1. 标签页导航:通过底部标签页切换不同功能模块,课程表作为核心功能位于默认标签页
@Builder
tabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) {
  Column() {
    Image(this.currentIndex === targetIndex ? selectedImg : normalImg)
      .size({ width: 25, height: 25 })
    Text(title)
      .fontColor(this.currentIndex === targetIndex ? '#0A59F7' : '#60000000')
  }
  .height('100%')
  .margin({ top: 8 })
}

双向滚动技术的应用场景拓展

双向滚动技术不仅适用于课程表,还能在多种需要二维数据展示的场景中发挥价值,其核心优势在于能够同时展示两个维度的信息,并保持维度之间的关联关系。

企业应用中的数据可视化

在企业管理系统中,双向滚动技术可用于:

  1. 财务报表分析:横向展示时间维度(月份/季度),纵向展示科目维度(收入/支出),交叉点显示具体数值
  2. 项目甘特图:横向为时间轴,纵向为任务列表,直观展示项目进度
  3. 人力资源管理:横向为时间周期,纵向为员工列表,展示考勤、绩效等信息

教育领域的创新应用

除课程表外,教育类应用还可拓展:

  1. 知识图谱可视化:横向为学科分支,纵向为难度层级,帮助学生梳理知识结构
  2. 学习进度跟踪:横向为时间维度,纵向为知识点列表,显示掌握程度
  3. 多维度测试分析:横向为题型,纵向为学生列表,展示答题情况

生活服务类应用场景

在生活服务领域,双向滚动技术可用于:

  1. 日程管理:横向为日期,纵向为不同类型的日程(工作/生活/家庭)
  2. 健身数据监测:横向为时间,纵向为健康指标(心率/血压/体重)
  3. 旅行规划:横向为旅行天数,纵向为行程类型(交通/住宿/景点)

技术实现的挑战与优化策略

双向滚动技术在带来强大展示能力的同时,也面临一些技术挑战:

性能优化关键点

  1. 虚拟列表渲染:对于大规模数据,使用LazyForEach代替ForEach,实现按需渲染
// 虚拟列表优化示例
LazyForEach(this.arr, (_temp: number, index: number) => {
  // 渲染逻辑...
}, (_temp: number) => _temp.toString())
  1. 滚动事件节流:限制滚动事件触发频率,避免频繁重绘
// 滚动事件节流实现
let lastScrollTime = 0;
.onScrollFrameBegin((offset: number) => {
  const now = new Date().getTime();
  if (now - lastScrollTime > 100) { // 100ms内只处理一次
    lastScrollTime = now;
    // 滚动同步逻辑...
  }
  return { offsetRemain: offset };
})
  1. 组件复用:将课程卡片等重复组件封装为独立@Builder方法,提高渲染效率

多设备适配方案

  1. 响应式布局:根据屏幕宽度动态调整列宽和卡片大小
@State screenWidth: number = getContentRect().width;

// 根据屏幕宽度获取合适的列宽
private getColumnWidth(): number {
  if (this.screenWidth < 600) return 100;   // 小屏设备
  if (this.screenWidth < 1024) return 120;  // 平板
  return 150;  // 大屏设备
}
  1. 手势适配:针对不同设备优化滚动灵敏度和惯性效果
Scroll(this.horizontalScroller) {
  // 内容...
}
.scrollBar(BarState.Off)
.scrollable(ScrollDirection.Horizontal)
.dragEffect(DragEffect.Fling)  // 惯性滚动效果
.friction(0.8)  // 滚动摩擦系数

附:代码

import { common } from '@kit.AbilityKit';

import { CommonModifier } from '@kit.ArkUI';

let preResourceId = 0;

export class Course {
  public name: ResourceStr = '-'
  public backColor: ResourceColor = '#EDC7FF'

  constructor(name: ResourceStr, backgroundColor: ResourceColor) {
    this.name = name;
    this.backColor = backgroundColor;
  }
}

export const COURSE_MODEL: Course[] = [new Course($r('app.string.empty'), Color.White),
  new Course($r('app.string.course8'), '#EDC7FF'), new Course($r('app.string.course5_1'), '#FFE1E8'),
  new Course($r('app.string.course2'), '#E1F9FF'), new Course($r('app.string.course1_1'), '#E5F0E1'),
  new Course($r('app.string.course7'), '#FFDACC'), new Course($r('app.string.course6'), '#ECFC9A'),
  new Course($r('app.string.course5'), '#FFE1E8'), new Course($r('app.string.course4_1'), '#FFFCA3'),
  new Course($r('app.string.course4'), '#FBF0FF'), new Course($r('app.string.course3'), '#FFE4C7'),
  new Course($r('app.string.course1'), '#FFF0F0'), new Course($r('app.string.course9'), '#ECDC96')]

@Entry
@Component
export struct Index {
  @State classIndex: Array<string> = ['1', '2', '3', '4', '5', '6', '7', '8']
  @State classTime: Array<string> = ['8:30-9:15', '9:25-10:10', '10:30-11:15', '11:15-12:10',
    '14:10-14:55', '15:05-15:45', '15:55-16:30', '16:45-17:00']
  weekDay: number = new Date().getDay()
  classScroller = new Scroller();
  timeScroller = new Scroller();
  weekdaysScroller = new Scroller();
  horizontalScroller = new Scroller();
  verticalScroller = new Scroller();
  data: Course[][] = [
    [COURSE_MODEL[0], COURSE_MODEL[1], COURSE_MODEL[4], COURSE_MODEL[1],
      COURSE_MODEL[10], COURSE_MODEL[2], COURSE_MODEL[0]],
    [COURSE_MODEL[3], COURSE_MODEL[1], COURSE_MODEL[4], COURSE_MODEL[1],
      COURSE_MODEL[10], COURSE_MODEL[2], COURSE_MODEL[0]],
    [COURSE_MODEL[5], COURSE_MODEL[8], COURSE_MODEL[9], COURSE_MODEL[0],
      COURSE_MODEL[0], COURSE_MODEL[7], COURSE_MODEL[10]],
    [COURSE_MODEL[5], COURSE_MODEL[8], COURSE_MODEL[9], COURSE_MODEL[0],
      COURSE_MODEL[0], COURSE_MODEL[7], COURSE_MODEL[10]],
    [COURSE_MODEL[11], COURSE_MODEL[1], COURSE_MODEL[2], COURSE_MODEL[0],
      COURSE_MODEL[0], COURSE_MODEL[0], COURSE_MODEL[0]],
    [COURSE_MODEL[11], COURSE_MODEL[12], COURSE_MODEL[2], COURSE_MODEL[12],
      COURSE_MODEL[0], COURSE_MODEL[0], COURSE_MODEL[0]],
    [COURSE_MODEL[11], COURSE_MODEL[12], COURSE_MODEL[2], COURSE_MODEL[12],
      COURSE_MODEL[4], COURSE_MODEL[5], COURSE_MODEL[0]],
    [COURSE_MODEL[0], COURSE_MODEL[0], COURSE_MODEL[6], COURSE_MODEL[1],
      COURSE_MODEL[4], COURSE_MODEL[5], COURSE_MODEL[6]]
  ]
  arr: number[] = []
  classificationNames: Array<Resource> = [$r('app.string.monday'), $r('app.string.tuesday'),$r('app.string.wednesday'),
    $r('app.string.thursday'), $r('app.string.friday'), $r('app.string.saturday'), $r('app.string.sunday')]
  @State currentIndex: number = 2;
  private controller: TabsController = new TabsController();
  @StorageProp('bottomRectHeight') bottomRectHeight: number = 0;
  @StorageProp('topRectHeight') topRectHeight: number = 0;
  @State tabBarModifier: CommonModifier = new CommonModifier();

  aboutToAppear() {
    for (let index = 0; index < this.classIndex.length; index++) {
      this.arr.push(index)
    }
    this.tabBarModifier.alignRules({})
  }

  @Builder
  itemBuilder(msg: ResourceStr, curHeight: Length = 100, curWidth: Length = 120,
    curBackgroundColor: ResourceColor = Color.White, curFontColor: ResourceColor = Color.Black) {
    Row() {
      Column() {
        Stack() {
          Column() {
          }
          .backgroundColor(curBackgroundColor)
          .width('100%')
          .height('100%')
          .opacity(0.7)

          Text(msg)
            .fontSize(12)
            .fontWeight(400)
            .textAlign(TextAlign.Center)
            .fontColor(curFontColor)
            .opacity(0.9)
            .width('100%')
            .height('100%')
            .padding(6)
        }
      }
      .padding(6)
      .justifyContent(FlexAlign.Center)
      .width(curWidth)
      .height(curHeight)
      .backgroundColor(Color.White)
    }
  }

  @Builder
  tabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) {
    Column() {
      Image(this.currentIndex === targetIndex ? selectedImg : normalImg)
        .size({ width: 25, height: 25 })
      Text(title)
        .fontColor(this.currentIndex === targetIndex ? '#0A59F7' : '#60000000')
        .fontSize(10)
        .fontWeight(500)
        .margin({ top: 4 })
    }
    .height('100%')
    .margin({ top: 8 })
    .width('auto')
    .alignItems(HorizontalAlign.Center)
  }

  build() {
    Tabs({
      barPosition: BarPosition.End,
      index: this.currentIndex,
      controller: this.controller,
      barModifier: this.tabBarModifier
    }) {

      TabContent() {
        Column() {
        }
        .padding({ top: px2vp(this.topRectHeight) })
        .height('100%')
      }
      .height('100%')
      .tabBar(this.tabBuilder('主页', 0, $r('app.media.main_page'), $r('app.media.main_page')))

      TabContent() {
        Column() {
        }
        .padding({ top: px2vp(this.topRectHeight) })
        .height('100%')
      }
      .height('100%')
      .tabBar(this.tabBuilder('消息', 1, $r('app.media.message'), $r('app.media.message')))

      TabContent() {
        Column() {
          Row() {
            Image($r('app.media.back'))
              .width(42)
              .margin({ right: 8 })
            Text('第8周')
              .fontSize(22)
              .fontWeight(800)
              .lineHeight(27)
          }
          .margin({ left: 32, bottom: 8, top: 12 })
          .height(42)
          .width('100%')
          .justifyContent(FlexAlign.Start)

          Column() {
            Row() {
              Column() {
                Text('节')
                  .fontSize(14)
                  .fontWeight(400)
                  .textAlign(TextAlign.Center)
                  .fontColor(Color.Black)
                  .width('100%')
                  .height('100%')
              }
              .justifyContent(FlexAlign.Center)
              .width(50)
              .height(42)

              Column() {
                Text('上课时间')
                  .fontSize(14)
                  .fontWeight(400)
                  .textAlign(TextAlign.Center)
                  .fontColor(Color.Black)
                  .width('100%')
                  .height('100%')
              }
              .justifyContent(FlexAlign.Center)
              .width(60)
              .height(42)

              Column() {
                Scroll(this.weekdaysScroller) {
                  List() {
                    ForEach(this.classificationNames, (item: Resource, index: number) => {
                      ListItem() {
                        Row() {
                          Column() {
                            Text(item)
                              .fontSize(14)
                              .fontWeight(400)
                              .textAlign(TextAlign.Center)
                              .fontColor(Color.Black)
                              .width('100%')
                          }
                          .padding(4)
                          .justifyContent(FlexAlign.Center)
                          .width(120)
                          .height(42)
                          .backgroundColor(Color.White)
                        }
                      }
                    }, (item: string) => item + new Date().toString())
                  }
                  .listDirection(Axis.Horizontal)
                  .edgeEffect(EdgeEffect.None)
                }
                .scrollable(ScrollDirection.Horizontal)
                .scrollBar(BarState.Off)
                .height(42)
                .onScrollFrameBegin((offset: number) => {
                  this.horizontalScroller.scrollBy(offset, 0);
                  return { offsetRemain: offset };
                })
              }
              .width('70%')
            }
            .width('100%')
            .position({ x: 0, y: 0 })
            .backgroundColor(Color.White)
            .borderRadius({ topLeft: 16 })
            .shadow({ radius: 50, color: '#25000000' })
            .zIndex(10)


            Row() {
              Column() {
                Scroll(this.classScroller) {
                  Column() {
                    List({ space: 0, initialIndex: 0 }) {
                      ForEach(this.classIndex, (item: string, index: number) => {
                        ListItem() {
                          Row() {
                            Column() {
                              Text(item)
                                .fontSize(14)
                                .fontWeight(400)
                                .textAlign(TextAlign.Center)
                                .fontColor(Color.Black)
                                .width('100%')
                            }
                            .padding(4)
                            .justifyContent(FlexAlign.Center)
                            .width(50)
                            .height(100)
                            .backgroundColor(Color.White)
                          }
                        }
                      }, (item: string) => JSON.stringify(item))
                    }
                    .listDirection(Axis.Vertical)
                    .edgeEffect(EdgeEffect.None) // 滑动到边缘无效果
                  }
                }
                .position({ x: 0, y: 0 })
                .width(50)
                .backgroundColor('#F8F8FF')
                .scrollBar(BarState.Off)
                .onScrollFrameBegin((offset: number) => {
                  this.verticalScroller.scrollBy(0, offset);
                  this.timeScroller.scrollBy(0, offset);
                  return { offsetRemain: offset };
                })

                Scroll(this.timeScroller) {
                  Column() {
                    List({ space: 0, initialIndex: 0 }) {
                      ForEach(this.classTime, (item: string, index: number) => {
                        ListItem() {
                          Row() {
                            Column() {
                              Text(item.split('-')[0] + ' -')
                                .fontSize(12)
                                .textAlign(TextAlign.Center)
                                .fontColor(Color.Black)
                                .width('100%')
                                .height('50%')
                                .align(Alignment.Bottom)
                                .lineHeight(20)
                              Text(item.split('-')[1])
                                .fontSize(12)
                                .textAlign(TextAlign.Center)
                                .fontColor(Color.Black)
                                .width('100%')
                                .height('50%')
                                .align(Alignment.Top)
                                .lineHeight(20)
                            }
                            .padding(4)
                            .justifyContent(FlexAlign.Center)
                            .width(60)
                            .height(100)
                            .backgroundColor(Color.White)
                          }
                        }
                      }, (item: string) => JSON.stringify(item))
                    }
                    .listDirection(Axis.Vertical)
                    .edgeEffect(EdgeEffect.None)
                  }
                }
                .position({ x: 50, y: 0 })
                .width(60)
                .backgroundColor('#F8F8FF')
                .scrollBar(BarState.Off)
                .onScrollFrameBegin((offset: number) => {
                  this.verticalScroller.scrollBy(0, offset);
                  this.classScroller.scrollBy(0, offset);
                  return { offsetRemain: offset };
                })
              }
              .width(110)
              .height('100%')
              .backgroundColor(Color.White)
              .shadow({ radius: 45, color: '#25000000', offsetY: -15 })
              .padding({top:4, bottom:20})

              Column() {
                Scroll(this.horizontalScroller) {
                  Scroll(this.verticalScroller) {
                    Column() {
                      ForEach(this.arr, (_temp: number, index: number) => {
                        Row() {
                          ForEach(this.data[this.arr[_temp]], (item: Course) => {
                            this.itemBuilder(item.name, 100, 120, item.backColor, Color.Black)
                          }, (item: Course) => getResourceID().toString())
                        }
                      }, (_temp: number) => _temp + new Date().toString())
                    }
                  }
                  .scrollBar(BarState.Off)
                  .scrollable(ScrollDirection.Vertical)
                  .height('100%')
                  .width(this.classificationNames.length * 120)
                  .onScrollFrameBegin((offset: number) => {
                    this.classScroller.scrollBy(0, offset);
                    this.timeScroller.scrollBy(0, offset)
                    return { offsetRemain: offset };
                  })
                }
                .padding({top:4, left:4, right:4, bottom:20})
                .scrollBar(BarState.Off)
                .scrollable(ScrollDirection.Horizontal)
                .onScrollFrameBegin((offset: number) => {
                  this.weekdaysScroller.scrollBy(offset, 0);
                  return { offsetRemain: offset };
                })
              }
              .zIndex(-100)
              .width('70%')
            }
            .position({ x: 0, y: 42 })
            .width('100%')
            .height('100%')
            .backgroundColor(Color.White)
          }
          .height('88%')
          .onAppear(() => {
            if (this.weekDay > 1) {
              this.weekdaysScroller.scrollTo({ xOffset: (this.weekDay - 1) * 120, yOffset: 0 })
              this.horizontalScroller.scrollTo({ xOffset: (this.weekDay - 1) * 120, yOffset: 0 })
            }
          })
          .width('100%')

        }
        .padding({ top: px2vp(this.topRectHeight) })
        .height('100%')
      }
      .height('100%')
      .backgroundColor('#F1F3F5')
      .tabBar(this.tabBuilder('课表', 2, $r('app.media.course_table_clicked'), $r('app.media.course_table_unclicked')))
    }
    .onChange((index: number) => {
      this.currentIndex = index;
    })
    .width('100%')
    .height('100%')
    .barHeight(80)
    .barBackgroundColor('#F1F3F5')
  }
}

function getResourceID(): string {
  preResourceId++;
  return preResourceId.toString()
}

// string.json

{
  "string": [
    {
      "name": "module_desc",
      "value": "module description"
    },
    {
      "name": "EntryAbility_desc",
      "value": "description"
    },
    {
      "name": "EntryAbility_label",
      "value": "label"
    },
    {
      "name": "course1",
      "value": "B4037 软件项目管理   1-18周 2C304"
    },
    {
      "name": "course2",
      "value": "B1469 Java Web框架技术   1-18周 1504"
    },
    {
      "name": "course3",
      "value": "B1427 统一建模语言   1-18周 1509"
    },
    {
      "name": "course4",
      "value": "B1678 网络管理与维护   1-18周 1510"
    },
    {
      "name": "course5",
      "value": "B2857 软件工程   1-18周 1411"
    },
    {
      "name": "course6",
      "value": "B0383 大学生就业指导   1-8周 3406"
    },
    {
      "name": "course1_1",
      "value": "B4037 软件项目管理   1、3、5、7、9、11、13、15、17周 2C304"
    },
    {
      "name": "course7",
      "value": "B3873 信息检索   1-8周 3408"
    },
    {
      "name": "course8",
      "value": "B1478 移动应用开发综合实训   1-18周 2C602"
    },
    {
      "name": "course4_1",
      "value": "B1678 网络管理与维护   2、4、6、8、10、12、14、16、18周 1604"
    },
    {
      "name": "course5_1",
      "value": "B2857 软件工程   1、3、5、7、9、11、13、15、17周 1509"
    },
    {
      "name": "course9",
      "value": "B2857 软件工程综合实训   11-12周 1502"
    },
    {
      "name": "empty",
      "value": "-"
    },
    {
      "name": "monday",
      "value": "星期一"
    },
    {
      "name": "tuesday",
      "value": "星期二"
    },
    {
      "name": "wednesday",
      "value": "星期三"
    },
    {
      "name": "thursday",
      "value": "星期四"
    },
    {
      "name": "friday",
      "value": "星期五"
    },
    {
      "name": "saturday",
      "value": "星期六"
    },
    {
      "name": "sunday",
      "value": "星期天"
    }
  ]
}

鸿蒙OS双向滚动课程表展示了二维数据可视化的优雅解决方案,通过滚动控制器的协同工作和精心设计的UI布局,实现了时间与课程维度的高效展示。这种技术不仅适用于教育领域的课程表,还能在企业管理、健康医疗、生活服务等多个领域发挥重要作用。

对于开发者而言,双向滚动技术的核心在于理解多个滚动控制器的协同机制,以及二维数据的高效渲染方式。通过合理的性能优化和多设备适配策略,可以将这种技术应用于更广泛的场景,为用户提供更加直观、高效的数据可视化体验。随着鸿蒙OS生态的不断发展,双向滚动技术必将在更多创新应用中展现其价值。

收藏00

登录 后评论。没有帐号? 注册 一个。