banner_slider
Reusable Flutter widget for mixed-media hero banners: network video, raster images (including GIF), and SVG, with cross-fade transitions, optional page dots, visibility-aware autoplay, and a customizable overlay.
Demo
Table of contents
- Features
- Installation
- Quick start
- API overview
BannerSliderMedia- Timing and autoplay
- Mute and overlay (
BannerSliderMuteState) - Layout, dots, and tap
- Single-item constructor
- Breaking changes (from
videoUrlBuilder) - Example app
Features
| Area | What you get |
|---|---|
| Media | Video (network URL), images/GIF (Image.network / Image.asset), SVG (SvgPicture.network / SvgPicture.asset). |
| Transitions | Cross-fade between slides; video preloading; raster image preloading for the next slide. |
| Generic items | BannerSlider<T> — your model type, mediaBuilder maps item → BannerSliderMedia. |
| Overlay | itemBuilder(context, index, muteState) for titles, CTAs, mute button, etc. |
| Mute | BannerSliderMuteState: muted, canMute, toggle — no dead buttons on non-video slides. |
| Dots | Built-in indicators; position (DotsPosition), optional active/inactive colors (multi-item slider). |
| Layout | padding, contentPadding, borderRadius, width, height, aspectRatio, mediaFit. |
| Behavior | loop, visibility-based play/pause, lifecycle handling, onItemTap(T item). |
Installation
Add the package to your app pubspec.yaml (path or pub.dev once published):
dependencies:
banner_slider:
path: ../banner_slider
Then:
flutter pub get
Transitive dependencies used by this package include video_player, visibility_detector, and flutter_svg for SVG rendering.
Quick start
import 'package:banner_slider/banner_slider.dart';
BannerSlider<MyItem>(
items: myItems,
mediaBuilder: (item) => item.media,
itemBuilder: (context, index, muteState) {
return YourOverlay(/* use muteState when muteState.canMute */);
},
);
API overview
BannerSlider<T>
| Parameter | Type | Default | Description |
|---|---|---|---|
items |
List<T> |
required | Slide data. |
mediaBuilder |
BannerSliderMedia Function(T) |
required | Maps each item to its media + timing hints. |
itemBuilder |
ItemBuilder |
required | Overlay: (context, index, muteState). |
loop |
bool |
true |
Whether the slider wraps after the last item. |
nonTimedItemDuration |
Duration |
3s |
Fallback display time for image/svg (and for video when not playing to end — see timing). |
defaultPlayVideoToEnd |
bool |
true |
If true, video uses natural end unless overridden per item. |
loadingBuilder |
Widget Function(BuildContext, int)? |
default spinner | Builds the loading UI for the current slide while media is being prepared. |
loaderColor |
Color? |
theme default | Colors the built-in loader when loadingBuilder is not provided. |
onItemTap |
ValueChanged<T>? |
null |
Called when the banner is tapped (current item). |
padding |
EdgeInsetsGeometry |
horizontal 16 |
Outer padding around the slider. |
contentPadding |
EdgeInsetsGeometry |
12 all sides |
Padding for overlay content (and dot alignment). |
borderRadius |
double |
16 |
Corner radius of the clipped banner. |
width / height |
double? |
null |
Optional SizedBox around the aspect-ratio child. |
aspectRatio |
double |
21/9 |
Banner aspect ratio. |
mediaFit |
BoxFit |
fitWidth |
How video/image/svg fill the frame. |
dotActiveColor / dotInactiveColor |
Color? |
primary / white 50% | Dot colors when items.length > 1 (inactive defaults to Colors.white at 50% opacity). |
dotsPosition |
DotsPosition |
bottomCenter |
Corner/edge alignment for dots. |
BannerSlider<T>.single(...)
Convenience factory: same behavior as BannerSlider(items: [item], ...) but without dotActiveColor, dotInactiveColor, or dotsPosition parameters. Dots only render when there is more than one slide, so these are omitted from .single to keep the API small.
BannerSliderMedia
Use the constructors below from mediaBuilder:
| Constructor | Use case |
|---|---|
BannerSliderMedia.video({ url, displayDuration?, playToEnd? }) |
Network MP4 (etc.). |
BannerSliderMedia.imageNetwork({ url, displayDuration? }) |
Remote PNG/JPEG/WebP/GIF. |
BannerSliderMedia.imageAsset({ path, displayDuration? }) |
Asset raster image. |
BannerSliderMedia.svgNetwork({ url, displayDuration? }) |
Remote SVG. |
BannerSliderMedia.svgAsset({ path, displayDuration? }) |
Asset SVG. |
Optional fields:
displayDuration— Forces a timed slide; when set, this duration is used for that slide (including video — transitions early instead of waiting for end-of-file).playToEnd(video only) — Overrides sliderdefaultPlayVideoToEndfor that item (true= wait for video end if nodisplayDuration).
Timing and autoplay
-
Image / GIF / SVG
- If
displayDurationis set on the media → use it. - Else → use
nonTimedItemDuration(default 3 seconds).
- If
-
Video
- If
displayDurationis set → advance after that duration (video does not need to finish). - Else, if
playToEnd ?? defaultPlayVideoToEndis true → advance near end of video (existing end-detection behavior). - Else → use
nonTimedItemDurationas the video’s fixed time on screen.
- If
-
Visibility
Playback pauses when the banner is not sufficiently visible; resumes when it is (viavisibility_detector). -
Single video +
loop: true
Native looping may apply when there is only one video item and timing is “play to end” (see implementation in package).
Mute and overlay (BannerSliderMuteState)
The overlay builder receives:
BannerSliderMuteState {
bool muted; // Last known muted flag for video volume
bool canMute; // true only when current slide is video with initialized controller
VoidCallback? toggle; // null when !canMute; otherwise toggles mute
}
Recommended UI: show a volume control only when muteState.canMute; use muteState.toggle as onPressed. For image/svg slides, canMute is false so you do not show a misleading mute button.
Layout, dots, and tap
- Tap: the full banner area triggers
onItemTapwith the currentTitem (your model), not the fading layer. - Dots: shown only when
items.length > 1. Positions:topLeft,topCenter,topRight,bottomLeft,bottomCenter,bottomRight. - Gradient: the package draws a subtle bottom-to-top gradient behind your overlay for readability; your
itemBuildercontent sits on top. - Loading UI: customize the loader with
loadingBuilder; or keep the built-in spinner and change its color withloaderColor.
Single-item constructor
BannerSlider<Item>.single(
item: onlyItem,
mediaBuilder: (item) => item.media,
itemBuilder: (context, index, muteState) => const SizedBox.shrink(),
onItemTap: (item) { },
);
Useful for one full-width or one-card banner without duplicating [item] yourself.
Breaking changes (from videoUrlBuilder)
Older versions used videoUrlBuilder: String Function(T). That API is removed in favor of:
mediaBuilder: BannerSliderMedia Function(T item)
Map each item to BannerSliderMedia.video(url: ...) or another media constructor. Overlay API also moved from (muted, onMute) to (muteState).
Full example
import 'package:banner_slider/banner_slider.dart';
import 'package:flutter/material.dart';
class PromoBanner {
final String title;
final BannerSliderMedia media;
const PromoBanner({required this.title, required this.media});
}
const items = <PromoBanner>[
PromoBanner(
title: 'Video',
media: BannerSliderMedia.video(
url: 'https://example.com/video.mp4',
playToEnd: true,
),
),
PromoBanner(
title: 'GIF',
media: BannerSliderMedia.imageNetwork(
url: 'https://example.com/animated.gif',
displayDuration: Duration(seconds: 2),
),
),
PromoBanner(
title: 'SVG',
media: BannerSliderMedia.svgNetwork(
url: 'https://example.com/logo.svg',
),
),
];
// In your widget tree:
BannerSlider<PromoBanner>(
items: items,
mediaBuilder: (item) => item.media,
nonTimedItemDuration: const Duration(seconds: 3),
defaultPlayVideoToEnd: true,
mediaFit: BoxFit.cover,
dotsPosition: DotsPosition.topRight,
dotActiveColor: Colors.white,
dotInactiveColor: Colors.white54,
itemBuilder: (context, index, muteState) {
final item = items[index];
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(item.title, style: const TextStyle(color: Colors.white)),
if (muteState.canMute)
IconButton(
onPressed: muteState.toggle,
icon: Icon(
muteState.muted ? Icons.volume_off : Icons.volume_up,
),
),
],
);
},
onItemTap: (item) {},
);
Example app
A runnable demo lives under example/ (mixed media, dots, horizontal list, etc.):
cd example
flutter run
Use it as a reference for mediaBuilder, timing, and muteState wiring.