Reputation: 2202
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
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
Reputation: 2202
Solved it there was problem in logic. Just changed if (visiblePercentage < 0)
to
if (visiblePercentage <= 0)
Upvotes: 0
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
Reputation: 473
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