There are many ways to realize the carousel image in Flutter, such as using three-party flutter_swiper, card_swiper, etc. Using these three parties, you can quickly and conveniently realize a carousel image display, which can basically meet our daily development needs. If Say, if you want some customized operations, you have to change the source code or customize one yourself. If you define it yourself, Flutter provides a native component PageView, which can be used to easily implement a carousel.
PageView is similar to ViewPager in Android. It can slide the page horizontally or vertically. The specific use method can be directly PageView(), or use PageView.builder(). Both methods can be realized. The difference is that the former will convert all pages One-time initialization, but the latter will not, in order to facilitate everyone to understand this component, we will simply give a small case.
According to past practice, let's first look at the outline of this article, which is roughly as follows:
1. List of final realization effects
2. The properties and specific use of the PageView component
3. Precautions for carousel image packaging
4. Case source code analysis
5. Packaged source code and how to use it
6. Summary
1. List of final realization effects
Use PageView to encapsulate some specific effects, such as text indicators, rounded corner indicators, and indicator positions, indentation display of carousel pictures, etc., and record a Gif rendering, as follows:
Two, the properties and specific use of the PageView component
After all, PageView is used to implement a carousel, so for this component, we need a brief introduction:
First look at the basic common attributes:
Attributes |
type |
overview |
scrollDirection |
Axis |
Scrolling direction, horizontal or vertical, the default is horizontal. Horizontal: Axis.horizontal Vertical: Axis.vertical |
controller |
PageController |
Scroll controller, which can locate the page and obtain information such as the page |
onPageChanged |
ValueChanged<int> |
Callback when the page changes |
physics |
ScrollPhysics |
Sliding effect, if not set, there will be different scrolling effects according to different platforms After NeverScrollableScrollPhysics is set, the page cannot be scrolled BouncingScrollPhysics means that there will be a bouncing effect after scrolling to the end, which is the default interaction of iOS ClampingScrollPhysics means that an effect will be given when scrolling to the end, which is the default interaction of Android FixedExtentScrollPhysics is the interaction of iOS classic selection time component UIDatePicker |
pageSnapping |
bool |
Whether it is a full page slide, the default is true |
In actual development, the PageView.builder() method is mostly used. It is also recommended that you use this method. It is very simple. You only need to return the page view in itemBuilder. The code is as follows:
PageView.builder(
itemCount: 6,
onPageChanged: (position) {
print("当前索引为:$position");
},
itemBuilder: (context, index) {
return Container(
color: Colors.amber,
alignment: Alignment.center,
child: Text("我是第$index个页面"));
})
The basic effect is as follows:
3. Precautions for packaging carousels
After basically mastering the usage of PageView, we started to package a carousel map. Let’s first analyze the elements that make up the carousel map. First, it meets the requirements of automatic carousel, and can dynamically set the carousel duration. Second, it must be able to meet the requirements of various indicators, and the position can be set dynamically. Third, it must meet the requirements of manual rotation and automatic rotation, and it must handle the direct conflict between gestures and timing. Fourth, the most important thing is to use Keep it simple.
Timer Considerations
After simply determining the elements, we can write by hand. Automatic carousel is very simple. We only need to start a timer, but the timer needs to pay attention to starting and pausing, that is, when to start and when to pause, otherwise it will be very difficult. It is easy to cause confusion in the carousel.
At the beginning of the carousel image, firstly, the automatic carousel property is actively set. When we enter the page, we need to enable the timing. If the page retreats to the background and then returns to the foreground, we also need to enable the carousel. It is pause. In addition to retreating into the background to pause, there is also a need to pause when gestures slide, otherwise it will cause conflicts with timing.
Gesture Considerations
Regarding gestures, if we directly monitor the gestures of page components, we find that they conflict with PageView. In order to solve this gesture problem, we can use the original pointer event listener to monitor gesture sliding.
Part of the code is as follows. After the finger is pressed, the timing is canceled, and when the finger is lifted, the timing is turned on. Of course, if there is only pressing and lifting, then it is a click event, and we can call back this event to the user.
Listener(
onPointerDown: (event) {
//手指按下,定时取消
_pauseTimer();
_isClick = true;
},
onPointerMove: (event) {
_isClick = false;
},
onPointerUp: (event) {
//手指抬起,定时开启
_startTimer();
//作为点击事件
if (_isClick && widget.bannerClick != null) {
widget.bannerClick!(_currentPage);
}
},
child: PageView.builder()t
)
Indicator Considerations
You need to pay attention to the indicator. If you use it for yourself, there is nothing wrong with one indicator. If it is for others to use, then it must be colorful and meet as many needs as possible.
4. Case source code analysis
1. Create a timer
The timer uses Timer, which defines two methods, which are convenient for starting and pausing. When the carousel time is up, the page switching operation can be performed, using PageController's animateToPage to switch.
/*
* 开启定时
* */
void _startTimer() {
if (!_isRunning) {
_isRunning = true;
_timer = Timer.periodic(Duration(seconds: widget.delay!), (timer) {
_controller.animateToPage(_pagePosition + 1,
duration: const Duration(milliseconds: 800),
curve: Curves.easeInOut);
});
}
}
/*
* 暂停定时
* */
void _pauseTimer() {
if (_isRunning) {
_isRunning = false;
_timer?.cancel(); //取消计时器
}
}
@override
void dispose() {
_controller.dispose();
_timer?.cancel();
super.dispose();
}
2. Perceive life cycle changes
When the page retreats to the background and returns to the foreground, we need to pause and start timing, then we need to monitor the page. After adding the monitor, remember the current class with WidgetsBindingObserver.
// 添加监听
WidgetsBinding.instance.addObserver(this);
/*
* 感知生命周期变化
* */
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed && widget.autoPlay!) {
_startTimer(); //页面可见,开启定时
} else if (state == AppLifecycleState.paused && _isRunning) {
_pauseTimer(); //页面不可见,关闭定时
}
}
3. Picture rounded corners
There are many rounded corners of pictures, such as Container decorator, or using the component ClipRRect.
ClipRRect(
//设置图片圆角
borderRadius: BorderRadius.circular(widget.radius!),
child: getBannerImage(imageUrl)))
4. Indicator type and position
The indicator type can be specially customized according to business needs. The current types in the source code include the following types, namely, circle, rounded corner, rectangle, text, and its position can be placed in the middle, left and right sides, and carousel below the figure.
/*
* 指示器
* */
Widget _buildIndicators(mainAxisAlignment) {
if (widget.indicatorType == IndicatorType.text) {
//文字
return Container(
alignment: widget.textIndicatorAlignment,
child: VipText(
"${_currentPage + 1}/${widget.imageList!.length}",
style: widget.textIndicatorStyle,
backgroundColor: widget.textIndicatorBgColor,
padding: widget.textIndicatorPadding,
paddingLeft: widget.textIndicatorPaddingLeft,
paddingTop: widget.textIndicatorPaddingTop,
paddingRight: widget.textIndicatorPaddingRight,
paddingBottom: widget.textIndicatorPaddingBottom,
),
);
}
return Row(
mainAxisAlignment: mainAxisAlignment,
children: List.generate(widget.imageList!.length, (index) {
return Container(
width: _currentPage == index
? widget.indicatorWidth
: widget.indicatorUnWidth ?? widget.indicatorWidth,
height: _currentPage == index
? widget.indicatorHeight
: widget.indicatorUnHeight ?? widget.indicatorHeight,
margin: EdgeInsets.symmetric(horizontal: widget.indicatorMargin!),
decoration: BoxDecoration(
shape: widget.indicatorType == IndicatorType.circle
? BoxShape.circle
: BoxShape.rectangle,
borderRadius: widget.indicatorType == IndicatorType.rectangle
? BorderRadius.all(Radius.circular(widget.indicatorRadius!))
: null,
color: _currentPage == index
? widget.indicatorSelectColor
: widget.indicatorUnSelectColor,
),
);
}),
);
}
5. Carousel image indentation effect
There are two types of indentation. One is that the left and right pictures will become smaller except for the current picture, and it will be enlarged after sliding to the current picture. The other is very simple indentation.
viewportFraction can be understood as the ratio of the content of a page to the screen, full is 1, less than 1 is not full.
PageController(viewportFraction: widget.viewportFraction!)
If we want to zoom in and zoom out the picture when sliding, then we need to perform a zoom in and zoom out animation Transform.scale.
return Transform.scale(
scale: endScale,
child: Container(
margin: widget.imageMargin != null
? EdgeInsets.all(widget.imageMargin!)
: EdgeInsets.only(
left: widget.imageMarginLeft!,
top: widget.imageMarginTop!,
right: widget.imageMarginRight!,
bottom: widget.imageMarginBottom!),
child: ClipRRect(
//设置图片圆角
borderRadius: BorderRadius.circular(widget.radius!),
child: getBannerImage(imageUrl))))
5. Packaged source code and how to use it
At present, the source code has been uploaded to Github. If you need it, you can check it. Due to the limited space, I will not paste it all. Address:
https://github.com/AbnerMing888/flutter_widget/blob/master/lib/ui/widget/vip_banner.dart
List of available attributes
Attributes |
type |
overview |
imageList |
List<String> |
Image address collection |
titleList |
List<String> |
title collection |
radius |
double |
Picture rounded corners |
height |
double |
image height |
delay |
int |
How many times to rotate |
autoPlay |
bool |
Whether to automatically rotate |
bannerClick |
Function(int) |
item click event |
showIndicators |
bool |
Whether to show the indicator |
imageMarginLeft |
double |
The distance from the image to the left |
imageMarginTop |
double |
The distance from the image to the top |
imageMarginRight |
double |
The distance from the image to the right |
imageMarginBottom |
double |
The distance from the image to the bottom |
imageMargin |
double |
The distance from the upper left to the lower right of the picture, set uniformly |
marginLeft |
double |
The overall distance from the carousel to the left |
marginTop |
double |
The overall distance of the carousel image from the top |
marginRight |
double |
The overall distance of the carousel image from the right |
marginBottom |
double |
The overall distance from the lower edge of the carousel image |
margin |
double |
The overall distance of the carousel image from the upper left to the lower right |
indicatorMarginLeft |
double |
The distance from the indicator to the left |
indicatorMarginRight |
double |
The distance from the indicator to the right |
indicatorMarginBottom |
double |
The distance from the indicator to the bottom |
indicatorSelectColor |
Color |
The color selected by the indicator |
indicatorUnSelectColor |
Color |
The unselected color of the indicator |
indicatorWidth |
double |
indicator width |
indicatorHeight |
double |
indicator high |
indicatorUnWidth |
double |
indicator unchecked wide |
indicatorUnHeight |
double |
indicator unchecked high |
indicatorMargin |
double |
indicator margin |
indicatorType |
IndicatorType |
indicator type circle, rectangle, text |
indicatorRadius |
double |
Number of rounded angles of the indicator |
indicatorBannerBottom |
bool |
Indicator position, whether it is on the banner or under the banner |
indicatorBottomColor |
Color |
The background of the indicator under the Banner, the default is transparent |
indicatorBottomHeight |
double |
The height of the indicator under the Banner |
indicatorBottomMarginRight |
double |
The indicator is to the right of the distance under the Banner |
indicatorBottomMarginLeft |
double |
The indicator is on the left of the distance under the Banner |
indicatorBottomMainAxisAlignment |
MainAxisAlignment |
The position of the indicator under the Banner left middle right |
viewportFraction |
double |
banner indentation |
textIndicatorAlignment |
Alignment |
text position |
textIndicatorStyle |
TextStyle |
text style |
textIndicatorBgColor |
Color |
text indicator background |
textIndicatorPadding |
double |
text indicator padding |
textIndicatorPaddingLeft |
double |
text indicator padding left |
textIndicatorPaddingTop |
double |
text indicator padding on |
textIndicatorPaddingRight |
double |
text indicator padding right |
textIndicatorPaddingBottom |
double |
text indicator padding down |
titleBgColor |
Color |
Text Title background |
titleHeight |
double |
Text Title Height |
titleAlignment |
Alignment |
The position of the text Title |
titleStyle |
TextStyle |
Text Title style |
titleMarginBottom |
double |
Text Title distance from bottom |
bannerOtherScale |
double |
Image scaling other than middle |
placeholderImage |
String |
Banner placeholder |
errorImage |
String |
Banner error graph |
imageBoxFit |
BoxFit |
Image scaling mode |
How to use
normal loading
VipBanner(
imageList: const [
"https://www.vipandroid.cn/ming/image/gan.png",
"https://www.vipandroid.cn/ming/image/zao.png"
],
bannerClick: (position) {
//条目点击
Toast.toast(context, msg: position.toString());
})
text indicator
VipBanner(
imageList: const [
"https://www.vipandroid.cn/ming/image/gan.png",
"https://www.vipandroid.cn/ming/image/zao.png"
],
indicatorType: IndicatorType.text,
bannerClick: (position) {
Toast.toast(context, msg: position.toString());
})
rounded corner indicator
VipBanner(
imageList: const [
"https://www.vipandroid.cn/ming/image/gan.png",
"https://www.vipandroid.cn/ming/image/zao.png"
],
indicatorType: IndicatorType.rectangle,
indicatorRadius: 5,
indicatorWidth: 20,
indicatorHeight: 5,
bannerClick: (position) {
Toast.toast(context, msg: position.toString());
})
There are many types of usage, so I won’t give examples one by one. You can see the page in the source code, the address is:
https://github.com/AbnerMing888/flutter_widget/blob/master/lib/ui/page/view/banner/banner_page.dart
6. Summary
When encapsulating, the following elements must be determined, one is the timing carousel, the other is gesture and timing conflict resolution, the third is infinite carousel, the fourth is the setting of the indicator, and the fifth is the effect of the picture carousel. It is not difficult to encapsulate these potential elements with a simple carousel.