Flutter 实现下拉刷新和自动加载更多
Flutter Banner封装
Flutter使用官方CustomScrollView实现复杂页面下拉刷新和加载更多
Flutter之Router和Navigator实现页面跳转
Flutter的基本应用
似乎好久没有写文章了,趁着最近项目时间不紧,总结一下项目中使用的部分Flutter组件。
项目中使用第三方的Banner插件 carousel_slider: ^4.2.1,但是这些组件一般是不能在项目直接使用的,我们一般会重新封装成简单易用于项目的组件。
// 轮播图点击Item回调
typedef OnItemClick = void Function(int, BannerModel);
class BannerWidget extends StatefulWidget {
/// 轮播图数据
final List<BannerModel> bannerList;
/// 轮播图高度
final double bannerHeight;
/// 轮播图宽度
final double bannerWidth;
/// 轮播图viewportFraction 1.0 默认
final double viewportFraction;
/// 指示器对齐方式
final MainAxisAlignment indicatorAlignment;
/// 指示器形状
final BoxShape indicatorShape;
/// 指示器颜色
final Color indicatorColor;
/// 指示器大小
final Size? indicatorSize;
/// 指示器间距
final double indicatorSpacing;
/// 点击回调
final OnItemClick? onItemClick;
const BannerWidget(
{super.key,
required this.bannerList,
required this.bannerHeight,
required this.bannerWidth,
this.indicatorAlignment = MainAxisAlignment.center,
this.indicatorShape = BoxShape.circle,
this.indicatorColor = Colors.white,
this.indicatorSize,
this.viewportFraction = 1.0,
this.indicatorSpacing = 2,
this.onItemClick});
@override
State<BannerWidget> createState() => _BannerWidgetState();
}
class _BannerWidgetState extends State<BannerWidget> {
final CarouselController _carouselController = CarouselController();
int _currentIndex = 0;
@override
Widget build(BuildContext context) {
return Stack(
children: [
buildCarouselSlider(),
Positioned(
left: 0,
right: 0,
bottom: 10.wpx,
child: Row(
mainAxisAlignment: widget.indicatorAlignment,
children: _buildIndicator(),
),
),
],
);
}
CarouselSlider buildCarouselSlider() {
return CarouselSlider(
items: _items(),
carouselController: _carouselController,
options: CarouselOptions(
height: widget.bannerHeight,
viewportFraction: widget.viewportFraction,
autoPlay: true,
onPageChanged: (index, reason) {
setState(() {
_currentIndex = index;
});
},
));
}
_items() {
return widget.bannerList.asMap().entries.map((entry) {
return GestureDetector(
onTap: () => widget.onItemClick?.call(entry.key, entry.value),
child: Image.network(entry.value.icon!,
width: widget.bannerWidth, fit: BoxFit.cover),
);
}).toList();
}
///构建指示器
_buildIndicator() {
return widget.bannerList.asMap().entries.map((entry) {
return GestureDetector(
onTap: () => _carouselController.animateToPage(entry.key),
child: Container(
margin: EdgeInsets.all(widget.indicatorSpacing),
width: widget.indicatorSize?.width ?? 6.wpx,
height: widget.indicatorSize?.height ?? 6.wpx,
decoration: BoxDecoration(
color: widget.indicatorColor
.withOpacity(_currentIndex == entry.key ? 1.0 : 0.4),
shape: widget.indicatorShape,
),
),
);
}).toList();
}
}
因为第三方插件没有指示器,所以项目中需要自行封装,本人Android开发所以代码和Android风格差不多,使用:
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
.......
body: BannerWidget(
bannerList: HttpUrls.getBanner(),
bannerHeight: 180.wpx,
bannerWidth: MediaQuery.of(context).size.width,
indicatorSize: Size(6.wpx, 6.wpx),
indicatorShape: BoxShape.circle,
indicatorColor: Colors.white,
indicatorSpacing: 3.wpx,
onItemClick: (index, model) {
launchUrl(
Uri.parse(model.url ?? ""),
mode: LaunchMode.externalApplication,
);
},
),
);
}
.........
}
使用非常简单,点击Item 直接打开自带浏览器。最后需要记住了,不要为了封装而封装,有些东西是慢慢用久了才封装起来的,并不是一开始就想着封装。