#星光不负 码向未来# 鸿蒙6.0实战:打造一个“左滑上瘾”的电影清单 App 原创

急行太保忍者神龟
发布于 2025-10-22 20:59
浏览
0收藏

@[toc]

本文目标

哈喽,各位未来的鸿蒙大佬们!欢迎来到我的学习笔记。今天我们要搞个大事——用 ArkTS 做一个酷炫的电影清单。

#星光不负 码向未来# 鸿蒙6.0实战:打造一个“左滑上瘾”的电影清单 App-鸿蒙开发者社区

你以为只是个平平无奇的列表?No no no!我们要实现左滑操作(什么收藏、想看、分享),

#星光不负 码向未来# 鸿蒙6.0实战:打造一个“左滑上瘾”的电影清单 App-鸿蒙开发者社区

还要能一滑到底直接删除
#星光不负 码向未来# 鸿蒙6.0实战:打造一个“左滑上瘾”的电影清单 App-鸿蒙开发者社区

这篇笔记非常适合像我一样刚上路的小白,咱们不求“精通”,只求“这玩意儿我好像搞明白了!”

准备好了吗?发车!

第一站:“装修”工具准备(导入依赖)

#星光不负 码向未来# 鸿蒙6.0实战:打造一个“左滑上瘾”的电影清单 App-鸿蒙开发者社区

万丈高楼平地起,咱得先把“砖头”和“水泥”搬进来。这里我们导入了鸿蒙官方的 UI 组件,还有一个重量级嘉宾——HdsListItem。看名字(Hds…)就知道,这可能是华为设计套件(UIDesignKit)里的“精装房”,能帮我们省不少事儿。

import { promptAction, SymbolGlyphModifier, TextModifier } from '@kit.ArkUI';
import { HdsListItem } from '@kit.UIDesignKit';

第二站:搭建“毛坯房”(组件主体)

#星光不负 码向未来# 鸿蒙6.0实战:打造一个“左滑上瘾”的电影清单 App-鸿蒙开发者社区

@Entry@Component 这俩“门牌号”挂上,咱的 HdsMovieListDemo 组件就正式开张了。

这里有两个核心成员:

  1. @State dataSource: 这位是我们的“数据大管家”,前面挂着 @State 装饰器。你得记住,在 ArkTS 里,被 @State 标记的变量一旦发生变化(比如你添加或删除了电影),UI 就会“嗖”地一下自动刷新。神奇吧!
  2. scroller: 这位是“电梯管理员”,负责控制列表的滚动。

<!-- end list -->

@Entry
@Component
struct HdsMovieListDemo {
  @State dataSource: LazyDataSource<MovieItem> = new LazyDataSource<MovieItem>();
  private scroller: Scroller = new Scroller();

  build() {
    // ... 下面是 build 里的内容 ...
  }
}

第三站:“精装修”开始(build 界面)

#星光不负 码向未来# 鸿蒙6.0实战:打造一个“左滑上瘾”的电影清单 App-鸿蒙开发者社区

build() 方法就是我们的“装修图纸”。我们先用一个 Column 把所有东西竖着(垂直)放好。

  build() {
    Column() {
      // 顶部标题
      Text('我的观影清单')
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
        .fontColor('#2c3e50')
        .margin({ top: 16, bottom: 8, left: 16 })

      // 简介文案
      Text('左滑试试:收藏 / 想看 / 分享;滑到底可自动删除')
        .fontSize(12)
        .fontColor('#7f8c8d')
        .margin({ left: 16, bottom: 12 })
      
      // ... 列表马上就来 ...
    }
    .backgroundColor('#F7F9FC') // 给整个页面来个高级灰背景
    .width('100%')
    .height('100%')
  }

第四站:上硬菜!“无限滚动”列表

#星光不负 码向未来# 鸿蒙6.0实战:打造一个“左滑上瘾”的电影清单 App-鸿蒙开发者社区

来了来了,主角登场!List 就是列表本身。

注意看,我们用的是 LazyForEach,而不是普通的 ForEach。为啥要“偷懒”(Lazy)?

因为“偷懒”是美德!LazyForEach 非常聪明,它只会创建当前屏幕上看得见的列表项。当你滚动时,它会销毁滚出去的,创建刚要滚进来的。这样就算你有 10000 条电影数据,App 也不会卡成 PPT。

