Flutter for OpenHarmony 第三方库实战:使用 carousel_slider 构建旅行推荐轮播卡片应用
在移动应用开发中,二维码是非常常见的功能。例如个人名片、活动签到、网页跳转、 WiFi 分享、订单核销等场景,都可以通过二维码把文字信息转换成可扫描的图形。如果手动绘制二维码,难度会比较高,因为二维码不仅要处理编码规则,还要处理纠错等级、模块绘制和图形展示。因此本篇文章选择使用 OpenHarmony 三方库来完成二维码生成。本篇文章以“电子名片二维码生成器”为场景,使用根据用户输入的信息生成二维
欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net
项目效果
本文实现的是一个基于 Flutter for OpenHarmony 的旅行推荐轮播卡片应用。项目中使用 Flutter 第三方库 carousel_slider 实现卡片轮播效果,让多个旅行地点以横向滑动卡片的形式展示。
最终运行效果如下:


页面主要包含以下内容:
- 顶部标题栏;
- 旅行推荐说明卡片;
- 自动播放轮播卡片;
- 当前轮播位置指示器;
- 上一张、下一张控制按钮;
- 当前景点详情展示;
- 推荐地点列表;
- 第三方库使用说明;
- 页面整体采用 Flutter Material 风格布局。
本文重点是演示如何在 Flutter for OpenHarmony 项目中使用 Flutter 第三方库 carousel_slider。项目代码写在 lib/main.dart 中,依赖配置写在 pubspec.yaml 中,符合 Flutter for OpenHarmony 第三方库实践方向。
前言
在移动应用开发中,轮播图是非常常见的页面组件。例如首页 Banner、商品推荐、活动宣传、旅游景点展示、新闻头条、课程推荐等,都可以使用轮播卡片来实现。
如果只使用普通列表展示内容,页面会比较平铺直叙。轮播组件可以在有限空间内展示多条重点信息,并且可以通过自动播放吸引用户注意力。
本文选择使用 Flutter 第三方库 carousel_slider 实现轮播卡片。它可以快速创建横向滑动轮播,并支持自动播放、无限滚动、居中放大、自定义卡片内容等功能。
本项目以“旅行推荐轮播卡片应用”为例,使用 carousel_slider 展示多个旅行地点,并通过 Flutter 页面状态更新展示当前选中的景点详情。
一、项目目标
本次实践主要实现以下目标:
- 创建 Flutter for OpenHarmony 项目;
- 在
pubspec.yaml中添加第三方库carousel_slider; - 使用
flutter pub get获取依赖; - 在
lib/main.dart中引入carousel_slider; - 使用
CarouselSlider.builder()构建轮播卡片; - 使用
CarouselOptions配置自动播放和页面切换; - 使用
CarouselSliderController控制上一张和下一张; - 实现轮播指示器;
- 实现当前景点详情展示;
- 将应用运行到 OpenHarmony 设备或模拟器中。
二、技术栈
| 类型 | 内容 |
|---|---|
| 开发方向 | Flutter for OpenHarmony |
| 开发语言 | Dart |
| UI 框架 | Flutter |
| 第三方库 | carousel_slider |
| 功能场景 | 轮播图 / 推荐卡片 / 首页展示 |
| 核心组件 | CarouselSlider / CarouselOptions / CarouselSliderController |
| 项目入口 | lib/main.dart |
| 依赖配置 | pubspec.yaml |
| 运行平台 | OpenHarmony 设备或模拟器 |
三、为什么选择 carousel_slider
在实际应用中,轮播组件的使用场景非常多。例如:
- 首页 Banner;
- 活动宣传页;
- 商品推荐页;
- 新闻头条页;
- 课程推荐页;
- 旅行景点推荐;
- 音乐专辑推荐;
- 电影海报展示;
- 应用功能介绍页。
如果完全自己使用 PageView 实现轮播,也可以完成基本滑动效果。但如果还要加入自动播放、无限滚动、居中放大、控制按钮和页面回调,就需要写更多逻辑。
carousel_slider 已经封装了常见轮播功能,可以让开发者更快完成轮播类页面。
在本项目中,carousel_slider 主要完成以下工作:
- 构建横向轮播卡片;
- 支持自动播放;
- 支持无限循环;
- 支持当前卡片居中放大;
- 支持页面切换回调;
- 支持通过控制器切换上一张和下一张。
四、创建 Flutter for OpenHarmony 项目
在已经配置好 Flutter for OpenHarmony 开发环境的前提下,可以创建一个 Flutter 项目。
示例项目名称:
flutter create travel_carousel_demo
进入项目目录:
cd travel_carousel_demo
项目创建完成后,主要关注两个文件:
travel_carousel_demo
├── pubspec.yaml
└── lib
└── main.dart
其中:
| 文件 | 作用 |
|---|---|
| pubspec.yaml | 配置 Flutter 项目依赖 |
| lib/main.dart | 编写 Flutter 页面和业务逻辑 |
五、添加 carousel_slider 第三方库
打开项目根目录下的 pubspec.yaml 文件,在 dependencies 中添加 carousel_slider。
示例配置如下:
dependencies:
flutter:
sdk: flutter
carousel_slider: ^5.1.2
完整结构大致如下:
name: travel_carousel_demo
description: A Flutter for OpenHarmony carousel slider demo.
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.4.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
carousel_slider: ^5.1.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
添加完成后,在终端执行:
flutter pub get
执行成功后,就可以在 Dart 代码中使用 carousel_slider 了。
六、项目结构
本项目主要修改 lib/main.dart 文件:
lib
└── main.dart
本项目不需要编写 OpenHarmony 原生 ArkTS 页面,也不需要修改 Index.ets。
因为这是 Flutter for OpenHarmony 项目,页面主体应该是 Flutter 代码。审核重点会看:
- 是否使用
pubspec.yaml添加 Flutter 第三方库; - 是否在 Dart 文件中
import package; - 是否在
lib/main.dart中实际调用第三方库; - 是否属于 Flutter for OpenHarmony 项目。
七、核心实现思路
本项目的核心流程如下:
- 在
pubspec.yaml中添加carousel_slider; - 在
main.dart中引入第三方库; - 定义旅行地点数据模型;
- 使用
CarouselSlider.builder()构建轮播卡片; - 使用
CarouselOptions设置自动播放、无限滚动和居中放大; - 使用
onPageChanged记录当前轮播下标; - 使用小圆点展示当前轮播位置;
- 使用
CarouselSliderController控制上一张和下一张; - 根据当前下标展示景点详情。
第三方库引入代码如下:
import 'package:carousel_slider/carousel_slider.dart';
轮播组件核心代码如下:
CarouselSlider.builder(
itemCount: _places.length,
itemBuilder: (context, index, realIndex) {
return _buildCarouselItem(_places[index]);
},
options: CarouselOptions(
height: 230,
autoPlay: true,
enlargeCenterPage: true,
onPageChanged: (index, reason) {
setState(() {
_currentIndex = index;
});
},
),
)
八、main.dart 完整代码
打开文件:
lib/main.dart
将其中内容替换为下面代码:
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const TravelCarouselApp());
}
class TravelCarouselApp extends StatelessWidget {
const TravelCarouselApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Travel Carousel Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.light,
),
useMaterial3: true,
),
home: const TravelHomePage(),
);
}
}
class TravelPlace {
const TravelPlace({
required this.title,
required this.city,
required this.tag,
required this.description,
required this.icon,
required this.color,
});
final String title;
final String city;
final String tag;
final String description;
final IconData icon;
final Color color;
}
class TravelHomePage extends StatefulWidget {
const TravelHomePage({super.key});
State<TravelHomePage> createState() => _TravelHomePageState();
}
class _TravelHomePageState extends State<TravelHomePage> {
final CarouselSliderController _carouselController =
CarouselSliderController();
final List<TravelPlace> _places = const [
TravelPlace(
title: '海边日落',
city: '厦门',
tag: '治愈 / 拍照',
description: '适合傍晚散步、看海、拍照,也适合安排轻松的短途旅行。',
icon: Icons.wb_twilight,
color: Colors.orange,
),
TravelPlace(
title: '古镇小巷',
city: '苏州',
tag: '人文 / 慢生活',
description: '适合体验江南水乡氛围,感受老街、小桥和传统建筑。',
icon: Icons.house,
color: Colors.teal,
),
TravelPlace(
title: '山间露营',
city: '杭州',
tag: '自然 / 户外',
description: '适合周末放松、轻徒步和露营,能暂时远离城市节奏。',
icon: Icons.terrain,
color: Colors.green,
),
TravelPlace(
title: '城市夜景',
city: '上海',
tag: '都市 / 夜游',
description: '适合夜间散步、观景和拍摄城市灯光,交通也比较方便。',
icon: Icons.location_city,
color: Colors.indigo,
),
TravelPlace(
title: '博物馆路线',
city: '南京',
tag: '学习 / 历史',
description: '适合喜欢历史文化的游客,可以安排一天的城市文化路线。',
icon: Icons.museum,
color: Colors.deepPurple,
),
];
int _currentIndex = 0;
TravelPlace get _currentPlace {
return _places[_currentIndex];
}
void _goToPrevious() {
_carouselController.previousPage(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
);
}
void _goToNext() {
_carouselController.nextPage(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
);
}
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('旅行推荐轮播'),
centerTitle: true,
),
body: SafeArea(
child: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildIntroCard(theme),
const SizedBox(height: 16),
_buildCarouselCard(theme),
const SizedBox(height: 16),
_buildDetailCard(theme),
const SizedBox(height: 16),
_buildPlaceListCard(theme),
const SizedBox(height: 16),
_buildLibraryCard(theme),
],
),
),
);
}
Widget _buildIntroCard(ThemeData theme) {
return Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(22),
),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Container(
width: 76,
height: 76,
decoration: BoxDecoration(
color: theme.colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(24),
),
child: Icon(
Icons.travel_explore,
size: 42,
color: theme.colorScheme.onPrimaryContainer,
),
),
const SizedBox(height: 18),
Text(
'Flutter for OpenHarmony',
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'使用 carousel_slider 构建旅行地点推荐轮播页面',
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
height: 1.5,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
Widget _buildCarouselCard(ThemeData theme) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: [
Expanded(
child: Text(
'热门旅行推荐',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
Text(
'${_currentIndex + 1} / ${_places.length}',
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
),
const SizedBox(height: 16),
CarouselSlider.builder(
carouselController: _carouselController,
itemCount: _places.length,
itemBuilder: (context, index, realIndex) {
return _buildCarouselItem(theme, _places[index]);
},
options: CarouselOptions(
height: 230,
viewportFraction: 0.82,
enlargeCenterPage: true,
enlargeFactor: 0.22,
enableInfiniteScroll: true,
autoPlay: true,
autoPlayInterval: const Duration(seconds: 3),
autoPlayAnimationDuration:
const Duration(milliseconds: 800),
autoPlayCurve: Curves.fastOutSlowIn,
onPageChanged: (index, reason) {
setState(() {
_currentIndex = index;
});
},
),
),
const SizedBox(height: 16),
_buildIndicator(theme),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: _goToPrevious,
icon: const Icon(Icons.arrow_back),
label: const Text('上一张'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: _goToNext,
icon: const Icon(Icons.arrow_forward),
label: const Text('下一张'),
),
),
],
),
),
],
),
),
);
}
Widget _buildCarouselItem(ThemeData theme, TravelPlace place) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
place.color.withOpacity(0.88),
place.color.withOpacity(0.58),
],
),
boxShadow: [
BoxShadow(
color: place.color.withOpacity(0.22),
blurRadius: 18,
offset: const Offset(0, 10),
),
],
),
child: Padding(
padding: const EdgeInsets.all(22),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
place.icon,
size: 52,
color: Colors.white,
),
const Spacer(),
Text(
place.title,
style: theme.textTheme.headlineSmall?.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 6),
Text(
place.city,
style: theme.textTheme.titleMedium?.copyWith(
color: Colors.white.withOpacity(0.92),
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 10),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.18),
borderRadius: BorderRadius.circular(20),
),
child: Text(
place.tag,
style: theme.textTheme.bodySmall?.copyWith(
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
);
}
Widget _buildIndicator(ThemeData theme) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(_places.length, (index) {
final bool active = index == _currentIndex;
return AnimatedContainer(
duration: const Duration(milliseconds: 250),
width: active ? 22 : 8,
height: 8,
margin: const EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
color: active
? theme.colorScheme.primary
: theme.colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8),
),
);
}),
);
}
Widget _buildDetailCard(ThemeData theme) {
final TravelPlace place = _currentPlace;
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 58,
height: 58,
decoration: BoxDecoration(
color: place.color.withOpacity(0.16),
borderRadius: BorderRadius.circular(18),
),
child: Icon(
place.icon,
color: place.color,
size: 30,
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'当前推荐:${place.title}',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 6),
Text(
'${place.city} · ${place.tag}',
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 10),
Text(
place.description,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
height: 1.5,
),
),
],
),
),
],
),
),
);
}
Widget _buildPlaceListCard(ThemeData theme) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'推荐地点列表',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
...List.generate(_places.length, (index) {
final TravelPlace place = _places[index];
final bool selected = index == _currentIndex;
return Container(
margin: const EdgeInsets.only(bottom: 10),
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {
_carouselController.animateToPage(
index,
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
);
setState(() {
_currentIndex = index;
});
},
child: Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: selected
? place.color.withOpacity(0.12)
: theme.colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: selected ? place.color : Colors.transparent,
width: 1.2,
),
),
child: Row(
children: [
Icon(
place.icon,
color: place.color,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
place.title,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'${place.city} · ${place.tag}',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
),
if (selected)
Icon(
Icons.check_circle,
color: place.color,
),
],
),
),
),
);
}),
],
),
),
);
}
Widget _buildLibraryCard(ThemeData theme) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'第三方库说明',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
_buildInfoRow(
theme,
title: '库名称',
value: 'carousel_slider',
),
_buildInfoRow(
theme,
title: '配置文件',
value: 'pubspec.yaml',
),
_buildInfoRow(
theme,
title: '导入方式',
value: "import 'package:carousel_slider/carousel_slider.dart';",
),
_buildInfoRow(
theme,
title: '核心组件',
value: 'CarouselSlider / CarouselOptions / CarouselSliderController',
),
_buildInfoRow(
theme,
title: '应用场景',
value: '首页 Banner、活动推荐、商品展示、旅行推荐、内容轮播',
),
],
),
),
);
}
Widget _buildInfoRow(
ThemeData theme, {
required String title,
required String value,
}) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 82,
child: Text(
title,
style: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
Expanded(
child: Text(
value,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
),
],
),
);
}
}
九、代码实现说明
1. 引入 carousel_slider 第三方库
代码开头引入第三方库:
import 'package:carousel_slider/carousel_slider.dart';
这说明项目确实使用了 Flutter 第三方库,而不是 OpenHarmony 原生库。
本项目中主要使用以下组件和类:
CarouselSlider
CarouselOptions
CarouselSliderController
其中:
| 组件或类 | 作用 |
|---|---|
| CarouselSlider | 构建轮播组件 |
| CarouselOptions | 配置轮播参数 |
| CarouselSliderController | 手动控制轮播切换 |
2. 定义旅行地点数据模型
项目中定义了旅行地点模型:
class TravelPlace {
const TravelPlace({
required this.title,
required this.city,
required this.tag,
required this.description,
required this.icon,
required this.color,
});
final String title;
final String city;
final String tag;
final String description;
final IconData icon;
final Color color;
}
字段说明如下:
| 字段 | 作用 |
|---|---|
| title | 景点标题 |
| city | 所在城市 |
| tag | 景点标签 |
| description | 景点说明 |
| icon | 页面图标 |
| color | 卡片主题色 |
这样页面可以根据数据动态生成轮播卡片。
3. 使用 CarouselSlider.builder 构建轮播
轮播组件核心代码如下:
CarouselSlider.builder(
itemCount: _places.length,
itemBuilder: (context, index, realIndex) {
return _buildCarouselItem(theme, _places[index]);
},
options: CarouselOptions(...),
)
其中:
| 参数 | 作用 |
|---|---|
| itemCount | 轮播项数量 |
| itemBuilder | 构建每一个轮播卡片 |
| options | 配置轮播效果 |
使用 CarouselSlider.builder() 的好处是可以根据数据列表动态生成页面,不需要手动写多个重复卡片。
4. 使用 CarouselOptions 配置轮播效果
本项目中使用了以下配置:
CarouselOptions(
height: 230,
viewportFraction: 0.82,
enlargeCenterPage: true,
enlargeFactor: 0.22,
enableInfiniteScroll: true,
autoPlay: true,
autoPlayInterval: const Duration(seconds: 3),
)
参数说明如下:
| 参数 | 作用 |
|---|---|
| height | 轮播区域高度 |
| viewportFraction | 每张卡片占屏幕宽度比例 |
| enlargeCenterPage | 是否放大中间卡片 |
| enlargeFactor | 中间卡片放大程度 |
| enableInfiniteScroll | 是否开启无限循环 |
| autoPlay | 是否自动播放 |
| autoPlayInterval | 自动播放间隔 |
通过这些配置,可以让轮播卡片具有自动切换和居中突出效果。
5. 监听当前轮播下标
轮播切换时,通过 onPageChanged 获取当前下标:
onPageChanged: (index, reason) {
setState(() {
_currentIndex = index;
});
}
当轮播页面发生变化时,_currentIndex 会更新。页面中的指示器和详情卡片也会跟着变化。
6. 实现轮播指示器
指示器通过 List.generate() 生成多个小圆点:
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(_places.length, (index) {
final bool active = index == _currentIndex;
return AnimatedContainer(...);
}),
)
当前选中的圆点会变长,其他圆点保持较短,用来提示用户当前处于第几张卡片。
7. 使用 CarouselSliderController 控制上一张和下一张
页面中创建控制器:
final CarouselSliderController _carouselController =
CarouselSliderController();
上一张按钮调用:
_carouselController.previousPage(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
);
下一张按钮调用:
_carouselController.nextPage(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
);
这样用户除了等待自动播放,也可以手动切换卡片。
8. 点击列表切换到指定轮播项
推荐地点列表中,点击某个地点后调用:
_carouselController.animateToPage(
index,
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
);
这样可以直接跳转到指定轮播卡片。
同时更新当前下标:
setState(() {
_currentIndex = index;
});
这样详情区域和选中状态可以同步更新。
十、运行项目
完成代码后,在终端执行:
flutter pub get
然后连接 OpenHarmony 设备或启动 OpenHarmony 模拟器。
查看设备:
flutter devices
运行项目:
flutter run
如果环境配置正确,应用会运行到 OpenHarmony 设备或模拟器中。
运行成功后,页面会显示“旅行推荐轮播”。页面中的旅行卡片会自动播放,也可以点击“上一张”“下一张”按钮进行手动切换。
十一、开发中遇到的问题
1. carousel_slider 依赖没有生效
如果代码中出现找不到 carousel_slider 的问题,可以检查 pubspec.yaml 中是否添加了:
carousel_slider: ^5.1.2
然后重新执行:
flutter pub get
如果还是不行,可以重启编辑器。
2. import 导入报错
如果下面代码报错:
import 'package:carousel_slider/carousel_slider.dart';
通常有几种原因:
pubspec.yaml中没有添加依赖;- 没有执行
flutter pub get; - YAML 缩进错误;
- 包名写错;
- 编辑器没有刷新依赖。
其中 YAML 缩进最容易出问题。依赖必须写在 dependencies 下面,并且缩进要正确。
3. 轮播卡片没有显示
如果轮播卡片没有显示,可以检查:
CarouselSlider是否有固定高度;itemCount是否大于 0;itemBuilder是否返回了有效组件;- 页面是否被其他组件遮挡;
- 是否正确导入第三方库。
本项目中通过下面代码设置高度:
CarouselOptions(
height: 230,
)
如果不给轮播组件足够空间,页面可能无法正常显示。
4. 自动播放没有生效
如果轮播没有自动播放,可以检查是否设置了:
autoPlay: true
同时可以设置自动播放间隔:
autoPlayInterval: const Duration(seconds: 3)
这样轮播会每隔 3 秒自动切换一次。
5. 上一张和下一张按钮无效
如果按钮点击后没有效果,可以检查:
- 是否创建了
CarouselSliderController; - 是否把控制器传给了
CarouselSlider; - 按钮中是否调用了
previousPage()或nextPage()。
正确绑定方式如下:
CarouselSlider.builder(
carouselController: _carouselController,
...
)
6. 指示器没有同步变化
如果指示器没有跟着轮播变化,可以检查是否在 onPageChanged 中调用了:
setState(() {
_currentIndex = index;
});
Flutter 页面状态变化后,需要使用 setState() 通知页面刷新。
7. 运行不到 OpenHarmony 设备
如果项目无法运行到 OpenHarmony 设备或模拟器,可以检查:
- Flutter for OpenHarmony 环境是否配置完成;
- 设备是否连接成功;
flutter devices是否能识别设备;- 是否执行了
flutter pub get; - 是否选择了正确的运行设备;
- 项目是否为 Flutter 项目,而不是原生鸿蒙项目。
如果 flutter devices 都识别不到设备,那应该先处理环境问题,而不是盯着轮播代码反复怀疑人生。
十二、本文和原生鸿蒙项目的区别
本文是 Flutter for OpenHarmony 第三方库实践,不是 OpenHarmony 原生 ArkTS 项目。
主要区别如下:
| 对比项 | 本文写法 | 原生鸿蒙写法 |
|---|---|---|
| UI 技术 | Flutter | ArkUI |
| 主要语言 | Dart | ArkTS |
| 页面入口 | lib/main.dart | Index.ets |
| 依赖配置 | pubspec.yaml | oh-package.json5 |
| 依赖安装 | flutter pub get | ohpm install |
| 第三方库 | carousel_slider | OpenHarmony 原生库 |
| 页面组件 | MaterialApp / Scaffold / CarouselSlider | @Entry / @Component |
因此本文符合 Flutter for OpenHarmony 第三方库实践方向。
十三、总结
本篇完成了一个基于 carousel_slider 的 Flutter for OpenHarmony 旅行推荐轮播卡片应用。项目通过 Flutter 第三方库实现轮播卡片效果,并结合页面状态更新展示当前景点详情和轮播指示器。
通过本次实践,我主要完成了以下内容:
- 创建 Flutter for OpenHarmony 项目;
- 在
pubspec.yaml中添加carousel_slider依赖; - 使用
flutter pub get获取第三方库; - 在
lib/main.dart中引入carousel_slider; - 使用
CarouselSlider.builder()构建轮播卡片; - 使用
CarouselOptions设置自动播放、无限滚动和居中放大; - 使用
CarouselSliderController控制上一张和下一张; - 使用
onPageChanged监听当前轮播下标; - 使用 Flutter Material 组件构建推荐页面;
- 将项目运行到 OpenHarmony 设备或模拟器中。
这个项目虽然只是一个基础轮播应用,但完整展示了 Flutter for OpenHarmony 项目中第三方库的使用流程。
后续可以在这个基础上继续扩展,例如:
- 添加真实景点图片;
- 添加网络图片加载;
- 添加景点详情页跳转;
- 添加收藏功能;
- 添加搜索功能;
- 添加城市筛选;
- 添加旅行计划列表;
- 添加地图定位;
- 添加暗色主题;
- 添加本地数据保存。
整体来看,carousel_slider 可以帮助 Flutter 开发者快速实现轮播展示效果。通过这个项目,可以理解 Flutter for OpenHarmony 中第三方库依赖配置、轮播组件使用和页面状态同步之间的基本关系。
更多推荐
所有评论(0)