banner_slider 0.0.1
banner_slider: ^0.0.1 copied to clipboard
Reusable Flutter video, image, text, banner and auto-playing slider widget.
import 'package:banner_slider/banner_slider.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Banner Slider Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
),
home: const ExampleHomePage(),
);
}
}
class ExampleHomePage extends StatelessWidget {
const ExampleHomePage({super.key});
static const List<BannerItem> _sliderItems = [
BannerItem(
title: 'Big Weekend Sale',
badge: 'Video',
deeplink: '/sale',
media: BannerSliderMedia.video(
url:
'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4',
playToEnd: true,
),
),
BannerItem(
title: 'Summer Collection',
badge: 'Image',
deeplink: '/collection',
media: BannerSliderMedia.imageNetwork(
url: 'https://picsum.photos/id/1020/1200/600',
displayDuration: Duration(seconds: 2),
),
),
BannerItem(
title: 'Animated Promo',
badge: 'GIF',
deeplink: '/promo',
media: BannerSliderMedia.imageNetwork(
url: 'https://media.giphy.com/media/ICOgUNjpvO0PC/giphy.gif',
),
),
BannerItem(
title: 'Flutter Branding',
badge: 'SVG',
deeplink: '/svg',
media: BannerSliderMedia.svgNetwork(
url:
'https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg',
displayDuration: Duration(seconds: 3),
),
),
];
static const BannerItem _singleItem = BannerItem(
title: 'Single Video Banner',
badge: 'Single',
deeplink: '/single',
media: BannerSliderMedia.video(
url:
'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4',
),
);
static void _showTapSnackBar(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message), behavior: SnackBarBehavior.floating),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Banner Slider Example'),
),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 16.0),
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
'Mixed Media BannerSlider',
style: Theme.of(context).textTheme.titleMedium,
),
),
const SizedBox(height: 12.0),
BannerSlider<BannerItem>(
items: _sliderItems,
mediaBuilder: (item) => item.media,
nonTimedItemDuration: const Duration(seconds: 3),
defaultPlayVideoToEnd: true,
padding: EdgeInsets.zero,
contentPadding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 8.0,
),
borderRadius: 0.0,
loop: true,
mediaFit: BoxFit.cover,
dotActiveColor: Colors.white,
dotInactiveColor: Colors.grey,
dotsPosition: DotsPosition.bottomRight,
itemBuilder: (context, index, muteState) {
final item = _sliderItems[index];
return _BannerCardContent(item: item, muteState: muteState);
},
onItemTap: (item) {
_showTapSnackBar(
context,
'Tapped: ${item.title} (${item.deeplink ?? '-'})',
);
},
),
const SizedBox(height: 24.0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
'BannerSlider.single()',
style: Theme.of(context).textTheme.titleMedium,
),
),
const SizedBox(height: 12.0),
SizedBox(
height: 340,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
BannerSlider<BannerItem>(
items: _sliderItems,
mediaBuilder: (item) => item.media,
nonTimedItemDuration: const Duration(seconds: 3),
defaultPlayVideoToEnd: true,
padding: EdgeInsets.zero,
contentPadding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 8.0,
),
borderRadius: 0.0,
loop: true,
width: 200,
mediaFit: BoxFit.cover,
dotActiveColor: Colors.white,
dotInactiveColor: Colors.grey,
dotsPosition: DotsPosition.bottomRight,
itemBuilder: (context, index, muteState) {
final item = _sliderItems[index];
return _BannerCardContent(item: item, muteState: muteState);
},
onItemTap: (item) {
_showTapSnackBar(
context,
'Tapped: ${item.title} (${item.deeplink ?? '-'})',
);
},
),
BannerSlider<BannerItem>.single(
item: _singleItem,
loop: true,
padding: EdgeInsets.zero,
contentPadding: const EdgeInsets.all(12.0),
borderRadius: 0.0,
aspectRatio: 9 / 16,
width: 200,
mediaFit: BoxFit.cover,
mediaBuilder: (item) => item.media,
itemBuilder: (context, index, muteState) {
return _BannerCardContent(
item: _singleItem,
muteState: muteState,
);
},
onItemTap: (item) {
_showTapSnackBar(
context,
'Tapped single banner: ${item.title}',
);
},
),
BannerSlider<BannerItem>.single(
item: _singleItem,
loop: true,
padding: EdgeInsets.zero,
contentPadding: const EdgeInsets.all(12.0),
borderRadius: 0.0,
aspectRatio: 9 / 16,
width: 200,
mediaFit: BoxFit.cover,
mediaBuilder: (item) => item.media,
itemBuilder: (context, index, muteState) {
return _BannerCardContent(
item: _singleItem,
muteState: muteState,
);
},
onItemTap: (item) {
_showTapSnackBar(
context,
'Tapped single banner: ${item.title}',
);
},
),
],
),
),
const SizedBox(height: 24.0),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
'Timing rules: non-video uses displayDuration or fallback. '
'Video plays to end by default unless duration is provided.',
),
),
],
),
);
}
}
class BannerItem {
final String title;
final String? badge;
final String? deeplink;
final BannerSliderMedia media;
const BannerItem({
required this.title,
required this.media,
this.badge,
this.deeplink,
});
}
final class _BannerCardContent extends StatelessWidget {
final BannerItem item;
final BannerSliderMuteState muteState;
const _BannerCardContent({required this.item, required this.muteState});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (item.badge != null) _BannerBadge(label: item.badge!),
if (muteState.canMute)
IconButton(
onPressed: muteState.toggle,
style: IconButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.black.withValues(alpha: 0.25),
),
icon: Icon(
muteState.muted
? Icons.volume_off_rounded
: Icons.volume_up_rounded,
),
),
],
),
Text(
item.title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Colors.white,
fontWeight: FontWeight.w700,
),
),
],
);
}
}
final class _BannerBadge extends StatelessWidget {
final String label;
const _BannerBadge({required this.label});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(999),
),
child: Text(
label,
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
);
}
}