I'm trying to implement a scrollable background image (parallax).
Like in a home screen launcher.
An example:
In Evie launcher:
this video
I've tried using AnimatedBuilder mentioned here in the docs like this.
I'm using a ValueNotifier<double> as the listener for the animation of the AnimatedBuilder Widget.
The complete code is this
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'PageView Scrolling',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>{
ValueNotifier<double> _notifier;
double _prevnotifier;
double getOffset(){
if (_notifier.value == 0 && _prevnotifier != null){
return _prevnotifier;
}
return _notifier.value;
}
#override
void dispose() {
_notifier?.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
_notifier = ValueNotifier<double>(0);
_prevnotifier = _notifier.value;
_notifier.addListener(
(){
print('object ${_notifier.value}');
if (_notifier.value != 0)
_prevnotifier = _notifier.value;
}
);
}
#override
Widget build(BuildContext context) {
print("Size is ${MediaQuery.of(context).size}");
return Scaffold(
body: Stack(
children: <Widget>[
AnimatedBuilder(
animation: _notifier,
builder: (context, _) {
return Transform.translate(
offset: Offset(-getOffset() * 60, 0),
child: Image.network(
"https://w.wallhaven.cc/full/r2/wallhaven-r276qj.png",
height: MediaQuery.of(context).size.height,
fit: BoxFit.fitHeight
),
);
},
),
NotifyingPageView(
notifier: _notifier,
),
],
),
);
}
}
class NotifyingPageView extends StatefulWidget {
final ValueNotifier<double> notifier;
const NotifyingPageView({Key key, this.notifier}) : super(key: key);
#override
_NotifyingPageViewState createState() => _NotifyingPageViewState();
}
class _NotifyingPageViewState extends State<NotifyingPageView> {
int _previousPage;
PageController _pageController;
void _onScroll() {
// Consider the page changed when the end of the scroll is reached
// Using onPageChanged callback from PageView causes the page to change when
// the half of the next card hits the center of the viewport, which is not
// what I want
if (_pageController.page.toInt() == _pageController.page) {
_previousPage = _pageController.page.toInt();
}
widget.notifier?.value = _pageController.page - _previousPage;
}
#override
void initState() {
_pageController = PageController(
initialPage: 0,
viewportFraction: 0.9,
)..addListener(_onScroll);
_previousPage = _pageController.initialPage;
super.initState();
}
List<Widget> _pages = List.generate(
10,
(index) {
return Container(
height: 10,
alignment: Alignment.center,
color: Colors.transparent,
child: Text(
"Card number $index",
style: TextStyle(
color: Colors.teal,
fontWeight: FontWeight.bold,
fontSize: 25,
),
),
);
},
);
#override
Widget build(BuildContext context) {
return PageView(
children: _pages,
controller: _pageController,
);
}
}
The image can be found here
Now I have two issues:
The image when using fit: BoxFit.fitHeight is not overflowing fully. It's currently like this
Because the value will become zero when the animation is done it's snapping like this:
this video
I tried storing the value just before the _notifier.value becomes zero and use it when it returns zero but it resulted in that weird transition that I've shown you in that above video.
What do you suggest can be done to make something like a scrollable wallpaper in flutter?
Something like this
Design
This is not as trivial as I thought it would be.
TLDR; Github read the comments.
I used a ValueNotifier<double> like I mentioned to control the scroll.
Then instead of Transform.translate I used an OverflowBox with its alignment property. Which is computed based on the notifier.value before rendering.
And to display the image in fullscreen mode:
I used AspectRatio with a child DecoratedBox whose decoration is a BoxDecoration with its image as an ImageProvider.
All the code can be found here on github. (Read the comments)
And this issue on github has slightly detailed info and a less complicated alternate implementation by Antonello Galipò
Related
I want to show a random picture everytime the user enters the page. I also have a Button (the red container with hovering) on this page, and when the user is hovering it, a new random picture shows, but it shouldn't change since the page was loaded. I think it has something to do with the setState(), but I don't know what to do. Code:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:core';
import 'dart:math';
class Home2 extends StatefulWidget {
const Home2({Key? key}) : super(key: key);
#override
_Home2State createState() => _Home2State();
}
class _Home2State extends State<Home2> {
dynamic listCinematicImages = [
"assets/cinematic/1.jpg",
"assets/cinematic/2.jpg",
"assets/cinematic/3.jpg",
"assets/cinematic/4.jpg",
"assets/cinematic/5.jpg",
"assets/cinematic/6.jpg",
"assets/cinematic/7.jpg",
];
late Random rnd;
#override
bool isHoveringButton = false;
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.black,
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: double.infinity,
width: size.width * 0.5,
alignment: Alignment.center,
child: InkWell(
onTap: () {
},
onHover: (hovering) {
setState(() => isHoveringButton = hovering);
},
child: Container(
height: 50,
width: 50,
color: Colors.red,
),
),
),
Container(
height: double.infinity,
width: size.width * 0.5,
child: img(),
),
],
),
);
}
Image img() {
int min = 0;
int max = listCinematicImages.length-1;
rnd = new Random();
int r = min + rnd.nextInt(max - min);
String image_name = listCinematicImages[r].toString();
return Image.asset(image_name, fit: BoxFit.cover,);
}
}
I dont know if this helps but this is some error given out:
Error: Expected a value of type 'Map<String, dynamic>', but got one of type 'Null'
at Object.throw_ [as throw] (http://localhost:60569/dart_sdk.js:5054:11)
at Object.castError (http://localhost:60569/dart_sdk.js:5013:15)
at Object.cast [as as] (http://localhost:60569/dart_sdk.js:5336:17)
at Function.as_C [as as] (http://localhost:60569/dart_sdk.js:4959:19)
alright there's a lot a things going on here. Let's think about this step by step. You're trying to show a new image on certain events. So we need to create a variable to keep track of the current image:
class Home2 extends StatefulWidget {
const Home2({Key? key}) : super(key: key);
#override
_Home2State createState() => _Home2State();
}
class _Home2State extends State<Home2> {
static const listCinematicImages = [
"assets/cinematic/1.jpg",
"assets/cinematic/2.jpg",
"assets/cinematic/3.jpg",
"assets/cinematic/4.jpg",
"assets/cinematic/5.jpg",
"assets/cinematic/6.jpg",
"assets/cinematic/7.jpg",
];
late String currentImage;
}
before we start worrying about the events, let's figure out how to select a random image from your list. the function you provided has the right elements but there's some funky stuff going on. a simple example I'd give is:
String _getRandomImage() {
final randomIndex = Random().nextInt(listCinematicImages.length-1);
return listCinematicImages[randomIndex];
}
now we have all the elements, we just have to update the widget at the correct time. firstly you want to set a new image every time this widget is loaded. we can do this using the initState method:
#override
void initState() {
super.initState();
final newImage = _getRandomImage();
setState(() => currentImage = newImage);
}
and you want to change the image again when a user hovers over the image. You can indeed do this with an InkWell but keep in mind according to the documentation the onHover will provide a callback every time a mouse enters AND leaves the region, so we have to make sure to only update the image when we enter the region:
InkWell(
onHover: (bool hasEntered) {
if(!hasEntered) return;
final newImage = _getRandomImage();
setState(() => currentImage = newImage);
}
);
And that's it! to put it all together with your example:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:core';
import 'dart:math';
class Home2 extends StatefulWidget {
const Home2({Key? key}) : super(key: key);
#override
_Home2State createState() => _Home2State();
}
class _Home2State extends State<Home2> {
static const listCinematicImages = [
"assets/cinematic/1.jpg",
"assets/cinematic/2.jpg",
"assets/cinematic/3.jpg",
"assets/cinematic/4.jpg",
"assets/cinematic/5.jpg",
"assets/cinematic/6.jpg",
"assets/cinematic/7.jpg",
];
late String currentImage;
#override
void initState() {
super.initState();
final newImage = _getRandomImage();
setState(() => currentImage = newImage);
}
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.black,
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: double.infinity,
width: size.width * 0.5,
alignment: Alignment.center,
child: InkWell(
onHover: (bool hasEntered) {
if(!hasEntered) return;
final newImage = _getRandomImage();
setState(() => currentImage = newImage);
},
child: Container(
height: 50,
width: 50,
color: Colors.red,
),
),
),
Container(
height: double.infinity,
width: size.width * 0.5,
child: Image.asset(currentImage, fit: BoxFit.cover,);,
),
],
),
);
}
String _getRandomImage() {
final randomIndex = Random().nextInt(listCinematicImages.length-1);
return listCinematicImages[randomIndex];
}
}
I am quite new to app development and I had a few questions about Flutter I was hoping someone could help me with!
First, I am trying to code an intro-slide part in my code. I have found this code online (https://flutterawesome.com/simple-and-configurable-app-introduction-slider-for-flutter/) and when I tried executing it, using my own images, only the background color seems to print. When I remove the background colors, it is just a white screen. Is my pageImage part correct? I saved an assert folder everywhere, so I'm unsure if that is the problem. I have included my code at the end.
Thank you for your time!
class _MyHomePageState extends State<MyHomePage> {
List<Slide> slides = new List();
#override
void initState() {
super.initState();
slides.add(
new Slide(
title: "ERASER",
description: "Allow miles wound place the leave had. To sitting subject no improve studied limited",
pathImage: "assets/images/1.png",
backgroundColor: Colors.pink[200],
),
);
slides.add(
new Slide(
title: "PENCIL",
description: "Ye indulgence unreserved connection alteration appearance",
pathImage: "assets/images/1.png",
backgroundColor: Colors.blue[200],
),
);
slides.add(
new Slide(
title: "RULER",
description:
"Much evil soon high in hope do view. Out may few northward believing attempted. Yet timed being songs marry one defer men our. Although finished blessing do of",
pathImage: "assets/images/3.jpg",
),
);
}
void onDonePress() {
// TODO: go to next screen
}
void onSkipPress() {
// TODO: go to next screen
}
#override
Widget build(BuildContext context) {
return new IntroSlider(
slides: this.slides,
onDonePress: this.onDonePress,
onSkipPress: this.onSkipPress,
);
}
}
**Solution: edit assets in pubspec page
Edit:
On the left (the orange part) is how I want the blue image to appear: No scrolling and fills the whole page. However, I tried to make my image (on the right) fill the page by editing the width and height and I started having to scroll where there is the pink background below and above the image (I assume it is because it keeps having to center the image).
Is there any way to make my image my background so it is like the picture on the left? I understand the orange color background is the background color, but hopefully, by comparing the two it makes sense. Thank you!
I create new intro widget. Here is the code.
import 'package:flutter/material.dart';
class MyIntroView extends StatefulWidget {
final List<Widget> pages;
final VoidCallback onIntroCompleted;
const MyIntroView({
Key key,
#required this.pages,
#required this.onIntroCompleted,
}) : assert(pages != null),
assert(onIntroCompleted != null),
super(key: key);
#override
_MyIntroViewState createState() => _MyIntroViewState();
}
class _MyIntroViewState extends State<MyIntroView> {
PageController _pageController;
int _currentPage = 0;
#override
void initState() {
_pageController = PageController(
initialPage: _currentPage,
);
super.initState();
}
#override
void dispose() {
_pageController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
NotificationListener<ScrollEndNotification>(
onNotification: (x) {
setState(() {
_currentPage = _pageController.page.round();
});
return false;
},
child: PageView(
children: widget.pages,
controller: _pageController,
),
),
Align(
alignment: Alignment.bottomCenter,
child: _buildBottomButtons(),
),
],
);
}
bool get _isFinalPage => _currentPage == widget.pages.length - 1;
Widget _buildBottomButtons() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Opacity(
opacity: _isFinalPage ? 0.0 : 1.0,
child: _buildButton("SKIP", _gotoLastPage),
),
_buildNavIndicator(),
_isFinalPage
? _buildButton("DONE", widget.onIntroCompleted)
: _buildButton("NEXT", _gotoNextPage),
],
),
);
}
Widget _buildButton(String title, VoidCallback callback) {
return FlatButton(
child: Text(
title,
style: TextStyle(color: Colors.white),
),
onPressed: callback,
);
}
void _gotoLastPage() {
_pageController.animateToPage(
widget.pages.length - 1,
duration: const Duration(milliseconds: 600),
curve: Curves.ease,
);
}
void _gotoNextPage() {
_pageController.nextPage(
duration: const Duration(milliseconds: 600),
curve: Curves.easeInOut,
);
}
Widget _buildNavIndicator() {
final indicatorList = <Widget>[];
for (int i = 0; i < widget.pages.length; i++)
indicatorList.add(_buildIndicator(i == _currentPage));
return Row(children: indicatorList);
}
Widget _buildIndicator(bool isActive) {
return Padding(
padding: const EdgeInsets.all(5.0),
child: DecoratedBox(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isActive ? Colors.white : Colors.white30,
),
child: SizedBox(width: 8, height: 8),
),
);
}
}
Usage:
import 'package:flutter/material.dart';
import 'package:flutter_app_test3/my_intro_view.dart';
Future<void> main() async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MyIntroView(
pages: <Widget>[
Image.asset("assets/images/1.png", fit: BoxFit.cover),
Image.asset("assets/images/2.png", fit: BoxFit.cover),
Image.asset("assets/images/3.jpg", fit: BoxFit.cover),
],
onIntroCompleted: () {
print("Into is Completed");
//To the navigation stuff here
},
);
}
}
Ask me if you have any doubts in the comment
just try wrapping your Widget into Scaffold Widget and return
#override
Widget build(BuildContext context) {
return Scaffold(body:IntroSlider(
slides: this.slides,
onDonePress: this.onDonePress,
onSkipPress: this.onSkipPress,
));
}
I was facing the same issue and I fixed it by setting fit:Boxfit.fill for the image.
I have two animating menus. dropDownMenu is working fine, and I can set onPress events from this child to perform functions in the parent class using callbacks, for example:
class dropDownMenu extends StatefulWidget {
final Function() onPressed;
final String tooltip;
final IconData icon;
final _callback;
dropDownMenu({Key key, this.onPressed, this.tooltip, this.icon, #required void singlePlayerCallbacks(String callBackType) } ):
_callback = singlePlayerCallbacks, super(key: key);
#override
_dropDownMenuState createState() => _dropDownMenuState();
}
class _dropDownMenuState extends State<dropDownMenu>
with SingleTickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget> [
Column(
Container(
child: Opacity(
opacity: 0.0,
child: FloatingActionButton(
heroTag: null,
onPressed: isOpened == true? (){
widget?._callback('dice');
} : () {},
),
),
),
],
);
}
And then in the parent class:
class SinglePlayerMode extends StatefulWidget {
#override
SinglePlayerModeParentState createState() => SinglePlayerModeParentState();
}
class SinglePlayerModeParentState extends State<SinglePlayerMode> {
callBacks(String callBackType) {
switch(callBackType) {
case 'dice':
{
diceVisible = !diceVisible;
int rng = new Random().nextInt(19) + 1;
setState(() {
diceResult = rng;
});
}
}
break;
}
#override
Widget build(BuildContext context) {
child: Scaffold(
body: Container(
Padding(
padding: EdgeInsets.all(0.0),
child: dropDownMenu(
singlePlayerCallbacks: callBacks,
),
),
),
),
}
As a quick example, and this works perfectly fine.
What I need to do next is have another animated menu, called styleMenu, that animates when a button from dropDownMenu is pressed. This is where I am running into massive hurdles. I honestly don't mind HOW I get this done, I just need to get it done. This is what I am trying currently, without any success:
In dropDownMenu I have another button with a callback to the parent first:
Container(
child: Opacity(
opacity: 0.0,
child: FloatingActionButton(
heroTag: null,
onPressed: isOpened == true? (){
widget?._callback('theme');
} : () {},
),
),
),
Which triggers the callback function of the parent again, with a different switch case:
callBacks(String callBackType) {
case 'theme':
{
styleMenuState().animate();
}
break;
I obviously can't do this because it tells me that I am trying to animate a null object. Like I somehow have to instantiate styleMenu before I can call this function from here, but I don't know how to do this or even if it is possible.
My styleMenu class (extract):
class styleMenu extends StatefulWidget {
final Function() onPressed;
final String tooltip;
final IconData icon;
final _callback;
final VoidCallback animate;
styleMenu({this.onPressed, this.tooltip, this.animate, this.icon, #required void singlePlayerCallbacks(String callBackType) } ):
_callback = singlePlayerCallbacks;
#override
styleMenuState createState() => styleMenuState();
}
class styleMenuState extends State<styleMenu>
with SingleTickerProviderStateMixin {
bool isOpened = false;
AnimationController animationController;
Animation<Color> _buttonColor;
Animation<double> _animateIcon;
Animation<double> _translateButton;
Curve _curve = Curves.easeOut;
double _fabHeight = 52.0;
#override
initState() {
animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 600))
..addListener(() {
setState(() {});
});
_animateIcon =
Tween<double>(begin: 0.0, end: 1.0).animate(animationController);
_buttonColor = ColorTween(
begin: Colors.blue,
end: Colors.red,
).animate(CurvedAnimation(
parent: animationController,
curve: Interval(
0.0,
1.0,
curve: Curves.linear,
),
));
_translateButton = Tween<double>(
begin: 0.0,
end: _fabHeight,
).animate(CurvedAnimation(
parent: animationController,
curve: Interval(
0.0,
1.0,
curve: _curve,
),
));
super.initState();
}
#override
dispose() {
animationController.dispose();
super.dispose();
}
animate() {
if (!isOpened) {
styleMenuState().animationController.forward();
} else {
styleMenuState().animationController.reverse();
}
isOpened = !isOpened;
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget> [
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Stack(
children: <Widget>[
Transform(
transform: Matrix4.translationValues(
0,
_translateButton.value,
0,
),
child: blueTheme(),
),
Transform(
transform: Matrix4.translationValues(
0,
_translateButton.value * 2,
0,
),
child: greenTheme(),
),
Transform(
transform: Matrix4.translationValues(
0,
_translateButton.value * 3,
0,
),
child: redTheme(),
),
blackTheme(),
],
),
],
),
);
}
Again, I just need to be able to trigger the animate function from the styleMenu to pop this menu out by pressing a button inside the dropDownMenu and I just can't get my head around how to do this! I am sure there must be a simple way, but I am unable to find anything online.
Any pros out there?
I was just working on a similar problem. Using Streams, Provider with ChangeNotifiers, or inherited widgets are viable options. If you do want to simply trigger the child widget animations on setState called from the parent you can do that by using the didUpdateWidget in the child widget as shown below
#override
void didUpdateWidget(ImageHeartAnimation oldWidget) {
_controller.forward().orCancel;
super.didUpdateWidget(oldWidget);
}
I am trying to display all the phone gallery images myself by reading the external files directory and possibly every image that ends with jpg or png. I achieved that, but could not display all of them in a grid as due to their sizes or the no. of images, the app crashes. Code looks bit like this..
new GridView.count(
shrinkWrap: true,
physics: new ClampingScrollPhysics(),
crossAxisCount: 2,
// children: new List<Widget>.generate(_images.length, (index) {
// children: new List<Widget>.generate(allImages.length, (index) {
children: new List<Widget>.generate(_PhoneImages.length, (index) {
File imgFile = _phoneImageFiles[index];
thumbBytes = _phoneThumbBytes[index]; // assuming it got created!!!
// print('thumbbytes $thumbBytes');
print('phone image index: $index');
return new GridTile(
child: new GestureDetector(
child: new Stack(
children: [
new Card(
// color: Colors.blue.shade200,
color: Colors.white70,
child: new Center(
// child: new Text('tile $index'),
// child: new Image.asset(_images[index]),
/*
child: new CachedNetworkImage(
imageUrl: allImages[index].path,
// placeholder: new CircularProgressIndicator(),
errorWidget: new Icon(Icons.error),
)
*/
child: new Image.file(imgFile,
// child: new Image.memory(thumbBytes,
So I tried the imageresize library which tells me to do a heavy operation of resizing, that takes almost 20 minutes before I can show the thumbnails.
All I need is to read thumbnails from gallery like how the phone gallery displays. I don't need categorization. I need all and a link to their full version so that I can do something with them later on.
I think this might help multi_image_picker
e.g
import 'package:flutter/material.dart';
import 'package:multi_image_picker/asset.dart';
class AssetView extends StatefulWidget {
final int _index;
final Asset _asset;
AssetView(this._index, this._asset);
#override
State<StatefulWidget> createState() => AssetState(this._index, this._asset);
}
class AssetState extends State<AssetView> {
int _index = 0;
Asset _asset;
AssetState(this._index, this._asset);
#override
void initState() {
super.initState();
_loadImage();
}
void _loadImage() async {
await this._asset.requestThumbnail(300, 300); // here requesting thumbnail
setState(() {});
}
#override
Widget build(BuildContext context) {
if (null != this._asset.thumbData) {
return Image.memory(
this._asset.thumbData.buffer.asUint8List(),
fit: BoxFit.cover,
);
}
return Text(
'${this._index}',
style: Theme.of(context).textTheme.headline,
);
}
}
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:multi_image_picker/asset.dart';
import 'package:multi_image_picker/multi_image_picker.dart';
import 'asset_view.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Asset> images = List<Asset>();
String _error;
#override
void initState() {
super.initState();
}
Widget buildGridView() {
return GridView.count(
crossAxisCount: 3,
children: List.generate(images.length, (index) {
return AssetView(index, images[index]);
}),
);
}
Future<void> loadAssets() async {
setState(() {
images = List<Asset>();
});
List resultList;
String error;
try {
resultList = await MultiImagePicker.pickImages(
maxImages: 300,
);
} on PlatformException catch (e) {
error = e.message;
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
images = resultList;
if (error == null) _error = 'No Error Dectected';
});
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('Plugin example app'),
),
body: Column(
children: <Widget>[
Center(child: Text('Error: $_error')),
RaisedButton(
child: Text("Pick images"),
onPressed: loadAssets,
),
Expanded(
child: buildGridView(),
)
],
),
),
);
}
}
I'm looking for a way to implement Marquee style on a Text widget so that it automatically start scrolling when the text is overflowed from the screen.
Is there a way to do it.
I've tried all the decoration modes but cant seem to find a Marquee option there.
This widget is what I came up with, and I think it serves your needs:
class MarqueeWidget extends StatefulWidget {
final Widget child;
final Axis direction;
final Duration animationDuration, backDuration, pauseDuration;
const MarqueeWidget({
Key? key,
required this.child,
this.direction = Axis.horizontal,
this.animationDuration = const Duration(milliseconds: 6000),
this.backDuration = const Duration(milliseconds: 800),
this.pauseDuration = const Duration(milliseconds: 800),
}) : super(key: key);
#override
_MarqueeWidgetState createState() => _MarqueeWidgetState();
}
class _MarqueeWidgetState extends State<MarqueeWidget> {
late ScrollController scrollController;
#override
void initState() {
scrollController = ScrollController(initialScrollOffset: 50.0);
WidgetsBinding.instance.addPostFrameCallback(scroll);
super.initState();
}
#override
void dispose() {
scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
physics: NeverScrollableScrollPhysics(),
child: widget.child,
scrollDirection: widget.direction,
controller: scrollController,
);
}
void scroll(_) async {
while (scrollController.hasClients) {
await Future.delayed(widget.pauseDuration);
if (scrollController.hasClients) {
await scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: widget.animationDuration,
curve: Curves.ease,
);
}
await Future.delayed(widget.pauseDuration);
if (scrollController.hasClients) {
await scrollController.animateTo(
0.0,
duration: widget.backDuration,
curve: Curves.easeOut,
);
}
}
}
}
Its functionality should be pretty obvious. An example implementation would look like this:
class FlutterMarqueeText extends StatefulWidget {
const FlutterMarqueeText({Key? key}) : super(key: key);
#override
_FlutterMarqueeTextState createState() => _FlutterMarqueeTextState();
}
class _FlutterMarqueeTextState extends State<FlutterMarqueeText> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Flutter Marquee Text"),
),
body: const Center(
child: SizedBox(
width: 200.0,
child: MarqueeWidget(
direction: Axis.horizontal,
child: Text("This text is to long to be shown in just one line"),
),
),
),
);
}
}
Use The Marquee package.
and if you get any 'hasSize' Error or 'Incorrect uses of Parent Widget' Error..
Just wrap Marquee widget with container and give height and width of that container.