mponkin / fading_edge_scrollview

Flutter library for displaying fading edges on scroll views
BSD 3-Clause "New" or "Revised" License
43 stars 30 forks source link

Fade not visible on start of Scrollviews. #2

Open Reinbowsaur opened 4 years ago

Reinbowsaur commented 4 years ago

I've tested this package with 2 scrollviews I have in my app, the fade is only applying to the end of the scrollview, not the start.

spauldhaliwal commented 4 years ago

Same issue here.

mponkin commented 4 years ago

Sometimes I see such thing when I add FadingEdgeScrollView and experiment with it's parameters using hot reload. After using hot restart it works properly.

If hot restart does not help - could you provide some source code example which reproduces the problem?

liveaffiliates commented 4 years ago

Same thing, there is some issue in the code on Singlechildscrollview. If you add gradient to the start and end they add on to each other so the fade becomes twice as long on the bottom of the scrollview. I would say this is because you are adding a container or box over the scrollview and fading the ends but that container height has gone to zero. Just a guess.

Nico04 commented 3 years ago

Same issue here, any news ?

berdroid commented 2 years ago

I have this effect with a PageView as a child (created via PageView.builder). Once the page is scrolled left even a tiny bit the fading shows up.

kuklinorbert commented 2 years ago

I also have the same issue. Any solution to this problem?

8thgencore commented 2 years ago

Is there any solution already?

amukater commented 1 year ago

It can be reproduced on a physical iPhone device, but not on Simulator, in my case.

amukater commented 1 year ago

I was able to work around this by forcing a ScrollController callback only once at first after drawing.

useEffect(
  () {
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      () async {
        await Future.delayed(const Duration(milliseconds: 200));
        scrollController.animateTo(
          1,
          duration: const Duration(milliseconds: 1),
          curve: Curves.ease,
        );
        print(scrollController.position.maxScrollExtent);
      }();
    });

    return null;
  },
  const [],
);

(This code assumes flutter_hooks. If it is a StatefulWidget, place it in initState.)

1Nameless commented 1 year ago

I had the same issue when I created the ScrollController directly in the constructor. Defining a Scrollcontroller outside my Build function and then passing that controller fixed it for me

milkyway044 commented 1 year ago

Use this implementation. You simply need to wrap any scrollable widget (such as ListView, GridView, SingleChildScrollView, etc.) with the FadingEdgeScrollView widget and it will automatically determine the scroll direction and apply the fading effect. No ScrollController is needed.

import 'package:flutter/material.dart';

class FadingEdgeScrollView extends StatefulWidget {
  const FadingEdgeScrollView({
    super.key,
    required this.child,
    this.gradientFractionOnStart = 0.1,
    this.gradientFractionOnEnd = 0.1,
  })  : assert(gradientFractionOnStart >= 0 && gradientFractionOnStart <= 1),
        assert(gradientFractionOnEnd >= 0 && gradientFractionOnEnd <= 1);

  final Widget child;

  /// what part of screen on start half should be covered by fading edge gradient
  /// [gradientFractionOnStart] must be 0 <= [gradientFractionOnStart] <= 1
  /// 0 means no gradient,
  /// 1 means gradients on start half of widget fully covers it
  final double gradientFractionOnStart;

  /// what part of screen on end half should be covered by fading edge gradient
  /// [gradientFractionOnEnd] must be 0 <= [gradientFractionOnEnd] <= 1
  /// 0 means no gradient,
  /// 1 means gradients on start half of widget fully covers it
  final double gradientFractionOnEnd;

  @override
  State<FadingEdgeScrollView> createState() => _FadingEdgeScrollViewState();
}

class _FadingEdgeScrollViewState extends State<FadingEdgeScrollView> {
  bool? _isScrolledToStart;
  bool? _isScrolledToEnd;

  @override
  Widget build(BuildContext context) {
    return NotificationListener<ScrollUpdateNotification>(
      onNotification: (ScrollUpdateNotification notification) {
        final metrics = notification.metrics;
        setState(() {
          _isScrolledToStart = metrics.pixels <= metrics.minScrollExtent;
          _isScrolledToEnd = metrics.pixels >= metrics.maxScrollExtent;
        });
        return false;
      },
      child: ShaderMask(
        shaderCallback: (bounds) => LinearGradient(
          begin: _gradientStart,
          end: _gradientEnd,
          stops: [
            0,
            widget.gradientFractionOnStart * 0.5,
            1 - widget.gradientFractionOnEnd * 0.5,
            1,
          ],
          colors: _getColors(
              widget.gradientFractionOnStart > 0 &&
                  !(_isScrolledToStart ?? true),
              widget.gradientFractionOnEnd > 0 && !(_isScrolledToEnd ?? false)),
        ).createShader(
          bounds.shift(Offset(-bounds.left, -bounds.top)),
          textDirection: Directionality.of(context),
        ),
        blendMode: BlendMode.dstIn,
        child: widget.child,
      ),
    );
  }

  AlignmentGeometry get _gradientStart =>
      _scrollDirection == Axis.vertical ? _verticalStart : _horizontalStart;

  AlignmentGeometry get _gradientEnd =>
      _scrollDirection == Axis.vertical ? _verticalEnd : _horizontalEnd;

  Alignment get _verticalStart =>
      _reverse ? Alignment.bottomCenter : Alignment.topCenter;

  Alignment get _verticalEnd =>
      _reverse ? Alignment.topCenter : Alignment.bottomCenter;

  AlignmentDirectional get _horizontalStart => _reverse
      ? AlignmentDirectional.centerEnd
      : AlignmentDirectional.centerStart;

  AlignmentDirectional get _horizontalEnd => _reverse
      ? AlignmentDirectional.centerStart
      : AlignmentDirectional.centerEnd;

  List<Color> _getColors(bool isStartEnabled, bool isEndEnabled) => [
        (isStartEnabled ? Colors.transparent : Colors.white),
        Colors.white,
        Colors.white,
        (isEndEnabled ? Colors.transparent : Colors.white)
      ];

  /// The axis along which child view scrolls
  ///
  /// Look for more documentation at [ScrollView.scrollDirection]
  Axis get _scrollDirection {
    final child = widget.child;
    if (child is ScrollView) {
      return child.scrollDirection;
    } else if (child is SingleChildScrollView) {
      return child.scrollDirection;
    } else if (child is PageView) {
      return child.scrollDirection;
    } else if (child is AnimatedList) {
      return child.scrollDirection;
    } else {
      return Axis.vertical;
    }
  }

  /// Whether the scroll view scrolls in the reading direction.
  ///
  /// Look for more documentation at [ScrollView.reverse]
  bool get _reverse {
    final child = widget.child;
    if (child is ScrollView) {
      return child.reverse;
    } else if (child is SingleChildScrollView) {
      return child.reverse;
    } else if (child is PageView) {
      return child.reverse;
    } else if (child is AnimatedList) {
      return child.reverse;
    } else {
      return false;
    }
  }
}
Nico04 commented 1 year ago

Thanks @milkyway044 for the implementation. However, in my case, when the content is small enought to fit without scrolling, it causes the fade to be displayed at the end anyway, which is quite sad.

image

hyped0001 commented 3 weeks ago

bump