I have a background with some white lines animated from down to up, generated procedurally. Then, I have a ShaderMask who start to fade out the content at 0.5 and finish it at 0.7. The lines spawn on the bottom screen, goes up in around 4 seconds then fade out with the ShaderMask.
My problem is, for every lines, a 1 pixel right or left border will be still visible during and after the fading.
Here is a screenshot for better understanding: (zoom in if you don't see it)
As you can guess, the other 1 pixel column you can see are other lines faded away.
Here is the code (I tried to simplify it as much as possible).
import 'package:flutter/material.dart';
import 'dart:math';
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Stack(children: [RandomLines()]),
);
}
}
class RandomLines extends StatefulWidget {
RandomLines({Key key}) : super(key: key);
@override
_RandomLinesState createState() => _RandomLinesState();
}
class Line {
double height;
AnimationController controller;
Line({@required this.height, @required this.controller});
}
class _RandomLinesState extends State<RandomLines>
with TickerProviderStateMixin {
List<Line> _lines = [];
AnimationController _tick;
Random _rand;
@override
void initState() {
_rand = Random();
for (int i = 0; i < 10; i++) {
_lines.add(Line(height: 0, controller: null));
}
_tick = AnimationController(vsync: this, duration: Duration(seconds: 5));
_tick
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_tick.reverse();
} else if (status == AnimationStatus.dismissed) {
_tick.forward();
}
});
_tick.addListener(() {
setState(() {});
});
_tick.forward();
super.initState();
}
void reset() {
_lines.forEach((line) {
line.height = 0;
if (line.controller != null) {
line.controller.dispose();
line.controller = null;
}
});
}
@override
void dispose() {
_lines.forEach((line) {
if (line.controller != null) {
line.controller.dispose();
}
});
_tick.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
this._generateLines();
return Positioned(
width: MediaQuery.of(context).size.width,
bottom: -200,
child: Container(
padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: _lines.map((line) {
if (line.controller == null) return Container(width: 28);
return _buildLine(line.height, line.controller.value);
}).toList())));
}
void _generateLines() {
for (int i = 0; i < _lines.length; i++) {
if (_lines[i].controller == null) {
int randomNumb = _rand.nextInt(200);
if (randomNumb == 0) {
_lines[i].height = 50 + _rand.nextInt(200 - 50).toDouble();
_lines[i].controller = AnimationController(
vsync: this,
duration: Duration(seconds: 5),
lowerBound: 0,
upperBound:
(MediaQuery.of(context).size.height + 200).roundToDouble());
_lines[i].controller
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_lines[i].height = 0;
_lines[i].controller.dispose();
_lines[i].controller = null;
}
});
_lines[i].controller.addListener(() {
setState(() {});
});
_lines[i].controller.forward();
}
}
}
}
Widget _buildLine(double height, double offset) {
return Container(
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
child: ShaderMask(
shaderCallback: (rect) {
return LinearGradient(
stops: [0, 0.5, 0.7],
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [Colors.white, Colors.white, Colors.transparent],
).createShader(Rect.fromLTRB(0, 0, rect.width, rect.height));
},
blendMode: BlendMode.dstIn,
child: Container(
width: 8,
height: MediaQuery.of(context).size.height + 200,
child: Wrap(runAlignment: WrapAlignment.end, children: [
Container(
height: height,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.8),
borderRadius: BorderRadius.circular(10))),
SizedBox(height: offset),
]))));
}
}
Thanks you for reading!
