鸿蒙组件扩展与自定义——开发自定义组件、动效与事件

在这里插入图片描述
鸿蒙组件扩展与自定义——开发自定义组件、动效与事件✨
(适配鸿蒙API 9+,建议使用DevEco Studio 4.0+)


学习目标

  1. 掌握鸿蒙自定义组件的完整开发流程(定义/参数/样式/生命周期)
  2. 精通3类核心动效(基础动画/过渡动画/共享元素动画)
  3. 实现手势事件扩展(左滑/长按/拖拽等自定义操作)
  4. 通过「待办事项APP」实战,整合自定义组件+动效+手势全栈能力
  5. 解决组件开发90%的常见问题(样式冲突/动效卡顿/事件穿透等)

开篇:第5篇课后练习答案解析

5.1 练习1:新闻列表添加「下拉刷新+上拉加载」

// 1. 添加刷新/加载状态
@State isRefreshing: boolean = false;
@State isLoadingMore: boolean = false;
@State page: number = 1;

// 2. 包裹Refresh组件实现下拉刷新
Refresh({ refreshing: $isRefreshing, offset: 120 }) {
  List({ space: 15 }) {
    // 新闻列表项...
    // 加载更多标记
    if (this.isLoadingMore) {
      ListItem() {
        Text("加载更多中...")
          .fontSize(16)
          .padding(20)
          .width("100%")
          .textAlign(TextAlign.Center)
      }
    }
  }
  // 上拉加载更多
  .onReachEnd(() => {
    if (!this.isLoadingMore && !this.isRefreshing) {
      this.isLoadingMore = true;
      this.loadMore();  // 调用加载更多逻辑
    }
  })
}
// 下拉刷新事件
.onRefresh(async () => {
  this.page = 1;
  await this.fetchNews();
  this.isRefreshing = false;
});

// 3. 加载更多逻辑
async loadMore() {
  this.page++;
  const newData = await fetchNewsByPage(this.page);
  if (newData.length > 0) {
    this.newsList = this.newsList.concat(newData);
  }
  this.isLoadingMore = false;
}

5.2 练习2:实现新闻详情页

// 1. 在新闻列表项中添加跳转事件
Text(item.title)
  .onClick(() => {
    // 使用路由跳转(需配置pages.json)
    router.push({
      uri: "pages/NewsDetail",
      params: { news: item }
    });
  });

// 2. 详情页接收参数
import router from '@ohos.router';

@Entry
@Component
struct NewsDetail {
  @State news: NewsItem = router.getParams()?.news || {};

  build() {
    Column() {
      Text(this.news.title)
        .fontSize(28)
        .padding(20);
      Text(this.news.content)
        .fontSize(18)
        .padding({ left: 20, right: 20 });
    }
    .width("100%")
    .height("100%");
  }
}

5.3 练习3:收藏功能迁移到RelationalStore

// 1. 初始化收藏表
const CREATE_FAVORITES_TABLE = `
  CREATE TABLE IF NOT EXISTS favorites (
    news_id TEXT PRIMARY KEY
  )
`;

// 2. 检查收藏状态
async function isNewsFavorited(id: string) {
  const store = await initDatabase();
  const query = SqlBuilder.select(["news_id"])
    .from("favorites")
    .where("news_id = ?")
    .bindArgs([id])
    .build();
  const result = await store.query(query, []);
  const isFavorite = result.rowCount > 0;
  result.close();
  return isFavorite;
}

5.4 练习4:请求重试机制

// 重试3次,每次间隔1秒
async function fetchWithRetry(url: string, retryCount = 3): Promise<any> {
  for (let i = 0; i < retryCount; i++) {
    try {
      const response = await axios.get(url);
      return response.data;
    } catch (error) {
      if (i === retryCount - 1) throw error; // 最后一次重试失败,抛出错误
      await new Promise(resolve => setTimeout(resolve, 1000)); // 间隔1秒重试
    }
  }
  return null;
}

一、鸿蒙自定义组件基础

鸿蒙允许开发者封装可复用的UI模块为自定义组件,核心特点:

  • ✅ 独立的布局/样式/逻辑
  • ✅ 支持参数传递与事件回调
  • ✅ 可通过@Styles/@Extend复用样式

