Home > Blockchain >  Flutter - Show Row on extra scroll - Top of column (Like Whatsapp Archived Chats)
Flutter - Show Row on extra scroll - Top of column (Like Whatsapp Archived Chats)

Time:10-06

I want to put a row on top of a column, which will not be visible initially. It will only be visible when the scroll offset of the SingleChildScrollview is negative.

In other words, only if the user scrolls further than normal (downwards motion) will this Row show. This is an example in Whatsapp. The "Archived" Section is not shown initially, only if you scroll up:

enter image description here

CodePudding user response:

Using singleChildScrollView here is what I came up with using s NotificationListener() widget, there are other solutions but this one is the simplest one:

have a bool to determine Container visibility:

  bool shouldIShowTheUpperThing = false;

the have your SingleChildScrollView() wrapped with a NotificationListener() :

NotificationListener(
        child: SingleChildScrollView(
          child: Column(
            children: [
              shouldIShowTheUpperThing == false ? Row(
    children: [
      Container(height: 0,),
    ],
    ) :   Row(
    children: [
      Expanded(child: Container(color: Colors.red , height: 100 , child: Text('the hidden box'),)),
    ],
    ),
              Container(
                padding: EdgeInsets.all(130),
                child: Text('data'),
                color: Colors.blueGrey,
              ),
              Container(
                padding: EdgeInsets.all(130),
                child: Text('data'),
                color: Colors.blueAccent,
              ),
              Container(
                padding: EdgeInsets.all(130),
                child: Text('data'),
                color: Colors.amber,
              ),
              Container(
                padding: EdgeInsets.all(130),
                child: Text('data'),
                color: Colors.black12,
              ),
              Container(
                padding: EdgeInsets.all(130),
                child: Text('data'),
              ),
            ],
          ),
        ),
        
        onNotification: (t) {
          if (t is ScrollNotification) {
            if (t.metrics.pixels < 1.0) {
              setState(() {
                shouldIShowTheUpperThing = true;
              });
            }
          }
          return true;
        });
  }

CodePudding user response:

try SliverAppBar and set floating and snap named params to true

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: <Widget>[
        const  SliverAppBar(
            pinned: true,
            snap: true,
            floating: true,
            expandedHeight: 160.0,
            flexibleSpace: FlexibleSpaceBar(
              title:  Text('SliverAppBar'),
              background: FlutterLogo(),
            ),
          ),
          const SliverToBoxAdapter(
            child: SizedBox(
              height: 20,
              child: Center(
                child: Text('Scroll to see the SliverAppBar in effect.'),
              ),
            ),
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                return Container(
                  color: index.isOdd ? Colors.white : Colors.black12,
                  height: 100.0,
                  child: Center(
                    child: Text('$index', textScaleFactor: 5),
                  ),
                );
              },
              childCount: 20,
            ),
          ),
        ],
      ),
    );
  }
}

CodePudding user response:

Since this is a unusual scroll effect, if you want it to look good I think you need to use slivers.

Implementation

What I did was copy paste SliverToBoxAdapter and modify it so that on its first layout it will readjust the scroll offset.

class SliverHidedHeader extends SingleChildRenderObjectWidget {
  const SliverHidedHeader({
    Key? key,
    required Widget child,
  }) : super(key: key, child: child);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderSliverHidedHeader();
  }
}

class RenderSliverHidedHeader extends RenderSliverSingleBoxAdapter {
  RenderSliverHidedHeader({
    RenderBox? child,
  }) : super(child: child);

  bool isFirstLayout = true;

  @override
  void performLayout() {
    if (child == null) {
      geometry = SliverGeometry.zero;
      return;
    }
    final SliverConstraints constraints = this.constraints;
    child!.layout(constraints.asBoxConstraints(), parentUsesSize: true);
    final double childExtent;
    switch (constraints.axis) {
      case Axis.horizontal:
        childExtent = child!.size.width;
        break;
      case Axis.vertical:
        childExtent = child!.size.height;
        break;
    }
    final double paintedChildSize =
        calculatePaintOffset(constraints, from: 0.0, to: childExtent);
    final double cacheExtent = calculateCacheOffset(constraints, from: 0.0, to: childExtent);

    assert(paintedChildSize.isFinite);
    assert(paintedChildSize >= 0.0);

    // Here are the few custom lines, which use [scrollOffsetCorrection]
    // to remove the child size
    if (isFirstLayout) {
      geometry = SliverGeometry(
        scrollOffsetCorrection: childExtent,
      );
      isFirstLayout = false;
      return;
    }

    geometry = SliverGeometry(
      scrollExtent: childExtent,
      paintExtent: paintedChildSize,
      cacheExtent: cacheExtent,
      maxPaintExtent: childExtent,
      hitTestExtent: paintedChildSize,
      hasVisualOverflow:
          childExtent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
    );
    setChildParentData(child!, constraints, geometry!);
  }
}

How to use it

Use it at the top of your list of slivers:

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  runApp(MaterialApp(home: MyStatefulWidget()));
}

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: CustomScrollView(
          slivers: <Widget>[
            SliverHidedHeader(
              child: Container(
                child: Center(child: Text('SliverAppBar')),
                height: 100,
                color: Colors.redAccent,
              ),
            ),
            const SliverToBoxAdapter(
              child: SizedBox(
                height: 20,
                child: Center(
                  child: Text('Scroll to see the SliverAppBar in effect.'),
                ),
              ),
            ),
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
                  return Container(
                    color: index.isOdd ? Colors.white : Colors.black12,
                    height: 100.0,
                    child: Center(
                      child: Text('$index', textScaleFactor: 5),
                    ),
                  );
                },
                childCount: 20,
              ),
            ),
          ],
        ),
      ),
    );
  }
}
  • Related