在 Flutter 中创建图像轮播
从社交媒体应用程序到电子商务应用程序,大多数现代应用程序都有某种图像轮播来展示产品、图像或广告。 由于 flutter 提供的内置小部件,从头开始实现图像轮播并不像您想象的那么难。 在本文中,您将学习如何从头开始创建图像轮播并根据需要进行自定义。最后,您将学习如何使用carousel_slider插件以更少的代码创建图像轮播。 这些是我们将要讨论的主题。
- 图像轮播的小部件结构
- 使用 PageView 小部件创建轮播
- 控制初始页面
- 添加位置指示器
- 为滑动图像添加动画
- 如何使用 carousel_slider 插件
图像轮播的小部件结构
在我们进入编码部分之前,让我们了解我们需要拥有的小部件结构。
页面视图小部件需要在应用程序中实现图像滑动功能和图像视图以显示实际图像。除此之外,您需要一个容器小部件来在滑块底部实现页面指示器。
使用 PageView 小部件创建轮播
首先,让我们创建一个名为 Carousel 的有状态小部件。这是我们将用来实现轮播的小部件。 首先,让我们创建一个包含图像 URL 的列表:
List images = [
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQTIZccfNPnqalhrWev-Xo7uBhkor57_rKbkw&usqp=CAU",
"https://wallpaperaccess.com/full/2637581.jpg"
];
现在您可以使用 PageView 小部件构建器方法来创建轮播页面:
PageView.builder(
itemCount: 2,
pageSnapping: true,
itemBuilder: (context,pagePosition){
return Container(
margin: EdgeInsets.all(10),
child: Image.network());
})
itemCount
代表页数,它会决定
itemBuilder
需要执行多少次。因此,您可以通过访问它的索引来设置每个图像 URL。
PageView.builder(
itemCount: 2,
pageSnapping: true,
itemBuilder: (context,pagePosition){
return Container(
margin: EdgeInsets.all(10),
child: Image.network(images[pagePosition]));
})
现在您可以在屏幕上看到这些图像,并且可以滑动这些图像。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5iN5IjN2YWO1QDZ5EWY0MTYxYzXzUzMycTM4IzLcFTMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.gif)
如果您像这样显示图像,用户将不知道是否有更多图像。因此,在显示中间图像的同时显示左右图像的一部分将改善图像轮播的用户体验。 首先,您应该创建
PageController
并将其设置为我们的 PageView 小部件。此外,我添加了第三张图像并更改了
itemCount
从图像数组本身的长度获取计数。然后您可以设置该
viewportFraction
属性
PageController
以显示其他图像的分数:
late PageController _pageController;
List images = [
"https://images.wallpapersden.com/image/download/purple-sunrise-4k-vaporwave_bGplZmiUmZqaraWkpJRmbmdlrWZlbWU.jpg",
"https://wallpaperaccess.com/full/2637581.jpg",
"https://uhdwallpapers.org/uploads/converted/20/01/14/the-mandalorian-5k-1920x1080_477555-mm-90.jpg"
];
@override
void initState() {
super.initState();
_pageController = PageController(viewportFraction: 0.8);
}
@override
Widget build(BuildContext context) {
return PageView.builder(
itemCount: images.length,
pageSnapping: true,
controller: _pageController,
onPageChanged: (page) {
setState(() {
activePage = page;
});
},
itemBuilder: (context, pagePosition) {
return Container(
margin: EdgeInsets.all(10),
child: Image.network(images[pagePosition]),
);
});
}
尽管图像显示在 PageView 的中间,但它实际上占用了整个屏幕空间。如果您将
fit
类型更改为
cover
,您可以看到它占据了整个屏幕。
要控制它,您可以将小部件包装在小
SizedBox
部件中:
SizedBox(
height: 200,
width: MediaQuery.of(context).size.width,
child: PageView.builder(
itemCount: images.length,
pageSnapping: true,
controller: _pageController,
onPageChanged: (page) {
setState(() {
activePage = page;
});
},
itemBuilder: (context, pagePosition) {
return Container(
margin: EdgeInsets.all(10),
child: Image.network(images[pagePosition],fit: BoxFit.cover,),
);
}),
)
控制初始页面
轮播在第一次加载时将第一张图片加载为默认页面。但是如果您需要更改初始图像以从不同的页面开始,您可以
initialPage
在 PageController 中提供该属性。它接受索引作为一个位置:
_pageController = PageController(viewportFraction: 0.8,initialPage: 1);
添加位置指示器
首先,将
PageView
小部件移动到小部件
SizedBox
内部
Column
:
Column(
children: [
SizedBox(
width: MediaQuery.of(context).size.width,
height: 200,
child: PageView.builder(
itemCount: images.length,
pageSnapping: true,
controller: _pageController,
onPageChanged: (page) {
setState(() {
activePage = page;
});
},
itemBuilder: (context, pagePosition) {
return Container(
margin: EdgeInsets.all(10),
child: Image.network(images[pagePosition]),
);
}),
),
],
)
然后您可以创建一个方法来返回指标列表,您应该根据当前活动位置更改指标的颜色。因此,让我们创建一个接受
currentIndex
和
imagesLength
作为参数的方法。通过检查索引,您可以更改活动位置指示器的颜色,如下所示:
List indicators(imagesLength,currentIndex) {
return List.generate(imagesLength, (index) {
return Container(
margin: EdgeInsets.all(3),
width: 10,
height: 10,
decoration: BoxDecoration(
color: currentIndex == index ? Colors.black : Colors.black26,
shape: BoxShape.circle),
);
});
}
当用户滑动图像时,应该有一种方法可以获取当前活动位置。该
onPageChanged
方法适用于:
int activePage = 1;
PageView.builder(
itemCount: images.length,
pageSnapping: true,
controller: _pageController,
onPageChanged: (page) {
setState(() {
activePage = page;
});
})
现在您可以将
activePage
值传递给我们创建的方法。在小部件内创建一个 Row 小
Column
部件作为第二个孩子。然后,您可以将方法的返回指示符
indicators
作为子项分配给
Row
小部件:
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: indicators(images.length,activePage))
为滑动图像添加动画
尽管我们的轮播效果很好,但在图像之间滑动时有动画效果还是不错的。让我们看看如何在图像变化时添加动画。 有多种方法可用于应用放大动画。但是在这里我们将使用内置的 Flutter
AnimationContainer
小部件来创建这个动画,因为它开箱即用,
AnimationContainer
提供了我们需要的所有功能。 在当前滑块图像中,所有容器的大小都相同。通过更改图像的边距,您可以添加放大效果。
AnimationContainer
包含样特性
duration
,
curve
,
margin
,和
decoration
你可以用它来实现这个动画。 让我们创建一个单独的滑块方法,并将图像列表、当前页面位置以及当前图像是否处于活动状态作为参数。可以
itemBuilder
通过检查页面位置和当前活动页面位置来检查此活动状态。 该
curve
属性可用于指定需要如何放置动画曲线。请查看这篇Flutter 文章以了解可用值及其行为:
itemBuilder: (context, pagePosition) {
//checking active position
bool active = pagePosition == activePage;
return slider(images,pagePosition,active);
}
AnimatedContainer slider(images,pagePosition,active){
double margin = active ? 10 : 20;
return AnimatedContainer(
duration: Duration(milliseconds: 500),
curve: Curves.easeInOutCubic,
margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
image: DecorationImage(image: NetworkImage(images[pagePosition]))
),
);
}
如何使用 carousel_slider 插件
尽管从头开始实施轮播很容易,但根据您的项目和时间表,添加不同的功能可能很困难。因此,可以在您的项目中使用像 carousel_slider 这样的插件来创建一个更轻松的轮播。 首先,将插件添加到:
pubspec.yaml
dependencies:
carousel_slider: ^4.0.0
接下来,导入您要使用滑块的库:
import 'package:carousel_slider/carousel_slider.dart';
该
CarouselSlider
类主要接受两个参数:
options
和
items
。对于选项,您可以配置滑块所需的行为。我已经提到了
CarouselOptions
类中可用的一些属性。
Property | 描述 |
| 设置滑块高度 |
| 设置页面是否需要无限滚动。默认情况下,此选项处于打开状态,您可以根据需要将其关闭 |
| 滑块将自动更改图像。默认时间为 4 秒 |
| 更改自动播放间隔,默认4秒 |
| 页面更改时的回调触发器。它包含两个参数:当前位置和原因。该 值可以是定时、手动或控制器 |
| 放大中间页面,就像我们之前实现的那样 |
SliderPlugin(images) {
return CarouselSlider(
options: CarouselOptions(
height: 200.0,
enlargeCenterPage: true,
onPageChanged: (position,reason){
print(reason);
print(CarouselPageChangedReason.controller);
},
enableInfiniteScroll: false,
),
items: images.map((i) {
return Builder(
builder: (BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
image: DecorationImage(image: NetworkImage(i))));
},
);
}).toList(),
);
}
结论
最后附上完整源码
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text('Image carousel'),
),
body: Carousel(),
),
);
}
}
class Carousel extends StatefulWidget {
const Carousel({
Key? key,
}) : super(key: key);
@override
State createState() => _CarouselState();
}
class _CarouselState extends State {
late PageController _pageController;
List images = [
"https://images.wallpapersden.com/image/download/purple-sunrise-4k-vaporwave_bGplZmiUmZqaraWkpJRmbmdlrWZlbWU.jpg",
"https://wallpaperaccess.com/full/2637581.jpg",
"https://uhdwallpapers.org/uploads/converted/20/01/14/the-mandalorian-5k-1920x1080_477555-mm-90.jpg"
];
int activePage = 1;
@override
void initState() {
super.initState();
_pageController = PageController(viewportFraction: 0.8, initialPage: 1);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
width: MediaQuery.of(context).size.width,
height: 200,
child: PageView.builder(
itemCount: images.length,
pageSnapping: true,
controller: _pageController,
onPageChanged: (page) {
setState(() {
activePage = page;
});
},
itemBuilder: (context, pagePosition) {
bool active = pagePosition == activePage;
return slider(images,pagePosition,active);
}),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: indicators(images.length,activePage))
],
);
}
}
AnimatedContainer slider(images, pagePosition, active) {
double margin = active ? 10 : 20;
return AnimatedContainer(
duration: Duration(milliseconds: 500),
curve: Curves.easeInOutCubic,
margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
image: DecorationImage(image: NetworkImage(images[pagePosition]))),
);
}
imageAnimation(PageController animation, images, pagePosition) {
return AnimatedBuilder(
animation: animation,
builder: (context, widget) {
print(pagePosition);
return SizedBox(
width: 200,
height: 200,
child: widget,
);
},
child: Container(
margin: EdgeInsets.all(10),
child: Image.network(images[pagePosition]),
),
);
}
List indicators(imagesLength, currentIndex) {
return List.generate(imagesLength, (index) {
return Container(
margin: EdgeInsets.all(3),
width: 10,
height: 10,
decoration: BoxDecoration(
color: currentIndex == index ? Colors.black : Colors.black26,
shape: BoxShape.circle),
);
});
}