1.1 自定义组件的定义与结构

⌨️ 基础自定义按钮组件:

// 自定义按钮组件
@Component
export struct CustomButton {
  // 组件参数
  @Prop title: string;
  @Prop type: "primary" | "secondary" = "primary";

  // 点击事件回调
  @Prop onClick: () => void;

  build() {
    Button(this.title)
      .backgroundColor(this.type === "primary" ? "#007DFF" : "#E0E0E0")
      .fontColor(this.type === "primary" ? "#FFFFFF" : "#333333")
      .width(160)
      .height(45)
      .borderRadius(8)
      .onClick(() => this.onClick());
  }
}

// 使用自定义按钮
@Entry
@Component
struct TestPage {
  build() {
    Column({ space: 20 }) {
      CustomButton({ title: "提交", onClick: () => console.log("提交成功") });
      CustomButton({ title: "取消", type: "secondary", onClick: () => console.log("取消") });
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center);
  }
}

💡 命名规范:自定义组件首字母大写,参数使用@Prop/@Link等状态装饰器传递。

1.2 样式复用:@Styles与@Extend

1.2.1 @Styles:组件内样式复用
@Entry
@Component
struct TestPage {
  // 定义组件内可复用样式
  @Styles primaryTextStyle() {
    .fontSize(20)
    .fontColor("#007DFF")
    .fontWeight(FontWeight.Bold)
  }

  build() {
    Text("标题")
      .primaryTextStyle(); // 直接引用
    Text("副标题")
      .primaryTextStyle()
      .fontSize(18); // 可叠加修改
  }
}
1.2.2 @Extend:全局样式扩展(仅支持内置组件)
// 扩展Text组件的样式(全局可用)
@Extend(Text) function titleStyle() {
  .fontSize(24)
  .fontWeight(FontWeight.Bold)
  .margin({ bottom: 10 })
}

@Entry
@Component
struct TestPage {
  build() {
    Text("全局扩展标题")
      .titleStyle(); // 全局引用
  }
}

二、鸿蒙动效系统

鸿蒙提供4层动效能力,满足从基础到复杂的UI动效需求:

2.1 基础动画:animation

应用于元素属性变化(如尺寸/颜色/位置),自动生成过渡动画:

@Entry
@Component
struct AnimationTest {
  @State scale: number = 1;

  build() {
    Column() {
      Button("点击缩放")
        .width(150)
        .height(50)
        .scale({ x: this.scale, y: this.scale })
        // 动画配置:持续300ms,缓动曲线,无限循环
        .animation({ duration: 300, curve: Curve.EaseInOut, iterations: -1 })
        .onClick(() => this.scale = this.scale === 1 ? 1.2 : 1);
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center);
  }
}

2.2 过渡动画:transition

应用于元素的显示/隐藏(如ForEach添加/删除项):

@Entry
@Component
struct TransitionTest {
  @State showItem: boolean = false;

  build() {
    Column() {
      Button(this.showItem ? "隐藏" : "显示")
        .onClick(() => this.showItem = !this.showItem);

      if (this.showItem) {
        Text("过渡动画测试")
          .fontSize(24)
          .padding(20)
          .backgroundColor("#007DFF")
          // 入场动画:从下往上+透明度渐变
          .transition({
            type: TransitionType.Insert,
            opacity: { from: 0, to: 1 },
            translate: { from: { y: 50 }, to: { y: 0 } }
          })
          // 退场动画:从上往下+透明度渐变
          .transition({
            type: TransitionType.Delete,
            opacity: { from: 1, to: 0 },
            translate: { from: { y: 0 }, to: { y: -50 } }
          })
      }
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center);
  }
}

2.3 共享元素动画:sharedTransition

应用于页面跳转时的元素过渡(如商品图片从列表到详情页的放大动画):

// 列表页
Image({ uri: item.image })
  .width(120)
  .height(120)
  // 标记共享元素ID
  .sharedTransition(this.item.id, { duration: 500 });

// 详情页
Image({ uri: this.news.image })
  .width("100%")
  .height(300)
  // 匹配相同的ID
  .sharedTransition(this.news.id, { duration: 500 });

三、事件扩展与手势处理