LazyForEach 需要两个参数:

  1. this.dataSource:我们的数据大管家。
  2. (item: MovieItem) => ...:一个“模板”,告诉它每一项长啥样。
  3. (item: MovieItem) => \${item.id}-${item.rev}``:这是重点!它为每一项生成一个唯一的 Key。

小白避坑指南:这个 Key 极其重要!我们后面修改了 item.rev (revision,版本号),这个 Key 就会变,LazyForEach 就会知道:“哦!这个 item 被修改了,我必须刷新它!” 否则你改了数据,界面可能傻傻地不动。

      List({ space: 10, scroller: this.scroller }) {
        LazyForEach(this.dataSource, (item: MovieItem) => {
          // ... HdsListItem 马上登场 ...
        }, (item: MovieItem) => `${item.id}-${item.rev}`) // 关键的 Key!
      }
      .margin({ left: 12, right: 12, bottom: 12 })
      .width('100%')
      .layoutWeight(1) // 让列表撑满剩余空间

第五站:列表的“灵魂”—— HdsListItem 与左滑神技

现在我们来定义列表里每一项具体长啥样。还记得吗?我们请了“精装房”施工队 HdsListItem

5.1 列表项的“脸面”(textItem

hdsListItemCard 负责定义卡片长相。我们给它塞了 primaryText(主标题:电影名+年份)和 secondaryText(副标题:标语+状态)。

注意看副标题里的“三元运算符” (item.fav ? ' · 已收藏' : ''),这是程序员的“黑话”,意思就是:如果 item.favtrue,就显示“ · 已收藏”,否则就显示“空字符串”。

          HdsListItem({
            hdsListItemCard: {
              textItem: {
                primaryText: {
                  text: `${item.title}(${item.year})`,
                  modifier: new TextModifier().fontColor('#222').fontSize(16).fontWeight(FontWeight.Medium),
                },
                secondaryText: {
                  text: `${item.tagline}${item.fav ? ' · 已收藏' : ''}${item.wish ? ' · 想看' : ''}${item.shared ? ' · 已分享' : ''}`,
                  modifier: new TextModifier().fontColor('#666').fontSize(12)
                }
              }
            },
            // ... 激动人心的滑动操作来了 ...
          })

5.2 “左滑三连” (swipeActionOptions)

#星光不负 码向未来# 鸿蒙6.0实战:打造一个“左滑上瘾”的电影清单 App-鸿蒙开发者社区

这才是精髓!swipeActionOptions 允许我们定义一个“图标数组”(icons)。

我们来看看第一个“收藏”(爱心)按钮:

  • icon: 图标样式,根据 item.fav 状态切换颜色和大小。
  • backgroundColor: 背景色,同样根据状态切换。
  • onAction: 核心中的核心! 当用户点击这个按钮时:
    1. item.fav = !item.fav;:把收藏状态反转(truefalsefalsetrue)。
    2. item.rev++;版本号+1。还记得上面那个 Key 吗?就是通知 LazyForEach 刷新用的!
    3. this.dataSource.reload();:通知数据大管家:“数据变了,快刷新!”
    4. promptAction.openToast(...):弹出一个“吐司”(Toast)提示用户。

后面两个“想看”(时钟)和“分享”(分享)按钮,逻辑一模一样,换汤不换药。

            swipeActionOptions: {
              icons: [
                {
                  icon: new SymbolGlyphModifier($r('sys.symbol.heart')).fontColor([item.fav ? Color.White : Color.Orange]).fontSize(item.fav ? 18 : 16),
                  backgroundColor: item.fav ? Color.Orange : Color.Grey,
                  onAction: () => {
                    item.fav = !item.fav;
                    item.rev++;
                    this.dataSource.reload();
                    promptAction.openToast({ message: `${item.fav ? '已收藏' : '已取消收藏'}:${item.title}` , duration: 800 });
                  },
                },
                {
                  icon: new SymbolGlyphModifier($r('sys.symbol.clock')).fontColor([item.wish ? Color.White : Color.Green]).fontSize(item.wish ? 18 : 16),
                  backgroundColor: item.wish ? Color.Green : Color.Grey,
                  onAction: () => {
                    item.wish = !item.wish;
                    item.rev++;
                    this.dataSource.reload();
                    promptAction.openToast({ message: `${item.wish ? '加入想看' : '已取消想看'}:${item.title}`, duration: 800 });
                  },
                },
                {
                  icon: new SymbolGlyphModifier($r('sys.symbol.share')).fontColor([item.shared ? Color.White : Color.Blue]).fontSize(item.shared ? 18 : 16),
                  backgroundColor: item.shared ? Color.Blue : Color.Grey,
                  onAction: () => {
                    item.shared = !item.shared;
                    item.rev++;
                    this.dataSource.reload();
                    promptAction.openToast({ message: `${item.shared ? '准备分享' : '取消分享标记'}:${item.title}`, duration: 800 });
                  },
                },
              ],
              // ... 还没完,还有删除操作 ...
            }

5.3 终极大招:滑动删除

HdsListItem 还贴心地提供了两种删除方式:

  1. deleteIconOptions:当你左滑(但没滑到底)时,出现的那个红色“删除”按钮。点击它,触发 onAction,我们调用 this.dataSource.deleteItem(item) 来删除数据。
    #星光不负 码向未来# 鸿蒙6.0实战:打造一个“左滑上瘾”的电影清单 App-鸿蒙开发者社区

  2. fullDeleteOptions:当你“一路向左”滑到底时,直接触发 onFullDeleteAction。同样是删除数据。

#星光不负 码向未来# 鸿蒙6.0实战:打造一个“左滑上瘾”的电影清单 App-鸿蒙开发者社区

我们还加了个 animateTo 动画,让删除看起来更丝滑。

              deleteIconOptions: {
                backgroundColor: Color.Red,
                iconColor: Color.White,
                onAction: () => {
                  promptAction.openToast({ message: `删除:${item.title}`, duration: 800 });
                  // Demo:演示删除
                  this.getUIContext()?.animateTo({ duration: 250 }, () => {
                    this.dataSource.deleteItem(item)
                  });
                },
              },
              fullDeleteOptions: {
                isFullDelete: true,
                onFullDeleteAction: () => {
                  promptAction.openToast({ message: `滑动触发删除:${item.title}`, duration: 800 });
                  this.getUIContext()?.animateTo({ duration: 250 }, () => {
                    this.dataSource.deleteItem(item)
                  });
                },
              },
            }
          })
        // LazyForEach 的 } 在这里
      }
      // List 的 } 在这里
      // ...
    }
    // Column 的 } 在这里
  }
  // build() 的 } 在这里

