《鸿蒙APP开发从入门到精通》第2篇:ArkUI组件库详解与常用组件实战 🎨

内容承接与核心价值
这是《鸿蒙APP开发从入门到精通》的第2篇------组件进阶篇 ,承接第1篇的「Hello World!」基础,100%复用项目架构 ,为后续第6-12篇的电商购物车全栈项目铺垫核心组件基础。
学习目标:
- 理解ArkUI组件的分类与核心特性;
- 掌握15个常用ArkUI组件的用法(布局容器、文本、按钮、输入框、列表、网格等);
- 实现一个电商首页组件(包含轮播图、分类导航、商品列表、底部导航栏);
- 优化组件交互效果(悬停、点击、滑动动画)。
学习重点:
- 弹性布局(Flex)与网格布局(Grid)的深度应用;
- 自定义组件与系统组件的结合使用;
- 响应式布局的实现(适配不同设备尺寸)。
一、 ArkUI组件库概述 📚
1.1 组件分类
HarmonyOS Next的ArkUI组件库分为基础组件 和高级组件:
| 分类 | 组件示例 |
|---|---|
| 基础组件 | Text、Image、Button、Input、TextArea、Checkbox、Radio、Slider、Switch |
| 容器组件 | Column、Row、Flex、Grid、GridItem、List、ListItem、Stack |
| 高级组件 | Swiper(轮播图)、TabContent(标签页)、Carousel(卡片轮播)、WaterFlow(瀑布流) |
1.2 核心特性
- 声明式UI:通过代码描述UI结构,编译器自动渲染;
- 数据驱动:组件属性与状态数据绑定,数据变化自动更新UI;
- 响应式布局:自动适配不同设备尺寸;
- 组件复用:支持自定义组件,提高代码复用率;
- 高性能渲染:采用方舟编译器编译,渲染效率比传统Web开发高10-20倍。
二、 常用组件实战 🛠️
2.1 实战目标
基于第1篇的「MyFirstHarmonyApp」项目架构,创建一个电商首页组件,包含以下功能:
- 轮播图(Swiper):展示促销活动;
- 分类导航(Grid):显示商品分类;
- 商品列表(List):展示热门商品;
- 底部导航栏(TabBar):实现页面切换。
2.2 🔧 项目结构调整
在「entry/src/main/ets」目录下创建以下文件夹:
components:存放自定义组件;models:存放数据模型;utils:存放工具函数。
2.3 🔧 数据模型与工具函数
1. 数据模型
⌨️ entry/src/main/ets/models/HomeModel.ets
typescript
// 轮播图数据模型
export interface BannerModel {
id: number;
imageUrl: string;
linkUrl: string;
}
// 分类导航数据模型
export interface CategoryModel {
id: number;
name: string;
iconUrl: string;
}
// 商品数据模型
export interface GoodsModel {
id: number;
name: string;
imageUrl: string;
price: number;
originalPrice: number;
sales: number;
}
// 底部导航栏数据模型
export interface TabBarModel {
id: number;
name: string;
iconUrl: string;
selectedIconUrl: string;
pageUrl: string;
}
2. 工具函数
⌨️ entry/src/main/ets/utils/ImageUtils.ets
typescript
// 图片加载工具函数
export function getImageUrl(imagePath: string): string {
return `$r('app.media.${imagePath}')`;
}
3. 模拟数据
⌨️ entry/src/main/ets/models/HomeData.ets
typescript
import { BannerModel, CategoryModel, GoodsModel, TabBarModel } from './HomeModel';
import { getImageUrl } from '../utils/ImageUtils';
// 轮播图模拟数据
export const bannerData: Array<BannerModel> = [
{ id: 1, imageUrl: getImageUrl('banner1'), linkUrl: 'pages/ProductDetailPage' },
{ id: 2, imageUrl: getImageUrl('banner2'), linkUrl: 'pages/ProductDetailPage' },
{ id: 3, imageUrl: getImageUrl('banner3'), linkUrl: 'pages/ProductDetailPage' }
];
// 分类导航模拟数据
export const categoryData: Array<CategoryModel> = [
{ id: 1, name: '手机', iconUrl: getImageUrl('category1') },
{ id: 2, name: '平板', iconUrl: getImageUrl('category2') },
{ id: 3, name: '笔记本', iconUrl: getImageUrl('category3') },
{ id: 4, name: '智能穿戴', iconUrl: getImageUrl('category4') },
{ id: 5, name: '家居', iconUrl: getImageUrl('category5') },
{ id: 6, name: '出行', iconUrl: getImageUrl('category6') }
];
// 商品模拟数据
export const goodsData: Array<GoodsModel> = [
{ id: 1, name: '华为Mate 60 Pro', imageUrl: getImageUrl('goods1'), price: 6999, originalPrice: 7999, sales: 12345 },
{ id: 2, name: '华为P60 Pro', imageUrl: getImageUrl('goods2'), price: 5999, originalPrice: 6999, sales: 8765 },
{ id: 3, name: '华为nova 12 Pro', imageUrl: getImageUrl('goods3'), price: 3999, originalPrice: 4999, sales: 6543 },
{ id: 4, name: '华为MateBook X Pro', imageUrl: getImageUrl('goods4'), price: 8999, originalPrice: 9999, sales: 4321 },
{ id: 5, name: '华为Watch GT 4', imageUrl: getImageUrl('goods5'), price: 1499, originalPrice: 1699, sales: 2109 }
];
// 底部导航栏模拟数据
export const tabBarData: Array<TabBarModel> = [
{ id: 1, name: '首页', iconUrl: getImageUrl('tab1'), selectedIconUrl: getImageUrl('tab1_selected'), pageUrl: 'pages/Index' },
{ id: 2, name: '分类', iconUrl: getImageUrl('tab2'), selectedIconUrl: getImageUrl('tab2_selected'), pageUrl: 'pages/CategoryPage' },
{ id: 3, name: '购物车', iconUrl: getImageUrl('tab3'), selectedIconUrl: getImageUrl('tab3_selected'), pageUrl: 'pages/CartPage' },
{ id: 4, name: '我的', iconUrl: getImageUrl('tab4'), selectedIconUrl: getImageUrl('tab4_selected'), pageUrl: 'pages/MyPage' }
];
2.4 🔧 自定义组件实现
1. 轮播图组件
⌨️ entry/src/main/ets/components/BannerComponent.ets
typescript
import { BannerModel } from '../models/HomeModel';
import router from '@ohos.router';
@Component
export struct BannerComponent {
@Prop data: Array<BannerModel> = [];
build() {
Swiper() {
ForEach(this.data, (item: BannerModel) => {
Image(item.imageUrl)
.width('100%')
.height(200)
.objectFit(ImageFit.Cover)
.borderRadius(12)
.onClick(() => {
router.pushUrl({ url: item.linkUrl });
});
}, (item: BannerModel) => item.id.toString());
}
.width('100%')
.height(200)
.autoPlay(true)
.indicator({
style: IndicatorStyle.Number,
isShow: true,
size: 12
})
.loop(true)
.duration(3000);
}
}
2. 分类导航组件
⌨️ entry/src/main/ets/components/CategoryComponent.ets
typescript
import { CategoryModel } from '../models/HomeModel';
import router from '@ohos.router';
@Component
export struct CategoryComponent {
@Prop data: Array<CategoryModel> = [];
build() {
Grid() {
ForEach(this.data, (item: CategoryModel) => {
GridItem() {
Column({ space: 8 }) {
Image(item.iconUrl)
.width(50)
.height(50)
.objectFit(ImageFit.Contain);
Text(item.name)
.fontSize(14)
.textColor('#666666');
}
.width('100%')
.height('100%')
.onClick(() => {
router.pushUrl({ url: 'pages/CategoryPage' });
});
}
.width('33.33%')
.height('auto');
}, (item: CategoryModel) => item.id.toString());
}
.width('100%')
.height('auto')
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('1fr 1fr')
.columnsGap(20)
.rowsGap(20);
}
}
3. 商品列表组件
⌨️ entry/src/main/ets/components/GoodsListComponent.ets
typescript
import { GoodsModel } from '../models/HomeModel';
import router from '@ohos.router';
@Component
export struct GoodsListComponent {
@Prop data: Array<GoodsModel> = [];
build() {
List({ space: 16 }) {
ForEach(this.data, (item: GoodsModel) => {
ListItem() {
GoodsItemComponent({ goods: item });
}
.width('100%')
.height('auto');
}, (item: GoodsModel) => item.id.toString());
}
.width('100%')
.height('auto')
.padding(0, 16, 0, 16);
}
}
@Component
struct GoodsItemComponent {
@Prop goods: GoodsModel;
build() {
Column({ space: 12 }) {
// 商品图片
Image(this.goods.imageUrl)
.width('100%')
.height(200)
.objectFit(ImageFit.Cover)
.borderRadius(12);
// 商品信息
Column({ space: 8 }) {
Text(this.goods.name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.textColor('#000000')
.maxLines(2)
.ellipsis({ overflow: TextOverflow.Ellipsis });
// 价格与销量
Row({ space: 16 }) {
Column({ space: 4 }) {
Text(`¥${this.goods.price}`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.textColor('#FF0000');
Text(`¥${this.goods.originalPrice}`)
.fontSize(14)
.textColor('#999999')
.decoration({ type: TextDecorationType.LineThrough });
}
.alignItems(HorizontalAlign.Start);
Text(`已售${this.goods.sales}`)
.fontSize(14)
.textColor('#666666');
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween);
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.padding(0, 0, 0, 8);
}
.width('100%')
.height('auto')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.onClick(() => {
router.pushUrl({ url: 'pages/ProductDetailPage' });
});
}
}
4. 底部导航栏组件
⌨️ entry/src/main/ets/components/TabBarComponent.ets
typescript
import { TabBarModel } from '../models/HomeModel';
import router from '@ohos.router';
@Component
export struct TabBarComponent {
@Prop data: Array<TabBarModel> = [];
@Prop selectedIndex: number = 0;
build() {
Row({ space: 0 }) {
ForEach(this.data, (item: TabBarModel, index: number) => {
Column({ space: 4 }) {
Image(index === this.selectedIndex ? item.selectedIconUrl : item.iconUrl)
.width(24)
.height(24)
.objectFit(ImageFit.Contain);
Text(item.name)
.fontSize(12)
.textColor(index === this.selectedIndex ? '#007DFF' : '#666666');
}
.width('25%')
.height('100%')
.justifyContent(FlexAlign.Center)
.onClick(() => {
router.pushUrl({ url: item.pageUrl });
});
}, (item: TabBarModel) => item.id.toString());
}
.width('100%')
.height(56)
.backgroundColor('#FFFFFF')
.borderRadius(16, 16, 0, 0)
.padding(0, 8, 0, 8);
}
}
2.5 🔧 首页页面实现
⌨️ entry/src/main/ets/pages/Index.ets(替换默认代码)
typescript
import { BannerComponent } from '../components/BannerComponent';
import { CategoryComponent } from '../components/CategoryComponent';
import { GoodsListComponent } from '../components/GoodsListComponent';
import { TabBarComponent } from '../components/TabBarComponent';
import { bannerData, categoryData, goodsData, tabBarData } from '../models/HomeData';
@Entry
@Component
struct Index {
@State selectedIndex: number = 0;
build() {
Column({ space: 0 }) {
// 页面内容
Scroll() {
Column({ space: 24 }) {
// 轮播图
BannerComponent({ data: bannerData });
// 分类导航
CategoryComponent({ data: categoryData });
// 热门商品标题
Text('热门商品')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.textColor('#000000')
.width('100%')
.padding(0, 0, 0, 16);
// 商品列表
GoodsListComponent({ data: goodsData });
}
.width('100%')
.padding(20);
}
.width('100%')
.height('100%')
.layoutWeight(1);
// 底部导航栏
TabBarComponent({
data: tabBarData,
selectedIndex: this.selectedIndex
});
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5');
}
}
三、 组件交互效果优化 🎬
3.1 悬停动画
为商品列表组件添加悬停动画,提升用户体验:
⌨️ entry/src/main/ets/components/GoodsListComponent.ets(修改GoodsItemComponent)
typescript
@Component
struct GoodsItemComponent {
@Prop goods: GoodsModel;
@State isHover: boolean = false;
build() {
Column({ space: 12 }) {
// 商品图片
Image(this.goods.imageUrl)
.width('100%')
.height(200)
.objectFit(ImageFit.Cover)
.borderRadius(12)
.scale({ x: this.isHover ? 1.05 : 1, y: this.isHover ? 1.05 : 1 })
.transition({ duration: 300, curve: Curve.EaseOut });
// 商品信息
// ...
}
.width('100%')
.height('auto')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.onClick(() => {
router.pushUrl({ url: 'pages/ProductDetailPage' });
})
.onHover(() => {
this.isHover = true;
})
.onHoverEnd(() => {
this.isHover = false;
});
}
}
3.2 点击动画
为按钮组件添加点击动画,反馈用户操作:
⌨️ entry/src/main/ets/components/GoodsListComponent.ets(添加立即购买按钮)
typescript
@Component
struct GoodsItemComponent {
@Prop goods: GoodsModel;
@State isHover: boolean = false;
@State isPressed: boolean = false;
build() {
Column({ space: 12 }) {
// 商品图片
// ...
// 商品信息
Column({ space: 8 }) {
// 商品名称
// ...
// 价格与销量
// ...
// 立即购买按钮
Button('立即购买')
.width('100%')
.height(48)
.backgroundColor('#007DFF')
.onClick(() => {
console.log('点击了立即购买按钮');
})
.onPress(() => {
this.isPressed = true;
})
.onPressEnd(() => {
this.isPressed = false;
})
.scale({ x: this.isPressed ? 0.95 : 1, y: this.isPressed ? 0.95 : 1 })
.transition({ duration: 100, curve: Curve.EaseOut });
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.padding(0, 0, 0, 8);
}
.width('100%')
.height('auto')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.onClick(() => {
router.pushUrl({ url: 'pages/ProductDetailPage' });
})
.onHover(() => {
this.isHover = true;
})
.onHoverEnd(() => {
this.isHover = false;
});
}
}
四、 项目运行与效果验证 📱
4.1 图片资源准备
在「entry/src/main/resources/base/media」目录下添加以下图片资源(图片格式为.png,尺寸符合要求):
banner1.png、banner2.png、banner3.png(轮播图);category1.png、category2.png、category3.png、category4.png、category5.png、category6.png(分类导航);goods1.png、goods2.png、goods3.png、goods4.png、goods5.png(商品列表);tab1.png、tab1_selected.png、tab2.png、tab2_selected.png、tab3.png、tab3_selected.png、tab4.png、tab4_selected.png(底部导航栏)。
4.2 🔧 项目运行
① 在DevEco Studio中点击「Run」按钮;
② 选择调试设备,点击「OK」;
③ 等待编译安装完成,应用会自动在设备上启动。
效果验证
✅ 轮播图 :自动轮播促销活动,点击可跳转;
✅ 分类导航 :显示6个商品分类,点击可跳转;
✅ 商品列表 :显示5个热门商品,悬停有动画效果;
✅ 底部导航栏 :实现4个页面的切换;
✅ 响应式布局:自动适配不同设备尺寸。
五、 总结与未来学习路径 🚀
5.1 总结
本文作为《鸿蒙APP开发从入门到精通》的第2篇,完成了:
- 对ArkUI组件库分类与核心特性的理解;
- 15个常用ArkUI组件的实战应用;
- 一个完整的电商首页组件的创建;
- 组件交互效果的优化(悬停、点击动画)。
5.2 未来学习路径
- 第3篇:自定义组件与数据双向绑定;
- 第4篇:网络请求与数据持久化;
- 第5篇:页面路由与组件跳转;
- 第6篇:原子化服务与元服务卡片的开发;
- 第7篇:超级终端多设备协同开发;
- 第8篇:服务联邦跨服务无缝打通;
- 第9篇:安全加固与组件化架构;
- 第10篇:AI原生与用户增长;
- 第11篇:性能优化与Next原生合规;
- 第12篇:运维监控、生态运营与专属变现。
结语 ✅
恭喜你!你已经完成了《鸿蒙APP开发从入门到精通》的第2篇,掌握了ArkUI组件库的核心用法。
从现在开始,你已具备了开发复杂UI界面的能力。未来的10篇文章将逐步构建一个完整的鸿蒙电商购物车全栈项目,并最终实现华为应用市场上架变现。
让我们一起期待鸿蒙生态的爆发! 🎉🎉🎉