鸿蒙支持内置事件(onClick/onChange)和自定义手势事件(触摸/滑动/拖拽)。

3.1 自定义事件与参数传递

⌨️ 自定义组件传递点击事件并返回参数:

// 自定义卡片组件
@Component
export struct CustomCard {
  @Prop data: { id: number; title: string };

  // 声明自定义事件(携带id参数)
  controller: EventEmitter = new EventEmitter();

  build() {
    Column() {
      Text(this.data.title)
        .fontSize(20)
        .padding(20)
        .onClick(() => {
          // 触发自定义事件,传递参数
          this.controller.emit({
            eventId: 1,
            params: { cardId: this.data.id }
          });
        });
    }
    .backgroundColor(Color.White)
    .borderRadius(8);
  }
}

// 使用自定义卡片
@Entry
@Component
struct TestPage {
  build() {
    Column() {
      CustomCard({ 
        data: { id: 1, title: "卡片1" },
        controller: new EventEmitter().on(1, (event) => {
          console.log("点击了卡片:", event.params.cardId); // 输出:1
        })
      });
    }
  }
}

3.2 手势事件:onPan(滑动)与onLongPress(长按)

⌨️ 实现左滑删除功能:

@Entry
@Component
struct SwipeDeleteTest {
  @State offsetX: number = 0;
  @State isDeleted: boolean = false;

  build() {
    Column() {
      if (!this.isDeleted) {
        Row() {
          Text("左滑删除")
            .padding(20)
            .width("100%")
            .backgroundColor(Color.White)
            .borderRadius(8);

          Button("删除")
            .width(80)
            .height(60)
            .backgroundColor(Color.Red)
        }
        .width("90%")
        .translate({ x: this.offsetX }) // 跟随滑动偏移
        // 监听滑动事件
        .onPan((event) => {
          switch (event.action) {
            case PanDirection.Left:
              this.offsetX = Math.max(-80, event.offsetX); // 左滑最多80px
              break;
            case PanDirection.Right:
              this.offsetX = Math.min(0, event.offsetX); // 右滑最多0px
              break;
            case PanDirection.End:
              // 滑动结束,判断是否删除
              if (this.offsetX <= -40) {
                this.isDeleted = true;
              } else {
                this.offsetX = 0;
              }
              break;
          }
        });
      }
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center);
  }
}

四、实战:待办事项APP(自定义组件+动效+手势)

4.1 需求分析

  1. 自定义待办卡片组件,支持标题/优先级/状态
  2. 待办添加/删除时的过渡动画
  3. 左滑删除、长按编辑的手势操作
  4. 优先级颜色区分(高:红色/中:黄色/低:绿色)

4.2 完整代码实现

// 1. 定义待办数据类型
type TodoPriority = "high" | "medium" | "low";
interface TodoItem {
  id: number;
  title: string;
  priority: TodoPriority;
  isCompleted: boolean;
}

// 2. 自定义待办卡片组件
@Component
export struct TodoCard {
  @ObjectLink todo: TodoItem;
  controller: EventEmitter;

  // 优先级颜色映射
  private priorityColorMap: Record<TodoPriority, string> = {
    high: "#FF6B35",
    medium: "#FFD93D",
    low: "#6BCF7F"
  };

  build() {
    Row({ space: 15 }) {
      // 优先级标识
      Circle({ width: 12, height: 12 })
        .fill(this.priorityColorMap[this.todo.priority]);

      // 待办标题
      Text(this.todo.title)
        .fontSize(18)
        .fontColor(this.todo.isCompleted ? "#999999" : "#333333")
        .textDecoration({ type: this.todo.isCompleted ? TextDecorationType.LineThrough : TextDecorationType.None })
        .width("70%");

      // 完成状态
      Checkbox()
        .select(this.todo.isCompleted)
        .onChange((isChecked) => this.todo.isCompleted = isChecked);
    }
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(8)
    .width("100%")
    // 长按编辑
    .onLongPress(() => this.controller.emit({ eventId: 1, params: this.todo }))
  }
}