第六站:“开业大酬宾”—— 填充初始数据

#星光不负 码向未来# 鸿蒙6.0实战:打造一个“左滑上瘾”的电影清单 App-鸿蒙开发者社区

aboutToAppear() 是一个“生命周期”函数。你可以理解为:“当组件马上要显示在屏幕上时”,这个函数就会被调用。

我们在这里塞了 8 条“霸王别姬”、“大话西游”之类的经典电影数据,作为我们的“开业福利”。用 forEach 循环,一条条 pushItemdataSource 里。

  aboutToAppear() {
    const sample: MovieItem[] = [
      new MovieItem('1', '霸王别姬', 1993, '一段爱与执念的绝唱'),
      new MovieItem('2', '大话西游', 1995, '一万年太久,只争朝夕'),
      new MovieItem('3', '卧虎藏龙', 2000, '江湖决绝,剑气如虹'),
      new MovieItem('4', '英雄', 2002, '天下与一人之间的抉择'),
      new MovieItem('5', '一代宗师', 2013, '见自己,见天地,见众生'),
      new MovieItem('6', '刺客聂隐娘', 2015, '无声之刃,心有余音'),
      new MovieItem('7', '流浪地球', 2019, '带着地球去流浪'),
      new MovieItem('8', '长安三万里', 2023, '大唐气象,诗酒年华'),
    ];
    sample.forEach((m): void => this.dataSource.pushItem(m));
  }
} // HdsMovieListDemo 组件的 } 在这里

第七站:定义“电影”的数据结构(MovieItem 类)

