24

has anyone come across something like fadingEdgeLength in Android for Flutter so that when you scroll up items start fading into the top of the screen?

Below is my interface built up of the Widgets.

If it helps these are the properties I'm referring to:

android:fadingEdgeLength="10dp"

android:requiresFadingEdge="horizontal">

@override
Widget build(BuildContext context) {
return Scaffold(
  appBar: AppBar(
    title: Text('CMS Users'),
  ),
  body: ListView.builder(
      padding: EdgeInsets.only(top: 20.0, left: 4.0),
      itemExtent: 70.0,
      itemCount: data == null ? 0 : data.length,
      itemBuilder: (BuildContext context, int index) {
        return Card(
          elevation: 10.0,
          child: InkWell(
            onTap: () {
              Navigator.push(
                  context,
                  new MaterialPageRoute(
                    builder: (BuildContext context) =>
                        new PeopleDetails("Profile Page", profiles[index]),
                  ));
            },
            child: ListTile(
              leading: CircleAvatar(
                child: Text(profiles[index].getInitials()),
                backgroundColor: Colors.deepPurple,
                radius: 30.0,
              ),
              title: Text(
                  data[index]["firstname"] + "." + data[index]["lastname"]),
              subtitle: Text(
                  data[index]["email"] + "\n" + data[index]["phonenumber"]),
            ),
          ),
        );
      }),
   );
 }
}
Dan530c
  • 377
  • 1
  • 4
  • 13

6 Answers6

46

As others have mentioned, you can put the ListView under a ShaderMask, but with minor extra parameterizations you can get much better results - at least if you want to achieve what I wanted.

Optionally you can set the [stops] list for the LinearGradient:

The [stops] list, if specified, must have the same length as [colors]. It specifies fractions of the vector from start to end, between 0.0 and 1.0, for each color.

Plus: There are blend modes, where the color channels of the source are ignored, only the opacity has an effect. BlendMode.dstOut is also such in the example below. As you can see in the screenshot, the purple color is not used concretely, only for the fractions of the vector.

You can play with the different [blendMode] settings, there are quite a few of them.

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: FadingListViewWidget(),
      ),
    ),
  );
}

class FadingListViewWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        height: 320,
        child: ShaderMask(
          shaderCallback: (Rect rect) {
            return LinearGradient(
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
              colors: [Colors.purple, Colors.transparent, Colors.transparent, Colors.purple],
              stops: [0.0, 0.1, 0.9, 1.0], // 10% purple, 80% transparent, 10% purple
            ).createShader(rect);
          },
          blendMode: BlendMode.dstOut,
          child: ListView.builder(
            itemCount: 100,
            itemBuilder: (BuildContext context, int index) {
              return Card(
                color: Colors.orangeAccent,
                child: ListTile(
                  title: Text('test test test test test test'),
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

Flutter FadingListViewWidget

  • 3
    to avoid redundancy in your (symmetric) LinearGradient: CHANGE: "end: Alignment.bottomCenter" -> "end: Alignment.center" ADD: "tileMode: TileMode.mirror," (this is key!) DELETE: last two entries of "colors" and "stops" AND take the second entry of "stops" times two :) – Carlit0 Mar 13 '21 at 02:18
29

You could apply a ShaderMask on top of ListView and use BlendMode to get what you want.

Widget animationsList() {
    return Expanded(
      child: ShaderMask(
        shaderCallback: (Rect bounds) {
            return LinearGradient(
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
              colors: <Color>[Colors.transparent,Colors.red],
            ).createShader(bounds);
        },
        child: Container(height: 200.0, width: 200.0, color: Colors.blue,),
        blendMode: BlendMode.dstATop,
     ),
);
itsmysterybox
  • 2,748
  • 3
  • 21
  • 26
Martin Ptáček
  • 297
  • 3
  • 6
  • 3
    This is the best answere because, unlike when overlaying a Container with a Stack, this solution doesn't block the ListView's GestureDetector at the area where the ListView-children are fading out. – Lucas Aschenbach Apr 01 '20 at 18:25
  • 1
    This is a great answer, however, thanks to the way flutter works with redraws and builds, if you want the shading to go away once the user starts scrolling, you have to force a getState(...), and then nullify this whole structure after calculating if the user has scrolled, etc. Great idea. I hacked around it for this quick patch by simply adding a few linefeeds at the end of my text. I'll circle back to a more elegant solution later; I have to get this stupid patch out. Thanks for this, @Martin – ChrisH Jan 18 '21 at 13:37
  • Is there any way to apply this to `SliverList`? – ANDYNVT Oct 05 '21 at 01:53
11

I had similar request so I created a library for this task. You can find it here: fading_edge_scrollview

To use it you need to add a ScrollController to your ListView and then pass this ListView as child to FadingEdgeScrollView.fromScrollView constructor

Mikhail Ponkin
  • 2,563
  • 2
  • 19
  • 19
5

Wrap the Listview with Stack, add the Listview as the first child, the second is Positioned Container with LinearGradient. Sample from my code:

Stack:

return Stack(
                    children: <Widget>[
                      ListView(
                        scrollDirection: Axis.horizontal,
                        children: _myListOrderByDate,
                      ),
                      FadeEndListview(),
                    ],
                  );

The overlay class:

class FadeEndListview extends StatelessWidget {
  const FadeEndListview({
    Key key,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Positioned(
      right: 0,
      width: 8.0,
      height: kYoutubeThumbnailsHeight,
      child: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.centerRight,
            end: Alignment.centerLeft,
            stops: [0.0, 1.0],
            colors: [
                Theme.of(context).scaffoldBackgroundColor,
                Theme.of(context).scaffoldBackgroundColor.withOpacity(0.0),
            ],
          ),
        ),
      ),
    );
  }
}

And it will look something like this:

enter image description here

Yariv.A
  • 71
  • 1
  • 5
2

I combined the answer from Krisztián Ódor and a result from ChatGPT to get a widget that fades the content of a ListView as the user scrolls through the content. When the ListView is on either side, on this side there is no fading. I have done this because in the previous version part of my content was not visible due to the fading. The widget works for both scroll directions and handles reverse: true. To get more or less fading adjust the stops in the LinearGradient.

import 'package:flutter/material.dart';

class FadingListView extends StatefulWidget {
  const FadingListView({required this.child, super.key});

  final ListView child;

  @override
  State<FadingListView> createState() => _FadingListViewState();
}

class _FadingListViewState extends State<FadingListView> {
  double _stopStart = 0;
  double _stopEnd = 1;

  @override
  Widget build(BuildContext context) {
    return NotificationListener<ScrollNotification>(
      onNotification: (scrollNotification) {
        setState(() {
          _stopStart = scrollNotification.metrics.pixels / 10;
          _stopEnd = (scrollNotification.metrics.maxScrollExtent -
                  scrollNotification.metrics.pixels) /
              10;

          _stopStart = _stopStart.clamp(0.0, 1.0);
          _stopEnd = _stopEnd.clamp(0.0, 1.0);
        });
        return true;
      },
      child: ShaderMask(
        shaderCallback: (Rect rect) {
          return LinearGradient(
            begin: widget.child.scrollDirection == Axis.horizontal
                ? Alignment.centerLeft
                : Alignment.topCenter,
            end: widget.child.scrollDirection == Axis.horizontal
                ? Alignment.centerRight
                : Alignment.bottomCenter,
            colors: const [
              Colors.black,
              Colors.transparent,
              Colors.transparent,
              Colors.black
            ],
            stops: widget.child.reverse
                ? [0.0, 0.05 * _stopEnd, 1 - 0.05 * _stopStart, 1.0]
                : [0.0, 0.05 * _stopStart, 1 - 0.05 * _stopEnd, 1.0],
          ).createShader(rect);
        },
        blendMode: BlendMode.dstOut,
        child: Padding(
          padding: const EdgeInsets.all(1.0),
          child: widget.child,
        ),
      ),
    );
  }
}
anjerukare
  • 38
  • 3
raphaelDev
  • 21
  • 3
  • This worked really well. I modified it slightly to take a generic Widget and the direction explicitly, because I had a use case to wrap a SingleChildScrollView. – Rik Jun 02 '23 at 02:47
0

Try to use

Text(
        'This is big text, I am using Flutter and trying to fade text',
        overflow: TextOverflow.fade,
        maxLines: 1,
      ),
Santosh Anand
  • 1,230
  • 8
  • 13
  • 1
    Hi, thanks for the answer, however it's not quite what I'm after. What i'm hoping to achieve is as each ListTile goes up it fades into the top of the screen – Dan530c Jun 28 '18 at 08:44
  • that helped in my cast where i want single text to fade ,, but is there away to control the fade strength ? – Ahmed Osama Feb 20 '20 at 18:47