Get widget position in a CustomScrollView - flutter

How to get the widget position when it is in a CustomScrollView > SliverToBoxAdapter. My widget ZoomItem zoom by creating a new widget with overlay, but when I scroll up or down, or when I change the size of the window the overlay appear aside. Why "final Offset offset = renderBox.localToGlobal(Offset.zero);" still return the same position? Is there a way to update the renderBox? Why there is no update of the offset in my code?
ZoomItem:
class ZoomItem extends ConsumerStatefulWidget {
final Widget child;
const ZoomItem({Key? key, required this.child}) : super(key: key);
#override
_ZoomItemState createState() => _ZoomItemState();
}
class _ZoomItemState extends ConsumerState<ZoomItem>
with SingleTickerProviderStateMixin {
OverlayEntry? overlayEntry;
late bool hasReachedTop;
late double height, width, xPosition, yPosition;
late AnimationController _animationController;
late Animation<double> animTween;
#override
void initState() {
super.initState();
hasReachedTop = true;
_animationController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 400));
_animationController.addListener(() {
if (_animationController.isDismissed) {
closeDropDown();
}
});
animTween = Tween<double>(begin: 1.0, end: 2.0).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut));
final scrollController = ref.read(scrollControllerProvider);
scrollController.addListener(() {
if (scrollController.position.atEdge) {
if (scrollController.position.pixels == 0) {
hasReachedTop = true;
} else {
hasReachedTop = false;
}
}
});
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (onEnter) {
insertOverlay(context);
},
onExit: (onExit) {
if (!_animationController.isAnimating) {
closeDropDown();
}
},
child: widget.child);
}
void insertOverlay(BuildContext context) {
closeDropDown();
findDropdownData(context);
overlayEntry = _createOverlay();
Overlay.of(context)!.insert(overlayEntry!);
}
OverlayEntry _createOverlay() {
return OverlayEntry(builder: (context) {
return Stack(
alignment: Alignment.center,
children: [
Positioned(
top: yPosition,
left: xPosition,
height: height,
width: width,
child: MouseRegion(
onEnter: (onEnter) {
_animationController.forward();
},
onExit: (onExit) {
_animationController.reverse();
},
child:
ScaleTransition(scale: animTween, child: widget.child))),
],
);
});
}
void findDropdownData(BuildContext context) {
final RenderBox renderBox = context.findRenderObject()! as RenderBox;
height = renderBox.size.height;
width = renderBox.size.width;
final Offset offset = renderBox.localToGlobal(Offset.zero);
xPosition = offset.dx - kPaddingHorizontal;
final top = hasReachedTop ? kSliverTop : 0;
yPosition = offset.dy - top;
}
void closeDropDown() {
if (overlayEntry != null) {
overlayEntry!.remove();
overlayEntry = null;
}
}
}
GridViewStaggered:
class GridViewStaggered extends StatelessWidget {
final ResponsiveLayoutSize currentSize;
final List<Item> listItem;
const GridViewStaggered({Key? key, required this.currentSize, required this.listItem}) : super(key: key);
#override
Widget build(BuildContext context) {
return StaggeredGrid.count(
children: [
for(int i = 0 ; i < listItem.length; i++)
StaggeredGridTile.count(
crossAxisCellCount: i % 2 == 0 ? 2:1,
mainAxisCellCount: i % 2 == 0 ? 2:1,
child: i.isEven? ItemCard(
item: listItem[i],
):ZoomItem(
child: ItemCard(
item: listItem[i],
),
),
)
],
crossAxisCount: 4);
}
}

Related

Flutter - AnimatedScale not animating when widget update

