分步实现 Flutter 鸿蒙轮播图核心功能(搜索框 + 指示灯)
欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net
本文参考Flutter鸿蒙开发指南(七):轮播图搜索框和导航栏-CSDN博客
本文记录操作过程以及遇到的问题
在上一节中,我们已完成电商App首页布局的基础开发,成功实现了轮播图的基础搭建和自动播放功能。这一节,我们将聚焦轮播图的细节优化,重点实现「轮播图顶部搜索框」和「底部导航指示灯」两个核心交互组件,让轮播图既美观又具备实用价值,提升整体用户体验。
一、实现轮播图顶部搜索框
搜索框是电商App首页的核心交互组件之一,我们将其叠加在轮播图顶部,采用半透明样式,既不遮挡轮播图内容,又能清晰展示搜索功能。实现思路:利用Stack布局,将搜索框组件叠加在轮播图上方,通过Positioned组件控制搜索框的位置,配合样式优化实现美观效果。
核心操作:修改上一节编写的lib/components/Home/HmSlider.dart文件,在原有轮播图代码基础上,新增搜索框相关代码,完整修改后代码如下:
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:xiuhu_mall/viewmodels/home.dart';
class HmSlider extends StatefulWidget {
//父传子
final List<BannerItem> bannerList;
HmSlider({Key? key, required this.bannerList}) : super(key: key);
@override
State<HmSlider> createState() => _HmSliderState();
}
class _HmSliderState extends State<HmSlider> {
Widget _getSlider() {
//在Flutter中获取屏幕宽度的方法:媒体查询对象: MediaQuery
final double screenWidth = MediaQuery.of(context).size.width; //屏幕宽度
//返回轮播图插件
//根据数据渲染的不同的轮播选项
return CarouselSlider(
items: List.generate(widget.bannerList.length, (int index) {
return Image.network(
widget.bannerList[index].imgUrl,
fit: BoxFit.cover,
width: screenWidth,
);
}),
options: CarouselOptions(
//CarouselOptions中有一个视口占比: viewportFraction:
//高度默认300
height: 300,
//调整播放间距
autoPlayInterval: Duration(seconds: 5),
viewportFraction: 1,
autoPlay: true));
}
//搜索框代码
Widget _getSearch() {
return Positioned(
top: 10,
left: 0,
right: 0,
child: Padding(
padding: EdgeInsets.all(10),
child: Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.symmetric(horizontal: 40),
height: 50,
decoration: BoxDecoration(
color: const Color.fromRGBO(0, 0, 0, 0.4),
borderRadius: BorderRadius.circular(25)
),
child: Text(
"搜索...",
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
),
);
}
//搜索框代码
@override
Widget build(BuildContext context) {
//Stack里面放轮播图 再盖上搜索框 指示灯导航
return Stack(
children: [
//第一个放轮播图
_getSlider(),
_getSearch()
],
);
// return Container(
// color: Colors.blue,
// height: 300,
// alignment: Alignment.center,
// child:
// Text('轮播图', style: TextStyle(color: Colors.white, fontSize: 20)));
}
}
搜索框实现效果如下:

二、实现轮播图底部导航指示灯效果
导航指示灯用于提示用户当前轮播图的位置(共几张、当前显示第几张),同时支持点击指示灯切换对应轮播图,提升交互体验。整体实现分为4步:实现指示灯基础UI —— 实现点击指示灯切换图片 —— 完善指示灯选中/未选中样式 —— 添加指示灯切换动画,逐步优化效果。
2.1 实现图片指示灯基础UI
核心思路:继续使用Stack布局,将指示灯组件叠加在轮播图最底层(轮播图之上、搜索框之下),通过Positioned控制指示灯位于轮播图底部,利用Row布局横向排列指示灯,根据轮播图数量动态生成指示灯个数。
修改lib/components/Home/HmSlider.dart文件,在原有搜索框、轮播图代码基础上,新增指示灯UI代码,完整代码如下:
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:xiuhu_mall/viewmodels/home.dart';
class HmSlider extends StatefulWidget {
//父传子
final List<BannerItem> bannerList;
HmSlider({Key? key, required this.bannerList}) : super(key: key);
@override
State<HmSlider> createState() => _HmSliderState();
}
class _HmSliderState extends State<HmSlider> {
Widget _getSlider() {
//在Flutter中获取屏幕宽度的方法:媒体查询对象: MediaQuery
final double screenWidth = MediaQuery.of(context).size.width; //屏幕宽度
//返回轮播图插件
//根据数据渲染的不同的轮播选项
return CarouselSlider(
items: List.generate(widget.bannerList.length, (int index) {
return Image.network(
widget.bannerList[index].imgUrl,
fit: BoxFit.cover,
width: screenWidth,
);
}),
options: CarouselOptions(
//CarouselOptions中有一个视口占比: viewportFraction:
//高度默认300
height: 300,
//调整播放间距
autoPlayInterval: Duration(seconds: 5),
viewportFraction: 1,
autoPlay: true));
}
Widget _getSearch() {
return Positioned(
top: 10,
left: 0,
right: 0,
child: Padding(
padding: EdgeInsets.all(10),
child: Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.symmetric(horizontal: 40),
height: 50,
decoration: BoxDecoration(
color: const Color.fromRGBO(0, 0, 0, 0.4),
borderRadius: BorderRadius.circular(25)),
child: Text(
"搜索...",
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
),
);
}
//返回指示灯
Widget _getDots() {
return Positioned(
left: 0,
right: 0,
bottom: 10,
child: SizedBox(
height: 40,
width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.center, //主轴居中
children: List.generate(widget.bannerList.length, (int index) {
return Container(
height: 6,
width: 40,
margin: EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(3),
),
);
}),
),
));
}
@override
Widget build(BuildContext context) {
//Stack里面放轮播图 再盖上搜索框 指示灯导航
return Stack(
children: [
//第一个放轮播图
_getSlider(),
_getSearch(),
_getDots(),
],
);
// return Container(
// color: Colors.blue,
// height: 300,
// alignment: Alignment.center,
// child:
// Text('轮播图', style: TextStyle(color: Colors.white, fontSize: 20)));
}
}
指示灯基础UI效果如下:

2.2 实现点击指示灯切换对应图片
核心思路:使用carousel_slider插件提供的CarouselSliderController(轮播图控制器),绑定到轮播图组件,通过控制器的jumpToPage(index)方法,实现点击指示灯跳转至对应轮播图页面。
修改lib/components/Home/HmSlider.dart文件,新增控制器、绑定控制器并实现点击交互,完整代码如下:
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:xiuhu_mall/viewmodels/home.dart';
class HmSlider extends StatefulWidget {
//父传子
final List<BannerItem> bannerList;
HmSlider({Key? key, required this.bannerList}) : super(key: key);
@override
State<HmSlider> createState() => _HmSliderState();
}
class _HmSliderState extends State<HmSlider> {
CarouselSliderController _controller = CarouselSliderController();
Widget _getSlider() {
//在Flutter中获取屏幕宽度的方法:媒体查询对象: MediaQuery
final double screenWidth = MediaQuery.of(context).size.width; //屏幕宽度
//返回轮播图插件
//根据数据渲染的不同的轮播选项
return CarouselSlider(
carouselController: _controller, //绑定controller对象
items: List.generate(widget.bannerList.length, (int index) {
return Image.network(
widget.bannerList[index].imgUrl,
fit: BoxFit.cover,
width: screenWidth,
);
}),
options: CarouselOptions(
//CarouselOptions中有一个视口占比: viewportFraction:
//高度默认300
height: 300,
//调整播放间距
//autoPlayInterval: Duration(seconds: 5),
viewportFraction: 1,
autoPlay: false,
),
);
}
Widget _getSearch() {
return Positioned(
top: 10,
left: 0,
right: 0,
child: Padding(
padding: EdgeInsets.all(10),
child: Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.symmetric(horizontal: 40),
height: 50,
decoration: BoxDecoration(
color: const Color.fromRGBO(0, 0, 0, 0.4),
borderRadius: BorderRadius.circular(25),
),
child: Text(
"搜索...",
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
),
);
}
//返回指示灯
Widget _getDots() {
return Positioned(
left: 0,
right: 0,
bottom: 10,
child: SizedBox(
height: 40,
width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.center, //主轴居中
children: List.generate(widget.bannerList.length, (int index) {
return GestureDetector(
onTap: () {
_controller.jumpToPage(index);
},
child: Container(
height: 6,
width: 40,
margin: EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(3),
),
),
);
}),
),
),
);
}
@override
Widget build(BuildContext context) {
//Stack里面放轮播图 再盖上搜索框 指示灯导航
return Stack(
children: [
//第一个放轮播图
_getSlider(),
_getSearch(),
_getDots(),
],
);
// return Container(
// color: Colors.blue,
// height: 300,
// alignment: Alignment.center,
// child:
// Text('轮播图', style: TextStyle(color: Colors.white, fontSize: 20)));
}
}
点击交互效果测试
重新运行项目,点击不同的指示灯,可实现以下效果:
- 点击第一个指示灯,轮播图立即跳转至第一张图片;
- 点击第二个指示灯,轮播图立即跳转至第二张图片,以此类推;
- 点击交互流畅,无卡顿,跳转后指示灯暂未区分选中/未选中状态(后续完善);
2.3 完善指示灯切换效果(区分选中/未选中状态)
核心思路:新增_currentIndex变量,用于记录当前轮播图的索引;通过CarouselOptions的onPageChanged回调,监听轮播图页面切换,实时更新_currentIndex;使用三元表达式,根据_currentIndex动态修改指示灯的宽度和颜色,区分选中与未选中状态。
修改lib/components/Home/HmSlider.dart文件,完善指示灯样式,完整代码如下:
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:xiuhu_mall/viewmodels/home.dart';
class HmSlider extends StatefulWidget {
//父传子
final List<BannerItem> bannerList;
HmSlider({Key? key, required this.bannerList}) : super(key: key);
@override
State<HmSlider> createState() => _HmSliderState();
}
class _HmSliderState extends State<HmSlider> {
CarouselSliderController _controller =
CarouselSliderController(); //控制 轮播图跳转的控制器
int _currentIndex = 0;
Widget _getSlider() {
//在Flutter中获取屏幕宽度的方法:媒体查询对象: MediaQuery
final double screenWidth = MediaQuery.of(context).size.width; //屏幕宽度
//返回轮播图插件
//根据数据渲染的不同的轮播选项
return CarouselSlider(
carouselController: _controller, //绑定controller对象
items: List.generate(widget.bannerList.length, (int index) {
return Image.network(
widget.bannerList[index].imgUrl,
fit: BoxFit.cover,
width: screenWidth,
);
}),
options: CarouselOptions(
//CarouselOptions中有一个视口占比: viewportFraction:
//高度默认300
height: 300,
//调整播放间距
//autoPlayInterval: Duration(seconds: 5),
viewportFraction: 1,
autoPlay: false,
onPageChanged: (int index, reason) {
_currentIndex = index;
setState(() {});
},
),
);
}
Widget _getSearch() {
return Positioned(
top: 10,
left: 0,
right: 0,
child: Padding(
padding: EdgeInsets.all(10),
child: Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.symmetric(horizontal: 40),
height: 50,
decoration: BoxDecoration(
color: const Color.fromRGBO(0, 0, 0, 0.4),
borderRadius: BorderRadius.circular(25),
),
child: Text(
"搜索...",
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
),
);
}
//返回指示灯
Widget _getDots() {
return Positioned(
left: 0,
right: 0,
bottom: 10,
child: SizedBox(
height: 40,
width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.center, //主轴居中
children: List.generate(widget.bannerList.length, (int index) {
return GestureDetector(
onTap: () {
_controller.jumpToPage(index);
},
child: Container(
height: 6,
width: index == _currentIndex ? 40 : 20,
margin: EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
color: index == _currentIndex ? Colors.white : Color.fromRGBO(0, 0, 0, 0.3),
borderRadius: BorderRadius.circular(3),
),
),
);
}),
),
),
);
}
@override
Widget build(BuildContext context) {
//Stack里面放轮播图 再盖上搜索框 指示灯导航
return Stack(
children: [
//第一个放轮播图
_getSlider(),
_getSearch(),
_getDots(),
],
);
// return Container(
// color: Colors.blue,
// height: 300,
// alignment: Alignment.center,
// child:
// Text('轮播图', style: TextStyle(color: Colors.white, fontSize: 20)));
}
}
核心修改点说明:
- 新增int _currentIndex = 0;:记录当前显示的轮播图索引,默认选中第一张图片;
- 新增onPageChanged回调:监听轮播图页面切换(手动滑动、点击指示灯均可触发),实时更新_currentIndex,并通过setState(() {})刷新页面;
- 指示灯宽度优化:width: index == _currentIndex ? 40 : 20,选中的指示灯更宽,视觉上更突出;
- 指示灯颜色优化:color: index == _currentIndex ? Colors.white : Color.fromRGBO(0, 0, 0, 0.3),选中时为纯白色,未选中时为半透明黑色,对比清晰,贴合轮播图背景。
//用三元表达式改变指示灯切换效果UI的样式。
width: index == _currentIndex ? 40 : 20,
//使用三元表达式,当未切换时是黑色,切换时是白色,0.3为透明度
color: index == _currentIndex ? Colors.white : Color.fromRGBO(0, 0, 0, 0.3),
完善后指示灯效果如下:

2.4 实现指示灯切换动画效果
核心思路:使用Flutter自带的AnimatedContainer组件(动画容器)替换原来的Container,通过duration参数设置动画时长,实现指示灯选中/未选中状态切换时的平滑过渡动画,提升交互体验。
修改lib/components/Home/HmSlider.dart文件,仅修改指示灯组件部分,完整代码如下:
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:xiuhu_mall/viewmodels/home.dart';
class HmSlider extends StatefulWidget {
//父传子
final List<BannerItem> bannerList;
HmSlider({Key? key, required this.bannerList}) : super(key: key);
@override
State<HmSlider> createState() => _HmSliderState();
}
class _HmSliderState extends State<HmSlider> {
CarouselSliderController _controller =
CarouselSliderController(); //控制 轮播图跳转的控制器
int _currentIndex = 0;
Widget _getSlider() {
//在Flutter中获取屏幕宽度的方法:媒体查询对象: MediaQuery
final double screenWidth = MediaQuery.of(context).size.width; //屏幕宽度
//返回轮播图插件
//根据数据渲染的不同的轮播选项
return CarouselSlider(
carouselController: _controller, //绑定controller对象
items: List.generate(widget.bannerList.length, (int index) {
return Image.network(
widget.bannerList[index].imgUrl,
fit: BoxFit.cover,
width: screenWidth,
);
}),
options: CarouselOptions(
//CarouselOptions中有一个视口占比: viewportFraction:
//高度默认300
height: 300,
//调整播放间距
autoPlayInterval: Duration(seconds: 5),
viewportFraction: 1,
autoPlay: true,
onPageChanged: (int index, reason) {
_currentIndex = index;
setState(() {});
},
),
);
}
Widget _getSearch() {
return Positioned(
top: 10,
left: 0,
right: 0,
child: Padding(
padding: EdgeInsets.all(10),
child: Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.symmetric(horizontal: 40),
height: 50,
decoration: BoxDecoration(
color: const Color.fromRGBO(0, 0, 0, 0.4),
borderRadius: BorderRadius.circular(25),
),
child: Text(
"搜索...",
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
),
);
}
//返回指示灯
Widget _getDots() {
return Positioned(
left: 0,
right: 0,
bottom: 10,
child: SizedBox(
height: 40,
width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.center, //主轴居中
children: List.generate(widget.bannerList.length, (int index) {
return GestureDetector(
onTap: () {
_controller.jumpToPage(index);
},
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
height: 6,
width: index == _currentIndex ? 40 : 20,
margin: EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
color: index == _currentIndex ? Colors.white : Color.fromRGBO(0, 0, 0, 0.3),
borderRadius: BorderRadius.circular(3),
),
),
);
}),
),
),
);
}
@override
Widget build(BuildContext context) {
//Stack里面放轮播图 再盖上搜索框 指示灯导航
return Stack(
children: [
//第一个放轮播图
_getSlider(),
_getSearch(),
_getDots(),
],
);
// return Container(
// color: Colors.blue,
// height: 300,
// alignment: Alignment.center,
// child:
// Text('轮播图', style: TextStyle(color: Colors.white, fontSize: 20)));
}
}
动画效果说明
修改完成后,重新运行项目,指示灯切换时将呈现以下动画效果:
- 轮播图自动切换、手动滑动或点击指示灯时,指示灯的宽度和颜色将在300毫秒内平滑过渡,无生硬跳转;
- 动画过渡流畅,与轮播图切换节奏呼应,提升整体交互的细腻度;
- 多端适配:动画效果在鸿蒙、Android端均能正常显示,无差异;
- 可根据需求调整duration: Duration(milliseconds: 300)中的毫秒数,数值越小动画越快,数值越大动画越平缓。
三、上传代码及总结
3.1 上传代码
git add .
git commit -m "完成轮播图搜索框和指示灯"
git push
3.2 总结
本文衔接上一节轮播图基础功能,实现了电商App首页轮播图的两个核心细节组件——搜索框和导航指示灯,从基础UI到交互优化、动画效果,逐步完善,核心总结如下:
- 搜索框实现:利用Stack+Positioned布局,将半透明圆角搜索框叠加在轮播图顶部,既美观又不遮挡轮播图内容,适配多端屏幕;
- 指示灯实现:分4步逐步优化,从基础UI → 点击交互 → 选中状态区分 → 切换动画,核心依赖CarouselSliderController控制器和AnimatedContainer动画组件;
- 核心知识点:Stack层叠布局的使用、CarouselSliderController的绑定与使用、AnimatedContainer平滑动画的实现、三元表达式动态修改组件样式;
- 避坑重点:控制器必须与轮播图绑定才能实现跳转;onPageChanged回调需配合setState刷新页面;指示灯动画需使用AnimatedContainer替换普通Container;
- 功能延伸:本文实现的搜索框为基础样式,后续可扩展为可输入搜索框(添加TextField组件);指示灯可扩展为圆形样式、添加更多动画效果,进一步完善用户体验。
至此,轮播图的基础功能(基础渲染、自动播放、搜索框、导航指示灯)已全部实现。
更多推荐



所有评论(0)