Nabin Dhakal
Nabin Dhakal

Reputation: 2202

Pin the layout below and start scrolling at certain position in flutter

I am trying to achieve the scroll behaviour as in gif in this link. There is image slider which gets hidden when scroll and the title of the product goes to appbar title. Also there is a fixed button Add to Bag which is fixed but scrolls with the layout at certain screen position.

I could show and hide the Add to Bag buttton using vising visibility_detector. When scrolling slow its working but when scrolling fast the button is not visible.

I could achieve only this

enter image description here

I have tried as below:

Scaffold(
  body: SafeArea(
    child: CustomAppBar(
      centerTitle: false,
      expandedHeight: 355,
      searchIconShow: true,
      showBackButton: true,
      leadingWidget: const Icon(Icons.arrow_back),
      titleWidget: LayoutBuilder(
          builder: (BuildContext context, BoxConstraints constraints) {
      
        top = constraints.biggest.height;
        return top < 280
            ? FlexibleSpaceBar(
                centerTitle: false,
                titlePadding: const EdgeInsets.all(15),
                title: Container(
                  width: MediaQuery.of(context).size.width * 0.57,
                  height: 60,
                  padding: const EdgeInsets.fromLTRB(35, 0, 0, 0),
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      const Text("M.A.C Prep + Prep + Prime Fix+ -Original",
                          style: TextStyle(
                              overflow: TextOverflow.ellipsis,
                              color: Colors.black,
                              fontWeight: FontWeight.normal,
                              fontSize: 14)),
                      Expanded(
                        child: Row(
                          children: const [
                            Icon(Icons.star, size: 8, color: Colors.grey),
                            SizedBox(width: 2),
                            Text(
                              "4.1",
                              style: TextStyle(
                                  color: Colors.grey, fontSize: 10),
                            ),
                            SizedBox(width: 5),
                            Icon(Icons.circle, size: 5, color: Colors.grey),
                            SizedBox(width: 5),
                            Text("Rs 1200",
                                style: TextStyle(
                                    color: Colors.grey, fontSize: 10))
                          ],
                        ),
                      )
                    ],
                  ),
                ),
                background: Container())
            : FlexibleSpaceBar(
                centerTitle: false,
                titlePadding: const EdgeInsets.all(15),
                title: SingleChildScrollView(
                  physics:const NeverScrollableScrollPhysics(),
                  child: Container(
                    height:342,
                    padding: const EdgeInsets.fromLTRB(0, 135, 0, 0),
                    child: Column(
                      mainAxisSize: MainAxisSize.max,
                      children: [
                        _buildSlider(),
                        Row(
                          children: const [
                            Expanded(
                              child: Text(
                                  "M.A.C Prep + Prep + Prime Fix+ -Original",
                                  style: TextStyle(
                                      overflow: TextOverflow.ellipsis,
                                      color: Colors.black,
                                      fontWeight: FontWeight.normal,
                                      fontSize: 12)),
                            ),
                            Icon(
                              Icons.share,
                              color: Colors.black,
                              size: 20,
                            )
                          ],
                        ),
                        Row(
                          children: const [
                            Icon(Icons.star, size: 10, color: Colors.grey),
                            SizedBox(width: 2),
                            Text(
                              "4.1",
                              style:
                                  TextStyle(color: Colors.grey, fontSize: 10),
                            ),
                            SizedBox(width: 5),
                            Icon(Icons.circle, size: 5, color: Colors.grey),
                            SizedBox(width: 5),
                            Text("Rs 1200",
                                style: TextStyle(
                                    color: Colors.grey, fontSize: 10))
                          ],
                        )
                      ],
                    ),
                  ),
                ),
              );
      }),
      myWidget: Stack(children: [
        SingleChildScrollView(
          child: Column(
            children: [
              Container(height: 200, color: Colors.green),
              Container(height: 200, color: Colors.yellow),
              Container(height: 200, color: Colors.pink),
              Container(height: 200, color: Colors.grey),
              Container(height: 200, color: Colors.blueGrey),
              Container(height: 200, color: Colors.indigo),
              Container(height: 200, color: Colors.purple),
              Container(height: 200, color: Colors.green),
              Container(height: 200, color: Colors.yellow),
              Container(height: 200, color: Colors.pink),
              Container(height: 200, color: Colors.grey),
              VisibilityDetector(
                key: Key('my-widget-key'),
                onVisibilityChanged: (visibility) {
                  var visiblePercentage =
                      visibility.visibleFraction * 100;
                  if (visiblePercentage < 0) {
                    setState(() {
                      showBottomButton = false;
                    });
                  }

                  else{
                    setState(() {
                      showBottomButton = true;
                    });
                  }
                },
                child: Padding(
                  padding: const EdgeInsets.all(10.0),
                  child: Container(
                      padding: const EdgeInsets.all(20),
                      width: MediaQuery.of(context).size.width,
                      color: Colors.red,
                      child: const Text("Add to Bag",
                          style: TextStyle(color: Colors.white))),
                ),
              ),
              VisibilityDetector(
                  key: Key('my-widget-key2'),
                  onVisibilityChanged: (visibility) {

                    var visiblePercentage =
                        visibility.visibleFraction * 100;
                    if (visiblePercentage < 0) {
                      setState(() {
                        showBottomButton = true;
                      });
                    } else {
                      setState(() {
                        showBottomButton = false;
                      });
                    }
                  },
                  child: Column(
                    children: [
                      Container(height: 200, color: Colors.black),
                      Container(height: 200, color: Colors.indigo),
                      Container(height: 200, color: Colors.purple),
                      Container(height: 200, color: Colors.indigo),
                      Container(height: 200, color: Colors.purple),
                      Container(height: 200, color: Colors.indigo),
                      Container(height: 200, color: Colors.black),
                      Container(height: 200, color: Colors.purple),
                      Container(height: 200, color: Colors.black),
                      Container(height: 200, color: Colors.purple),
                      Container(height: 200, color: Colors.black),
                      Container(height: 200, color: Colors.purple),
                      Container(height: 200, color: Colors.black),
                      Container(height: 200, color: Colors.purple),
                      Container(height: 200, color: Colors.black),
                      Container(height: 200, color: Colors.purple),
                    ],
                  )),
            ],
          ),
        ),
        Visibility(
          visible: showBottomButton,
          child: Align(
            alignment: Alignment.bottomCenter,
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Container(
                  padding: const EdgeInsets.all(20),
                  width: MediaQuery.of(context).size.width,
                  color: Colors.red,
                  child: const Text("Add to Bag",
                      style: TextStyle(color: Colors.white))),
            ),
          ),
        ),
      ]),
    ),
  ),
);