I have a rating widget with 5 stars.
I can give a rating by dragging.
I want to animate the star with the star rating so that the scale gets bigger and the star without the star rating gets smaller again.
Therefore, I made sure that the star with the star rating is created using the AnimatedScale.
However, as you can see in the attached gif, the size of the star changes but it is not animated.
I gave the Duration value of the AnimatedScale property to 2 seconds, but it is being changed as soon as it is graded.
How can the stars grow and shrink smoothly?
Here is my code
import 'package:flutter/material.dart';
typedef void RatingChangeCallback(double rating);
class SmoothStarRating extends StatelessWidget {
final int starCount;
final double rating;
final RatingChangeCallback onRatingChanged;
final Color? color;
final Color? borderColor;
final double size;
final double spacing;
SmoothStarRating({
this.starCount = 5,
this.spacing = 0.0,
this.rating = 0.0,
required this.onRatingChanged,
this.color,
this.borderColor,
this.size = 25,
});
Widget getIcon(int starIndex) {
if (starIndex >= rating) {
return Icon(Icons.star_border, color: borderColor, size: size);
} else if (starIndex > rating - 0.5 && starIndex < rating) {
return AnimatedScale(
duration: Duration(seconds: 2),
scale: 1.1,
child: Icon(Icons.star_half, color: color, size: size),
);
} else {
return AnimatedScale(
duration: Duration(seconds: 2),
scale: 1.1,
child: Icon(Icons.star, color: color, size: size),
);
}
}
Widget buildStar(BuildContext context, int starIndex) {
return GestureDetector(
onTap: () {
onRatingChanged(starIndex + 1.0);
},
onHorizontalDragUpdate: (dragDetails) {
RenderBox box = context.findRenderObject() as RenderBox;
var _pos = box.globalToLocal(dragDetails.globalPosition);
var newRating = _pos.dx / size;
if (newRating > starCount) newRating = starCount.toDouble();
if (newRating < 0) newRating = 0.0;
onRatingChanged(newRating);
},
child: getIcon(starIndex),
);
}
#override
Widget build(BuildContext context) {
return Wrap(
alignment: WrapAlignment.start,
spacing: spacing,
children: List.generate(starCount, (starIndex) => buildStar(context, starIndex)),
);
}
}
+ ADDITIONAL
I tried implementing it using animation_controller, but the results are the same.
import 'package:flutter/material.dart';
typedef void RatingChangeCallback(double rating);
class SmoothStarRating extends StatefulWidget {
final int starCount;
final double rating;
final RatingChangeCallback onRatingChanged;
final Color? color;
final Color? borderColor;
final double size;
final double spacing;
SmoothStarRating({
this.starCount = 5,
this.spacing = 0.0,
this.rating = 0.0,
required this.onRatingChanged,
this.color,
this.borderColor,
this.size = 25,
});
#override
State<SmoothStarRating> createState() => _SmoothStarRatingState();
}
class _SmoothStarRatingState extends State<SmoothStarRating> with TickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
)..forward();
late final Animation<double> _animation = Tween(begin: 1.0, end: 1.1).animate(_controller);
#override
void dispose() {
_controller.dispose();
super.dispose();
}
Widget getIcon(int starIndex) {
if (starIndex >= widget.rating) {
return Icon(Icons.star_border, color: widget.borderColor, size: widget.size);
} else if (starIndex > widget.rating - 0.5 && starIndex < widget.rating) {
return Icon(Icons.star_half, color: widget.color, size: widget.size);
} else {
return ScaleTransition(
scale: _animation,
child: Icon(Icons.star, color: widget.color, size: widget.size),
);
}
}
Widget buildStar(BuildContext context, int starIndex) {
return GestureDetector(
onTap: () {
widget.onRatingChanged(starIndex + 1.0);
},
onHorizontalDragUpdate: (dragDetails) {
RenderBox box = context.findRenderObject() as RenderBox;
var _pos = box.globalToLocal(dragDetails.globalPosition);
var newRating = _pos.dx / widget.size;
if (newRating > widget.starCount) newRating = widget.starCount.toDouble();
if (newRating < 0) newRating = 0.0;
widget.onRatingChanged(newRating);
},
child: getIcon(starIndex),
);
}
#override
Widget build(BuildContext context) {
return Wrap(
alignment: WrapAlignment.start,
spacing: widget.spacing,
children: List.generate(widget.starCount, (starIndex) => buildStar(context, starIndex)),
);
}
}

How to use BLoC pattern via flutter_bloc library?

