
鸿蒙组件扩展与自定义——开发自定义组件、动效与事件✨
(适配鸿蒙API 9+,建议使用DevEco Studio 4.0+)
学习目标
- 掌握鸿蒙自定义组件的完整开发流程(定义/参数/样式/生命周期)
- 精通3类核心动效(基础动画/过渡动画/共享元素动画)
- 实现手势事件扩展(左滑/长按/拖拽等自定义操作)
- 通过「待办事项APP」实战,整合自定义组件+动效+手势全栈能力
- 解决组件开发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 需求分析
- 自定义待办卡片组件,支持标题/优先级/状态
- 待办添加/删除时的过渡动画
- 左滑删除、长按编辑的手势操作
- 优先级颜色区分(高:红色/中:黄色/低:绿色)
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 运行效果
✅ 自定义待办卡片支持优先级颜色、完成状态、长按编辑
✅ 待办添加时从下往上渐入,删除时从上往下渐出
✅ 支持左滑删除、长按编辑、点击完成
✅ 输入框添加待办后自动清空
五、开发最佳实践
- 组件粒度:自定义组件保持小而精,单组件职责不超过3个
- 样式隔离:使用@Styles/@Extend避免样式冲突
- 动效优化:动画时长控制在150-300ms,避免复杂动画叠加
- 事件性能:手势事件使用debounce/throttle避免频繁触发
- 状态管理:复杂组件状态使用@Provide/@Consume或状态管理库
六、课后练习
- 为待办APP添加编辑功能,长按后弹窗修改内容
- 实现待办分类(工作/生活/学习),支持切换筛选
- 添加待办提醒功能,使用鸿蒙通知API
- 优化左滑删除动画,添加删除确认弹窗
💡 提示:待办提醒可使用鸿蒙的NotificationManager发送本地通知。
七、总结与后续学习规划
7.1 本次学习成果
✅ 掌握了自定义组件的开发流程与样式复用
✅ 精通了鸿蒙动效系统的3类核心动效
✅ 实现了左滑/长按等自定义手势操作
✅ 构建了完整的待办事项APP,整合所有知识点
7.2 后续学习路径
第7篇:鸿蒙应用测试与调试——单元测试、UI自动化、性能调优
第8篇:鸿蒙应用打包与发布——签名申请、上架流程、版本更新
7.3 学习建议
- 多封装组件:将项目中重复的UI模块封装为自定义组件
- 学习动效库:尝试使用鸿蒙社区的动效库(如ohos-animation)
- 关注手势交互:参考主流APP的手势设计,提升用户体验
下一篇,我们将学习鸿蒙应用测试与调试,确保应用质量与性能!🚀

被折叠的 条评论
为什么被折叠?



