Story View In Flutter :
This flutter tutorial post is Story View In Flutter. That is used to whatsApp.
Screenshot :

main.dart
import 'package:flutter/material.dart'; import 'package:story_view/story_view.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Tutorial', debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.orange, ), home: Home()); } } class Home extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Story View Flutter"), ), body: Container( margin: EdgeInsets.all( 8, ), child: Column( children: <Widget>[ Container( height: 300, child: StoryView( [ StoryItem.text( "Tap Here", Colors.orange, roundedTop: true, ), StoryItem.inlineImage( NetworkImage( "https://image.ibb.co/gCZFbx/Banku-and-tilapia.jpg"), caption: Text( "Description", style: TextStyle( color: Colors.white, backgroundColor: Colors.black54, fontSize: 17, ), ), ), StoryItem.inlineImage( NetworkImage( "https://image.ibb.co/cU4WGx/Omotuo-Groundnut-Soup-braperucci-com-1.jpg"), caption: Text( "Description", style: TextStyle( color: Colors.white, backgroundColor: Colors.black54, fontSize: 17, ), ), ), ], onStoryShow: (s) { print("Showing a story"); }, onComplete: () { print("Completed a cycle"); }, progressPosition: ProgressPosition.bottom, repeat: false, inline: true, ), ), Material( child: InkWell( onTap: () { Navigator.of(context).push( MaterialPageRoute(builder: (context) => MoreStories())); }, child: Container( decoration: BoxDecoration( color: Colors.black54, borderRadius: BorderRadius.vertical(bottom: Radius.circular(8))), padding: EdgeInsets.symmetric(vertical: 8), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Icon( Icons.arrow_forward, color: Colors.white, ), SizedBox( width: 16, ), Text( "View more stories", style: TextStyle(fontSize: 16, color: Colors.white), ), ], ), ), ), ), ], ), ), ); } } class MoreStories extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("More"), ), body: StoryView( [ StoryItem.text( "I guess you'd love to see more of our food. That's great.", Colors.blue, ), StoryItem.text( "Nice!\n\nTap to continue.", Colors.red, ), StoryItem.pageImage( NetworkImage( "https://image.ibb.co/cU4WGx/Omotuo-Groundnut-Soup-braperucci-com-1.jpg"), caption: "Still sampling", ) ], onStoryShow: (s) { print("Showing a story"); }, onComplete: () { print("Completed a cycle"); }, progressPosition: ProgressPosition.top, repeat: false, ), ); } }
story_view.dart
import 'dart:math'; import 'dart:ui'; import 'dart:async'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; enum ProgressPosition { top, bottom } enum IndicatorHeight { small, large } class StoryItem { final Duration duration; bool shown; final Widget view; StoryItem( this.view, { this.duration = const Duration(seconds: 3), this.shown = false, }) : assert(duration != null, "[duration] should not be null"); static StoryItem text( String title, Color backgroundColor, { bool shown = false, double fontSize = 18, bool roundedTop = false, bool roundedBottom = false, }) { double contrast = ContrastHelper.contrast([ backgroundColor.red, backgroundColor.green, backgroundColor.blue, ], [ 255, 255, 255 ] /** white text */); return StoryItem( Container( decoration: BoxDecoration( color: backgroundColor, borderRadius: BorderRadius.vertical( top: Radius.circular(roundedTop ? 8 : 0), bottom: Radius.circular(roundedBottom ? 8 : 0), ), ), padding: EdgeInsets.symmetric( horizontal: 24, vertical: 16, ), child: Center( child: Text( title, style: TextStyle( color: contrast > 1.8 ? Colors.white : Colors.black, fontSize: fontSize, ), textAlign: TextAlign.center, ), ), //color: backgroundColor, ), shown: shown); } static StoryItem pageImage( ImageProvider image, { BoxFit imageFit = BoxFit.fitWidth, String caption, bool shown = false, }) { assert(imageFit != null, "[imageFit] should not be null"); return StoryItem( Container( color: Colors.black, child: Stack( children: <Widget>[ Center( child: Image( image: image, height: double.infinity, width: double.infinity, fit: imageFit, ), ), SafeArea( child: Align( alignment: Alignment.bottomCenter, child: Container( width: double.infinity, margin: EdgeInsets.only( bottom: 24, ), padding: EdgeInsets.symmetric( horizontal: 24, vertical: 8, ), color: caption != null ? Colors.black54 : Colors.transparent, child: caption != null ? Text( caption, style: TextStyle( fontSize: 15, color: Colors.white, ), textAlign: TextAlign.center, ) : SizedBox(), ), ), ) ], ), ), shown: shown); } static StoryItem inlineImage( ImageProvider image, { Text caption, bool shown = false, bool roundedTop = true, bool roundedBottom = false, }) { return StoryItem( Container( decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.vertical( top: Radius.circular(roundedTop ? 8 : 0), bottom: Radius.circular(roundedBottom ? 8 : 0), ), image: DecorationImage( image: image, fit: BoxFit.cover, )), child: Container( margin: EdgeInsets.only( bottom: 16, ), padding: EdgeInsets.symmetric( horizontal: 24, vertical: 8, ), child: Align( alignment: Alignment.bottomLeft, child: Container( child: caption == null ? SizedBox() : caption, width: double.infinity, ), ), ), ), shown: shown, ); } } class StoryView extends StatefulWidget { final List<StoryItem> storyItems; final VoidCallback onComplete; final ValueChanged<StoryItem> onStoryShow; final ProgressPosition progressPosition; final bool repeat; final bool inline; StoryView( this.storyItems, { this.onComplete, this.onStoryShow, this.progressPosition = ProgressPosition.top, this.repeat = false, this.inline = false, }) : assert(storyItems != null && storyItems.length > 0, "[storyItems] should not be null or empty"), assert(progressPosition != null, "[progressPosition] cannot be null"), assert( repeat != null, "[repeat] cannot be null", ), assert(inline != null, "[inline] cannot be null"); @override State<StatefulWidget> createState() { return StoryViewState(); } } class StoryViewState extends State<StoryView> with TickerProviderStateMixin { StoryViewState(); AnimationController animationController; Animation<double> currentAnimation; Timer debouncer; StoryItem get lastShowing => widget.storyItems.firstWhere((it) => !it.shown, orElse: () => null); @override void initState() { super.initState(); final firstPage = widget.storyItems.firstWhere((it) { return !it.shown; }, orElse: () { widget.storyItems.forEach((it2) { it2.shown = false; }); return null; }); if (firstPage != null) { final lastShownPos = widget.storyItems.indexOf(firstPage); widget.storyItems.sublist(lastShownPos).forEach((it) { it.shown = false; }); } play(); } @override void dispose() { animationController?.dispose(); super.dispose(); } void play() { animationController?.dispose(); // get the next playing page final storyItem = widget.storyItems.firstWhere((it) { return !it.shown; }); if (widget.onStoryShow != null) { widget.onStoryShow(storyItem); } animationController = AnimationController(duration: storyItem.duration, vsync: this); animationController.addStatusListener((status) { if (status == AnimationStatus.completed) { storyItem.shown = true; if (widget.storyItems.last != storyItem) { beginPlay(); } else { // done playing onComplete(); } } }); currentAnimation = Tween(begin: 0.0, end: 1.0).animate(animationController); animationController.forward(); } void beginPlay() { setState(() {}); play(); } void onComplete() { if (widget.onComplete != null) { widget.onComplete(); } else { print("Done"); } if (widget.repeat) { widget.storyItems.forEach((it) { it.shown = false; }); beginPlay(); } } void goBack() { animationController.stop(); if (this.lastShowing == null) { widget.storyItems.last.shown = false; } if (this.lastShowing == widget.storyItems.first) { beginPlay(); } else { this.lastShowing.shown = false; int lastPos = widget.storyItems.indexOf(this.lastShowing); final previous = widget.storyItems[lastPos - 1]; previous.shown = false; beginPlay(); } } void goForward() { if (this.lastShowing != widget.storyItems.last) { animationController.stop(); final _last = this.lastShowing; if (_last != null) { _last.shown = true; if (_last != widget.storyItems.last) { beginPlay(); } } } else { animationController.animateTo(1.0, duration: Duration(milliseconds: 10)); } } void pause() { this.animationController?.stop(canceled: false); } void unpause() { this.animationController?.forward(); } Widget get currentView => widget.storyItems .firstWhere((it) => !it.shown, orElse: () => widget.storyItems.last) .view; @override Widget build(BuildContext context) { return Container( color: Colors.white, child: Stack( children: <Widget>[ currentView, Align( alignment: widget.progressPosition == ProgressPosition.top ? Alignment.topCenter : Alignment.bottomCenter, child: SafeArea( bottom: widget.inline ? false : true, // we use SafeArea here for notched and bezeles phones child: Container( padding: EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), child: PageBar( widget.storyItems .map((it) => PageData(it.duration, it.shown)) .toList(), this.currentAnimation, key: UniqueKey(), indicatorHeight: widget.inline ? IndicatorHeight.small : IndicatorHeight.large, ), ), ), ), Align( alignment: Alignment.centerRight, heightFactor: 1, child: RawGestureDetector( gestures: <Type, GestureRecognizerFactory>{ TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>( () => TapGestureRecognizer(), (instance) { instance ..onTapDown = (details) { pause(); debouncer?.cancel(); debouncer = Timer(Duration(milliseconds: 500), () {}); } ..onTapUp = (details) { if (debouncer?.isActive == true) { debouncer.cancel(); debouncer = null; goForward(); } else { debouncer.cancel(); debouncer = null; unpause(); } }; }) }, ), ), Align( alignment: Alignment.centerLeft, heightFactor: 1, child: SizedBox( child: GestureDetector( onTap: () { goBack(); }, ), width: 70, ), ), ], ), ); } } class PageData { Duration duration; bool shown; PageData(this.duration, this.shown); } class PageBar extends StatefulWidget { final List<PageData> pages; final Animation<double> animation; final IndicatorHeight indicatorHeight; PageBar( this.pages, this.animation, { this.indicatorHeight = IndicatorHeight.large, Key key, }) : super(key: key); @override State<StatefulWidget> createState() { return PageBarState(); } } class PageBarState extends State<PageBar> { double spacing = 4; @override void initState() { super.initState(); int count = widget.pages.length; spacing = count > 15 ? 1 : count > 10 ? 2 : 4; widget.animation.addListener(() { setState(() {}); }); } bool isPlaying(PageData page) { return widget.pages.firstWhere((it) => !it.shown, orElse: () => null) == page; } @override Widget build(BuildContext context) { return Row( children: widget.pages.map((it) { return Expanded( child: Container( padding: EdgeInsets.only( right: widget.pages.last == it ? 0 : this.spacing), child: StoryProgressIndicator( isPlaying(it) ? widget.animation.value : it.shown ? 1 : 0, indicatorHeight: widget.indicatorHeight == IndicatorHeight.large ? 5 : 3, ), ), ); }).toList(), ); } } class StoryProgressIndicator extends StatelessWidget { final double value; final double indicatorHeight; StoryProgressIndicator( this.value, { this.indicatorHeight = 5, }) : assert(indicatorHeight != null && indicatorHeight > 0, "[indicatorHeight] should not be null or less than 1"); @override Widget build(BuildContext context) { return CustomPaint( size: Size.fromHeight( this.indicatorHeight, ), foregroundPainter: IndicatorOval( Colors.white.withOpacity(0.8), this.value, ), painter: IndicatorOval( Colors.white.withOpacity(0.4), 1.0, ), ); } } class IndicatorOval extends CustomPainter { final Color color; final double widthFactor; IndicatorOval(this.color, this.widthFactor); @override void paint(Canvas canvas, Size size) { final paint = Paint()..color = this.color; canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromLTWH(0, 0, size.width * this.widthFactor, size.height), Radius.circular(3)), paint); } @override bool shouldRepaint(CustomPainter oldDelegate) { return true; } } class ContrastHelper { static double luminance(int r, int g, int b) { final a = [r, g, b].map((it) { double value = it.toDouble() / 255.0; return value <= 0.03928 ? value / 12.92 : pow((value + 0.055) / 1.055, 2.4); }).toList(); return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722; } static double contrast(rgb1, rgb2) { return luminance(rgb2[0], rgb2[1], rgb2[2]) / luminance(rgb1[0], rgb1[1], rgb1[2]); } }
The flutter tutorial is a website that bring you the latest and amazing resources of code. All the languages codes are included in this website. The languages like flutter, android, java,kotlin etc.with the help of this languages any user can develop the beautiful application
For more information about Flutter. visit www.fluttertutorial.in