I'm writting a small tamagotchi app using Flutter and now I'm learning how to use flutter_bloc lib.
When user tap on a pet image on a screen, it must redraw a CircularPercentIndicator widget, but it won't work. I'm trying to connect a view with a bloc using a BlocBuilder and BlocProvider classes, but it did not help.
After tapping a pet widget, animation is forwarded, but the state of saturationCount and CircularPercentIndicator hasn't been updated.
Here is my BLoC for pet feeding:
class PetFeedingBloc extends Bloc<SaturationEvent, SaturationState> {
PetFeedingBloc()
: super(const SaturationState(saturationCount: 40.0)) {
on<SaturationSmallIncrementEvent>((event, emit) => state.saturationCount + 15.0);
on<SaturationBigIncrementEvent>((event, emit) => state.saturationCount + 55.0);
on<SaturationDecrementEvent>((event, emit) => state.saturationCount - 2.0);
}
}
In SaturationBarWidget class I'm trying to connect a percent indicator in a widget with a BLoC, but it does not work. Here it is:
class SaturationBarWidget extends StatefulWidget {
const SaturationBarWidget({Key? key}) : super(key: key);
#override
State<SaturationBarWidget> createState() => SaturationBarWidgetState();
}
class SaturationBarWidgetState extends State<SaturationBarWidget> {
#override
void initState() {
Timer? timer;
timer = Timer.periodic(const Duration(milliseconds: 3000), (_) {
setState(() {
context.read<PetFeedingBloc>().add(SaturationDecrementEvent());
if (context.read<PetFeedingBloc>().state.saturationCount <= 0) {
timer?.cancel();
}
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<PetFeedingBloc, SaturationState>(builder: (context, state){
return CircularPercentIndicator(
radius: 50.0,
lineWidth: 20.0,
animateFromLastPercent: true,
percent: context.read<PetFeedingBloc>().state.saturationCount / 100,
center: const Icon(
Icons.emoji_emotions_outlined,
size: 50.0,
),
backgroundColor: Colors.blueGrey,
progressColor: Colors.blue,
);
});
}
}
And here it is my PetWidget class with image that need to be tapped:
class PetWidget extends StatefulWidget {
const PetWidget({Key? key}) : super(key: key);
#override
State<PetWidget> createState() => PetWidgetState();
}
class PetWidgetState extends State<PetWidget> with TickerProviderStateMixin {
late Animation<Offset> _animation;
late AnimationController _animationController;
static GlobalKey<SaturationBarWidgetState> key = GlobalKey();
bool reverse = true;
Image cat = Image.asset('images/cat.png');
#override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: const Duration(seconds: 4));
_animation = Tween<Offset>(begin: Offset.zero, end: const Offset(1, 0))
.animate(CurvedAnimation(
parent: _animationController, curve: Curves.elasticIn));
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_animationController.reverse();
}
});
//_animationController.forward();
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Center(child:
BlocBuilder<PetFeedingBloc, SaturationState>(builder: (context, state) {
return Center(
child: SizedBox(
width: 300,
height: 400,
child: SlideTransition(
position: _animation,
child: GestureDetector(
child: cat,
onDoubleTap: () {
context.read<PetFeedingBloc>().add(SaturationBigIncrementEvent());
_animationController.forward();
},
onTap: () {
context.read<PetFeedingBloc>().add(SaturationSmallIncrementEvent());
_animationController.forward();
},
),
),
)
);
})
);
}
}
I think you have to call the emit method in you
PetFeedingBloc
class PetFeedingBloc extends Bloc<SaturationEvent, SaturationState> {
PetFeedingBloc()
: super(const SaturationState(saturationCount: 40.0)) {
on<SaturationSmallIncrementEvent>((event, emit) => emit(SaturationState(saturationCount: state.saturationCount + 15.0)) );
...
}
}

Update CustomPaint drawing

