Dan530c
Dan530c

Reputation: 457

Fading Edge ListView - Flutter

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"]),
            ),
          ),
        );
      }),
   );
 }
}

Upvotes: 32

Views: 30184

Answers (7)

Maksym Khaiuk
Maksym Khaiuk

Reputation: 1

I prefer to use stack with gradient to achieve this, also you should give top padding to your ListView, so when there is no scroll required gradient will not cover your first item. But ofc this only works if you have some background color under your ListView.

   Stack(
          children: [
            ConstrainedBox(
              constraints: const BoxConstraints(maxHeight: 550),
              child: ListView.separated(
                  shrinkWrap: true,
                  padding: const EdgeInsets.only(bottom: 16, top: 16),
                  itemBuilder: (context, i) {
                    return _ReferralCard(
                      name: 'SomeName${Random().nextInt(1000000)}',
                      avatarUri:
                          'https://picsum.photos/id/${Random().nextInt(600)}/200',
                    );
                  },
                  separatorBuilder: (context, i) => const SizedBox(
                        height: 8,
                      ),
                  itemCount: 2000),
            ),
            Positioned(
              top: 0,
              left: 0,
              right: 0,
              height: 20,
              child: Container(
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    colors: [
                      AppPalette.surfacePrimary,
                      AppPalette.surfacePrimary.withAlpha(0),
                    ],
                    begin: Alignment.topCenter,
                    end: Alignment.bottomCenter,
                  ),
                ),
              ),
            ),
          ],
        )

Upvotes: 0

raphaelDev
raphaelDev

Reputation: 61

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,
        ),
      ),
    );
  }
}

Upvotes: 6

Kriszti&#225;n &#211;dor
Kriszti&#225;n &#211;dor

Reputation: 1208

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

Upvotes: 58

Yariv.A
Yariv.A

Reputation: 81

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

Upvotes: 6

Mikhail Ponkin
Mikhail Ponkin

Reputation: 2711

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

Upvotes: 11

Martin Pt&#225;ček
Martin Pt&#225;ček

Reputation: 297

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,
     ),
);

Upvotes: 29

Santosh Anand
Santosh Anand

Reputation: 1228

Try to use

Text(
        'This is big text, I am using Flutter and trying to fade text',
        overflow: TextOverflow.fade,
        maxLines: 1,
      ),

Upvotes: 0

Related Questions