And I could achieve only this

Upvotes: 1

Views: 1345

Answers (3)

Nabin Dhakal
Nabin Dhakal

Reputation: 2202

Solved it there was problem in logic. Just changed if (visiblePercentage < 0) to

if (visiblePercentage <= 0)

Upvotes: 0

Diwyansh
Diwyansh

Reputation: 3514

You can achieve this result by using Listview and SliverAppBar. I'm sharing a demo of the same you can customize it as per your choice.

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

void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  ScrollController _controller = ScrollController();
  ScrollController _controller1 = ScrollController();

  @override
  void initState() {
    super.initState();
    _controller.addListener(listenChanges);
    _controller1.addListener(listListenChanges);
  }

  bool showTitle = false;
  bool isPrimary = false;

  void listenChanges() {
    if (_controller.offset >= 170) {
      showTitle = true;
    } else {
      showTitle = false;
    }
    if(_controller.offset == 0.0){
      isPrimary = false;
    }
    setState(() {});
  }

  void listListenChanges() {
    print(_controller1.offset == _controller1.position.maxScrollExtent);
    print(_controller1.offset == 0.0);
    print(_controller1.offset);
    print(_controller1.position.maxScrollExtent);
    if (_controller1.offset == _controller1.position.maxScrollExtent) {
      isPrimary = true;
    } else {
      isPrimary = false;
    }
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Fetch Data Example',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Builder(builder: (context) => Scaffold(
            body: NestedScrollView(
              headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) => [SliverAppBar(
                // collapsedHeight: 70,
                pinned: true,
                title: Visibility(
                  visible: showTitle,
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        "This is your fixed header ",
                        style:
                        TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
                      ),
                      Text(
                        "This is your fixed header ",
                        style: TextStyle(
                            fontSize: 12,
                            fontWeight: FontWeight.bold,
                            color: Colors.grey),
                      ),
                    ],
                  ),
                ),
                backgroundColor: Colors.red,
                expandedHeight:
                170.0, // This hand the expanded height of the header
                flexibleSpace: FlexibleSpaceBar(
                    background: Stack(
                      alignment: AlignmentDirectional.center,
                      children: [
                        Card(
                          child: Text(
                            "This is your fixed header ",
                            style:
                            TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                          ),
                        ), // your content to show on header here
                      ],
                    )),
              )],
              controller: _controller,
              body: ListView(
                physics: NeverScrollableScrollPhysics(),
                primary: false,
                shrinkWrap: true,
                children: [
                  Container(
                    color: Colors.yellow,
                    height: MediaQuery.of(context).size.height - 270,
                    child: Stack(
                      children: [
                        Positioned(
                            top: 0,
                            left: 0,
                            bottom: 40,
                            width: MediaQuery.of(context).size.width,
                            child: ListView.builder(
                                physics: isPrimary ? NeverScrollableScrollPhysics() : null,
                                controller: _controller1,
                                itemBuilder:
                                    (context, index) => Container(height: 100,child: Text("sssssss")),
                                itemCount: 15)),
                        Positioned(
                            bottom: 10,
                            left: 20,
                            height: 35,
                            right: 20,
                            child: ElevatedButton(onPressed: (){},child: Text("button"),)),
                      ],
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(15.0),
                    child: Text("Text 1"),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(15.0),
                    child: Text("Text 1"),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(15.0),
                    child: Text("Text 1"),
                  )
                ],
              ),

            ))));
  }
}

Upvotes: 1

You can do it with CustomScrollView and Slivers, on a simple way:

Scaffold(
        body: CustomScrollView(
      slivers: <Widget>[
        SliverAppBar(
          backgroundColor: Colors.red,
          expandedHeight: 200.0, // This hand the expanded height of the header
          flexibleSpace: FlexibleSpaceBar(
              background: Stack(
            alignment: AlignmentDirectional.center,
            children: [
              getMyContent(), // your content to show on header here              
            ],
          )),
        ),
        // The items that you show down
        SliverFixedExtentList(
          itemExtent: 150.0,
          delegate: SliverChildBuilderDelegate(
              (context, index) => getMyList(item: list[index]),
              childCount: list.length),
        ),
      ],
    ));

Upvotes: 1

Related Questions