I have a Problem with the CustomPainter Widget. I want to draw a PieChart which works fine, then I added a Variable which draws the Chart to until it reached this angle. Now I want to animate it, I used the Future.delayed function and in there with setState I wanted to update the variable but that doesn't work unfortunately.
I am developing for the web. Thanks for helping!
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:stats/data/listLanguages.dart';
import 'painter/pieChartPainter.dart';
class Chart extends StatefulWidget {
ListLanguages listLanguages;
Chart({ListLanguages listLanguages}) {
if (listLanguages == null) {
listLanguages = new ListLanguages();
}
this.listLanguages = listLanguages;
}
#override
_ChartState createState() => _ChartState();
}
class _ChartState extends State<Chart> {
#override
Widget build(BuildContext context) {
List angles = widget.listLanguages.calcCounts();
int angle = 0;
Future.delayed(new Duration(seconds: 2), (){
setState(() {
angle = 360;
print("test");
});
});
return Column(
children: [
Spacer(flex: 2),
Row(
children: [
Spacer(),
CustomPaint(
size: Size.square(400),
painter: PieChartPainter(
angles: angles,
colors: new List()
..add(Colors.green)
..add(Colors.blue)
..add(Colors.brown)
..add(Colors.pink)
..add(Colors.orange)
..add(Colors.grey.shade700),
angle: angle,
),
),
Spacer(flex: 10),
],
),
Spacer(flex: 3),
],
);
}
}
import 'package:flutter/material.dart';
import 'package:vector_math/vector_math.dart' as vm;
class PieChartPainter extends CustomPainter {
List angles, colors;
int angle;
PieChartPainter(
{#required List angles, #required List colors, int angle: 360}) {
this.angles = angles;
this.colors = colors;
this.angle = angle;
}
#override
void paint(Canvas canvas, Size size) {
Paint p = new Paint();
double start = -90;
double tmp = 0;
for (int i = 0; i < angles.length; i++) {
if (i < 5) {
p.color = colors[i];
} else {
p.color = colors[5];
}
if (tmp + angles[i] < angle) {
canvas.drawArc(Rect.fromLTRB(0, 0, size.width, size.height),
vm.radians(start), vm.radians(angles[i]), true, p);
start = start + angles[i];
tmp = tmp + angles[i];
} else {
double x = angle - tmp;
canvas.drawArc(Rect.fromLTRB(0, 0, size.width, size.height),
vm.radians(start), vm.radians(x), true, p);
return;
}
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
this is the complete code I have to create the Pie Chart
You can copy paste run full code below
In your case, to work with Future.delayed, you can move logic from build to initState and use addPostFrameCallback
working demo change angle in 2, 4, 6 seconds and angle is 150, 250, 360
code snippet
class _ChartState extends State<Chart> {
int angle = 0;
List angles;
#override
void initState() {
angles = widget.listLanguages.calcCounts();
WidgetsBinding.instance.addPostFrameCallback((_) {
Future.delayed(Duration(seconds: 2), () {
setState(() {
angle = 150;
});
});
Future.delayed(Duration(seconds: 4), () {
setState(() {
angle = 250;
});
});
Future.delayed(Duration(seconds: 6), () {
setState(() {
angle = 360;
});
});
});
working demo
full code
import 'package:flutter/material.dart';
import 'package:vector_math/vector_math.dart' as vm;
class ListLanguages {
List calcCounts() {
return [10.0, 20.0, 100.0, 150.0, 250.0, 300.0];
}
}
class Chart extends StatefulWidget {
ListLanguages listLanguages;
Chart({ListLanguages listLanguages}) {
if (listLanguages == null) {
listLanguages = ListLanguages();
}
this.listLanguages = listLanguages;
}
#override
_ChartState createState() => _ChartState();
}
class _ChartState extends State<Chart> {
int angle = 0;
List angles;
#override
void initState() {
angles = widget.listLanguages.calcCounts();
WidgetsBinding.instance.addPostFrameCallback((_) {
Future.delayed(Duration(seconds: 2), () {
print("delay");
setState(() {
angle = 150;
print("test");
});
});
Future.delayed(Duration(seconds: 4), () {
print("delay");
setState(() {
angle = 250;
print("test");
});
});
Future.delayed(Duration(seconds: 6), () {
print("delay");
setState(() {
angle = 360;
print("test");
});
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Spacer(flex: 2),
Row(
children: [
Spacer(),
CustomPaint(
size: Size.square(400),
painter: PieChartPainter(
angles: angles,
colors: List()
..add(Colors.green)
..add(Colors.blue)
..add(Colors.brown)
..add(Colors.pink)
..add(Colors.orange)
..add(Colors.grey.shade700),
angle: angle,
),
),
Spacer(flex: 10),
],
),
Spacer(flex: 3),
],
);
}
}
class PieChartPainter extends CustomPainter {
List angles, colors;
int angle;
PieChartPainter(
{#required List angles, #required List colors, int angle: 360}) {
this.angles = angles;
this.colors = colors;
this.angle = angle;
}
#override
void paint(Canvas canvas, Size size) {
Paint p = Paint();
double start = -90;
double tmp = 0;
for (int i = 0; i < angles.length; i++) {
if (i < 5) {
p.color = colors[i];
} else {
p.color = colors[5];
}
if (tmp + angles[i] < angle) {
canvas.drawArc(Rect.fromLTRB(0, 0, size.width, size.height),
vm.radians(start), vm.radians(angles[i]), true, p);
start = start + angles[i];
tmp = tmp + angles[i];
} else {
double x = angle - tmp;
canvas.drawArc(Rect.fromLTRB(0, 0, size.width, size.height),
vm.radians(start), vm.radians(x), true, p);
return;
}
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Chart(
listLanguages: ListLanguages(),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
I can not use your code so that I can run it (since it's only a small part) but what you need is:
Define an animation and animation controller in your state
Surround your CustomPainter with an "AnimatedBuilder" which will use this animation and will pass the value between 0 to 360 to your CustomPainter in 2 seconds.
Below is an example with comments (which you will have to take parts from and put in to your widget).
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
// NOTE: You need to add "SingleTickerProviderStateMixin" for animation to work
class _TestState extends State<Test> with SingleTickerProviderStateMixin {
Animation _animation; // Stores animation
AnimationController _controller; // Stores controller
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
); // Create a 2 second duration controller
_animation = IntTween(begin: 0, end: 360)
.animate(_controller); // Create the animation using controller with a tween from 0 to 360
WidgetsBinding.instance.addPostFrameCallback((_) {
_controller.forward(); // Start the animation when widget is displayed
});
}
#override
void dispose() {
_controller.dispose(); // Don't forget to dispose your controller
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder( // AnimatedBuilder using the animation
animation: _animation,
builder: (context, _){
return CustomPaint(
size: Size.square(400),
painter: PieChartPainter(
angles: angles,
colors: new List()
..add(Colors.green)
..add(Colors.blue)
..add(Colors.brown)
..add(Colors.pink)
..add(Colors.orange)
..add(Colors.grey.shade700),
angle: _animation.value, // Pass _animation.value (0 to 360) as your angle
),
);
},
);
}
}

Different behavior when nesting Transform.scale and Transform.translate

I want to implement scale and translate behavior. I write it in _TransformScaleAndTranslate().
But I got different behavior between
[Transform.translate outside Transform.rotate]
vs
[Transform.rotate outside Transform.translate]
The result shows that if I translate after scale, [Transform.translate outside Transform.rotate] can't translate follow my pointer correctly.
Is there something wrong? or anything I didn't get it?
import 'package:flutter/material.dart';
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Test1'),
),
body: const _BodyWidget(),
);
}
}
class _BodyWidget extends StatefulWidget {
const _BodyWidget({
Key key,
}) : super(key: key);
#override
__BodyWidgetState createState() => __BodyWidgetState();
}
class __BodyWidgetState extends State<_BodyWidget> {
Offset _startFocalPoint = Offset.zero;
Offset _lastOffset = Offset.zero;
Offset _currentOffset = Offset.zero;
double _lastScale = 1.0;
double _currentScale = 1.0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onScaleStart: _onScaleStart,
onScaleUpdate: _onScaleUpdate,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
_TransformScaleAndTranslate(_currentOffset, _currentScale),
],
),
);
}
void _onScaleStart(ScaleStartDetails details) {
_startFocalPoint = details.focalPoint;
_lastOffset = _currentOffset;
_lastScale = _currentScale;
}
void _onScaleUpdate(ScaleUpdateDetails details) {
if (details.scale != 1.0) {
double currentScale = _lastScale * details.scale;
if (currentScale < 0.5) {
currentScale = 0.5;
}
setState(() {
_currentScale = currentScale;
});
// The place I calculate for translate
} else if (details.scale == 1.0) {
Offset currentOffset =
_lastOffset + (details.focalPoint - _startFocalPoint) / _lastScale;
setState(() {
_currentOffset = currentOffset;
});
}
}
}
class _TransformScaleAndTranslate extends StatelessWidget {
final Offset _currentOffset;
final double _currentScale;
const _TransformScaleAndTranslate(
this._currentOffset,
this._currentScale, {
Key key,
}) : super(key: key);
//Put Transform.translate or Transform.scale outside got different behavior when translate after scaled.
#override
Widget build(BuildContext context) {
return Transform.translate(
offset: _currentOffset,
child: Transform.scale(
scale: _currentScale,
child: Image.asset(
'assets/images/elephant.jpg',
fit: BoxFit.contain,
),
),
);
}
}

UI works always - but error when checking widget sizes

I want to have a text in a given SizedBox (cannot change). But this text can vary from time to time.
So I coded a class, which displays me the texts centred when it fits in the given size, but scrolls over it when it is too big. But in my code, there is a tiny fraction of a second with an error.
It has to be dynamically displayed as needed.
How my code works:
I build by default a centred text. This is also the point, where I get an overflow error by x pixels.
I check then in an asynchronous method the text size and the given size.
If the text widget is too long, I start scrolling it (AlwaysScrollingText).
The question is now, how do I get the text size without an overflow xxx pixels.
import 'dart:async';
import 'package:flutter/material.dart';
class ScrollingText extends StatefulWidget {
final String text;
final TextStyle style;
final Axis scrollAxis;
final double ratioOfBlankToScreen;
final double width;
ScrollingText({
#required this.text,
#required this.width,
this.style,
this.scrollAxis: Axis.horizontal,
this.ratioOfBlankToScreen: 0.25,
}) : assert(text != null, width != null);
#override
State<StatefulWidget> createState() {
return ScrollingTextState();
}
}
class ScrollingTextState extends State<ScrollingText> {
bool scroll = false;
GlobalKey _sizeKey = GlobalKey();
#override
void didUpdateWidget(ScrollingText oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.text != widget.text) scroll = false;
}
#override
Widget build(BuildContext context) {
checkScroll();
return scroll
? SizedBox(
width: widget.width,
child: AlwaysScrollingText(
text: widget.text,
style: widget.style,
))
: getText();
}
Widget getText() {
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(widget.text, style: widget.style, maxLines: 1, key: _sizeKey)
],
),
);
}
checkScroll() async {
await Future.delayed(Duration(milliseconds: 500));
if (_sizeKey.currentContext == null) return;
double _textWidth =
_sizeKey.currentContext.findRenderObject().paintBounds.size.width;
bool scroll = _textWidth > widget.width;
print('$_textWidth > ${widget.width}');
if (scroll != this.scroll)
setState(() {
this.scroll = scroll;
});
}
}
class AlwaysScrollingText extends StatefulWidget {
final String text;
final TextStyle style;
final double ratioOfBlankToScreen;
AlwaysScrollingText({
#required this.text,
this.style,
this.ratioOfBlankToScreen: 0.25,
}) : assert(text != null,);
#override
_AlwaysScrollingTextState createState() => _AlwaysScrollingTextState();
}
class _AlwaysScrollingTextState extends State<AlwaysScrollingText>
with SingleTickerProviderStateMixin {
ScrollController scrollController;
double screenWidth;
double screenHeight;
double position = 0.0;
Timer timer;
final double _moveDistance = 3.0;
final int _timerRest = 100;
GlobalKey _key = GlobalKey();
#override
void initState() {
super.initState();
scrollController = ScrollController();
WidgetsBinding.instance.addPostFrameCallback((callback) {
startTimer();
});
}
void startTimer() {
if (_key.currentContext != null) {
double widgetWidth = getSizeFromKey(_key).width;
timer = Timer.periodic(Duration(milliseconds: _timerRest), (timer) {
double maxScrollExtent = scrollController.position.maxScrollExtent;
double pixels = scrollController.position.pixels;
if (pixels + _moveDistance >= maxScrollExtent) {
position = (maxScrollExtent -
screenWidth * widget.ratioOfBlankToScreen +
widgetWidth) /
2 -
widgetWidth +
pixels -
maxScrollExtent;
scrollController.jumpTo(position);
}
position += _moveDistance;
scrollController.animateTo(position,
duration: Duration(milliseconds: _timerRest), curve: Curves.linear);
});
}
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
screenWidth = MediaQuery.of(context).size.width;
screenHeight = MediaQuery.of(context).size.height;
}
Widget getBothEndsChild() {
return Center(
child: Text(
widget.text,
style: widget.style,
maxLines: 1,
));
}
Widget getCenterChild() {
return Container(width: screenWidth * widget.ratioOfBlankToScreen);
}
#override
void dispose() {
super.dispose();
if (timer != null) {
timer.cancel();
}
}
#override
Widget build(BuildContext context) {
return ListView(
key: _key,
scrollDirection: Axis.horizontal,
controller: scrollController,
physics: NeverScrollableScrollPhysics(),
children: <Widget>[
getCenterChild(),
getBothEndsChild(),
],
);
}
Size getSizeFromKey(GlobalKey key) =>
key.currentContext?.findRenderObject()?.paintBounds?.size;
}
Actual result:
https://i.imgur.com/FFewRtB.gif (not enough reputation to post images :c)
You can put your text field inside the expanded widget.Here is link to class.
expanded widget.
And here is code.
Expanded(
child: Text("hello"),
);
The bug was fixed with this little trick:
Replaced bool scroll = _textWidth > widget.width; with bool scroll = _textWidth >= widget.width;
Removed the Row widget in the getText method