// 3. 主页面
@Entry
@Component
struct TodoApp {
  @Observed
  private todos: TodoItem[] = [
    { id: 1, title: "学习鸿蒙自定义组件", priority: "high", isCompleted: false },
    { id: 2, title: "实现待办动效", priority: "medium", isCompleted: false },
    { id: 3, title: "学习手势处理", priority: "low", isCompleted: false }
  ];
  @State inputText: string = "";

  // 待办过渡动画
  private transitionStyle = {
    insert: TransitionEffect.Translate({ from: { y: 50 }, to: { y: 0 } })
      .combine(TransitionEffect.Opacity({ from: 0, to: 1 })),
    delete: TransitionEffect.Translate({ from: { y: 0 }, to: { y: -50 } })
      .combine(TransitionEffect.Opacity({ from: 1, to: 0 }))
  };

  build() {
    Column({ space: 15 }) {
      // 标题
      Text("待办事项")
        .fontSize(32)
        .fontWeight(FontWeight.Bold)
        .padding(20)
        .alignSelf(HorizontalAlign.Start);

      // 新增待办输入框
      Row({ space: 10 }) {
        TextField({ placeholder: "请输入待办内容" })
          .width("75%")
          .height(45)
          .backgroundColor("#F5F5F5")
          .borderRadius(8)
          .onChange((value) => this.inputText = value);

        CustomButton({ title: "添加", onClick: () => this.addTodo() });
      }
      .padding({ left: 20, right: 20 });

      // 待办列表
      List({ space: 15 }) {
        ForEach(this.todos, (item) => {
          ListItem() {
            TodoCard({
              todo: $item,
              controller: new EventEmitter().on(1, (event) => {
                console.log("长按待办:", event.params);
              })
            })
            // 应用过渡动画
            .transition(this.transitionStyle);
          }
          // 左滑删除
          .onPan((event) => {
            if (event.action === PanDirection.Left && event.offsetX <= -80) {
              this.todos = this.todos.filter(todo => todo.id !== item.id);
            }
          });
        });
      }
      .padding({ left: 20, right: 20 });
    }
    .width("100%")
    .height("100%")
    .backgroundColor("#F5F5F5");
  }

  // 添加待办
  addTodo() {
    if (this.inputText.trim() === "") return;
    const newTodo: TodoItem = {
      id: Date.now(),
      title: this.inputText,
      priority: "medium",
      isCompleted: false
    };
    this.todos.push(newTodo);
    this.inputText = "";
  }
}

4.3 运行效果

✅ 自定义待办卡片支持优先级颜色、完成状态、长按编辑
✅ 待办添加时从下往上渐入,删除时从上往下渐出
✅ 支持左滑删除、长按编辑、点击完成
✅ 输入框添加待办后自动清空


五、开发最佳实践

  1. 组件粒度:自定义组件保持小而精,单组件职责不超过3个
  2. 样式隔离:使用@Styles/@Extend避免样式冲突
  3. 动效优化:动画时长控制在150-300ms,避免复杂动画叠加
  4. 事件性能:手势事件使用debounce/throttle避免频繁触发
  5. 状态管理:复杂组件状态使用@Provide/@Consume或状态管理库

六、课后练习

  1. 为待办APP添加编辑功能,长按后弹窗修改内容
  2. 实现待办分类(工作/生活/学习),支持切换筛选
  3. 添加待办提醒功能,使用鸿蒙通知API
  4. 优化左滑删除动画,添加删除确认弹窗

💡 提示:待办提醒可使用鸿蒙的NotificationManager发送本地通知。


七、总结与后续学习规划

7.1 本次学习成果

✅ 掌握了自定义组件的开发流程与样式复用
✅ 精通了鸿蒙动效系统的3类核心动效
✅ 实现了左滑/长按等自定义手势操作
✅ 构建了完整的待办事项APP,整合所有知识点

7.2 后续学习路径

第7篇:鸿蒙应用测试与调试——单元测试、UI自动化、性能调优
第8篇:鸿蒙应用打包与发布——签名申请、上架流程、版本更新

7.3 学习建议

  • 多封装组件:将项目中重复的UI模块封装为自定义组件
  • 学习动效库:尝试使用鸿蒙社区的动效库(如ohos-animation)
  • 关注手势交互:参考主流APP的手势设计,提升用户体验

下一篇,我们将学习鸿蒙应用测试与调试,确保应用质量与性能!🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值