segmented_circular_next_button 0.0.1
segmented_circular_next_button: ^0.0.1 copied to clipboard
A Flutter widget that provides a circular next button with an animated segmented progress ring for onboarding flows
import 'package:flutter/material.dart';
import 'package:segmented_circular_next_button/segmented_circular_next_button.dart';
void main() => runApp(const ExampleApp());
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SegmentedCircularNextButton β Example',
debugShowCheckedModeBanner: false,
home: const OnboardingExample(),
);
}
}
class _Page {
const _Page({
required this.emoji,
required this.title,
required this.subtitle,
required this.color,
});
final String emoji;
final String title;
final String subtitle;
final Color color;
}
const List<_Page> _pages = [
_Page(
emoji: 'πΊοΈ',
title: 'Explore the world',
subtitle: 'Discover places you have never imagined, one swipe at a time.',
color: Color(0xFF4F6EF7),
),
_Page(
emoji: 'π€',
title: 'Meet real people',
subtitle: 'Connect with locals and fellow travellers wherever you go.',
color: Color(0xFF2EC4B6),
),
_Page(
emoji: 'πΈ',
title: 'Capture memories',
subtitle: 'Every moment deserves to be saved beautifully and shared.',
color: Color(0xFFFF6B6B),
),
_Page(
emoji: 'π',
title: 'Ready to launch',
subtitle: 'Your adventure starts the second you tap "Get started".',
color: Color(0xFFFFA552),
),
];
class OnboardingExample extends StatefulWidget {
const OnboardingExample({super.key});
@override
State<OnboardingExample> createState() => _OnboardingExampleState();
}
class _OnboardingExampleState extends State<OnboardingExample> {
late final PageController _controller;
final ValueNotifier<int> _currentPage = ValueNotifier<int>(0);
@override
void initState() {
super.initState();
_controller = PageController();
}
@override
void dispose() {
_controller.dispose();
_currentPage.dispose();
super.dispose();
}
bool get _isLastPage => _currentPage.value == _pages.length - 1;
void _next() {
if (_isLastPage) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('π All done! Navigate to home here.')),
);
return;
}
_controller.nextPage(
duration: const Duration(milliseconds: 450),
curve: Curves.easeInOut,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ValueListenableBuilder<int>(
valueListenable: _currentPage,
builder: (context, page, _) {
final _Page data = _pages[page];
return AnimatedContainer(
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
color: data.color,
child: SafeArea(
child: Column(
children: [
Expanded(
child: PageView.builder(
controller: _controller,
onPageChanged: (i) => _currentPage.value = i,
itemCount: _pages.length,
itemBuilder: (context, i) =>
_PageContent(page: _pages[i]),
),
),
SegmentedCircularNextButton(
currentPage: page,
totalPages: _pages.length,
onTap: _next,
icon: _isLastPage
? Icons.check_rounded
: Icons.arrow_forward_rounded,
ringColor: Colors.white,
ringBackgroundColor: Colors.white24,
buttonColor: Colors.white,
iconColor: data.color,
ringStrokeWidth: 3,
buttonSize: 64,
innerButtonSize: 52,
animationDuration: const Duration(milliseconds: 450),
animationCurve: Curves.easeInOut,
),
],
),
),
);
},
),
);
}
}
class _PageContent extends StatelessWidget {
const _PageContent({required this.page});
final _Page page;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(page.emoji, style: const TextStyle(fontSize: 96)),
const SizedBox(height: 32),
Text(
page.title,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.w700,
color: Colors.white,
height: 1.2,
),
),
const SizedBox(height: 16),
Text(
page.subtitle,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: Colors.white.withAlpha(150),
height: 1.6,
),
),
],
),
);
}
}