#星光不负 码向未来# 鸿蒙6.0实战:打造一个“左滑上瘾”的电影清单 App-鸿蒙开发者社区

这个很简单,就是定义一下“什么才算一部电影”。它需要有 id, title (标题), year (年份) 等等。

注意,fav (收藏), wish (想看), shared (分享) 默认都是 falserev (版本号) 默认是 0,就是我们前面用来刷新UI的“小机关”。

class MovieItem {
  public id: string;
  public title: string;
  public year: number;
  public tagline: string;
  public fav: boolean;
  public wish: boolean;
  public shared: boolean;
  public rev: number;

  constructor(id: string, title: string, year: number, tagline: string) {
    this.id = id;
    this.title = title;
    this.year = year;
    this.tagline = tagline;
    this.fav = false;
    this.wish = false;
    this.shared = false;
    this.rev = 0;
  }
}

最终站:我们的“数据大管家” (LazyDataSource)

#星光不负 码向未来# 鸿蒙6.0实战:打造一个“左滑上瘾”的电影清单 App-鸿蒙开发者社区

这是我们自定义的一个类,它实现了鸿蒙要求的 IDataSource 接口(可以理解为一套“行业规范”)。

LazyForEach 之所以能工作,全靠这个类给它提供了标准化的方法,比如:

  • totalCount(): 告诉列表总共有多少条数据。
  • getData(index: number): 列表问:“我要显示第 5 条,数据是啥?” 这个方法就负责把第 5 条数据递过去。
  • pushItem(item: T): 添加数据,并通知“监听者”(listeners,也就是我们的 List):“嘿!来新数据了!”
  • deleteItem(item: T): 删除数据,并通知“监听者”:“嘿!第 N 条数据没了!”
  • registerDataChangeListener(listener: DataChangeListener): List 一上来就会调用这个,把自己“注册”成一个监听者。
  • reload(): 当我们改了(比如收藏)数据后,我们手动调用这个方法,它会遍历所有监听者,大喊一声:“数据变了,全部刷新!”

<!-- end list -->

export class LazyDataSource<T> implements IDataSource {
  private elements: T[];
  private listeners: Set<DataChangeListener>;

  constructor(elements: T[] = []) {
    this.elements = elements;
    this.listeners = new Set();
  }

  totalCount(): number {
    return this.elements.length;
  }

  getData(index: number): T {
    return this.elements[index];
  }

  indexOf(item: T): number {
    return this.elements.indexOf(item);
  }

  pinItem(item: T, index: number): void {
    this.elements.splice(index, 1);
    this.elements.unshift(item);
    this.listeners.forEach(listener => listener.onDataReloaded());
  }

  pushItem(item: T) {
    this.elements.push(item);
    this.listeners.forEach(listener => listener.onDataAdd(this.elements.length - 1));
  }

  deleteItem(item: T): void {
    const index = this.elements.indexOf(item);
    if (index < 0) {
      return;
    }
    this.elements.splice(index, 1);
    this.listeners.forEach(listener => listener.onDataDelete(index));
  }

  deleteItemByIndex(index: number): void {
    this.elements.splice(index, 1);
    this.listeners.forEach(listener => listener.onDataDelete(index));
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    this.listeners.add(listener);
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    this.listeners.delete(listener);
  }

  public reload(): void {
    this.listeners.forEach((listener: DataChangeListener) => listener.onDataReloaded());
  }
}

总结

呼!搞定!

#星光不负 码向未来# 鸿蒙6.0实战:打造一个“左滑上瘾”的电影清单 App-鸿蒙开发者社区

通过这个 Demo,我们学到了:

  1. 如何使用 @State 和自定义数据源 LazyDataSource 来管理“响应式”数据。
  2. 如何用 List + LazyForEach 高效地显示长列表(以及那个 rev 刷新小技巧!)。
  3. 如何白嫖 HdsListItem 组件,快速实现带左滑操作滑动删除的酷炫列表。
  4. aboutToAppear 生命周期钩子的妙用。

是不是感觉鸿蒙开发好像也挺简单的呀!

没错,跟着我学,肯定简单,赶紧操作起来吧!

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2025-10-22 21:20:50修改
收藏
回复
举报
回复
    相关推荐