固定下面的布局并在 flutter 中的某个位置开始滚动
Pin the layout below and start scrolling at certain position in flutter
我正在尝试在此 link 中实现 gif 中的滚动行为。
滚动时隐藏图像滑块,产品标题转到应用栏标题。还有一个固定按钮添加到包,它是固定的,但会在特定屏幕位置随布局滚动。
我可以使用可视化 visibility_detector 显示和隐藏 添加到购物袋 按钮。当滚动变慢时它的工作但是当快速滚动时按钮不可见。
我只能做到这一点
我试过如下:
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))),
),
),
),
]),
),
),
);
而我只能做到这一点
您可以使用 CustomScrollView 和 Slivers 以一种简单的方式做到这一点:
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),
),
],
));
您可以使用 Listview 和 SliverAppBar 来实现此结果。我正在分享 demo
相同的内容,您可以根据自己的选择 customize
。
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"),
)
],
),
))));
}
}
解决了逻辑上的问题。刚刚将 if (visiblePercentage < 0)
更改为
if (visiblePercentage <= 0)
我正在尝试在此 link 中实现 gif 中的滚动行为。 滚动时隐藏图像滑块,产品标题转到应用栏标题。还有一个固定按钮添加到包,它是固定的,但会在特定屏幕位置随布局滚动。
我可以使用可视化 visibility_detector 显示和隐藏 添加到购物袋 按钮。当滚动变慢时它的工作但是当快速滚动时按钮不可见。
我只能做到这一点
我试过如下:
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))),
),
),
),
]),
),
),
);
而我只能做到这一点
您可以使用 CustomScrollView 和 Slivers 以一种简单的方式做到这一点:
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),
),
],
));
您可以使用 Listview 和 SliverAppBar 来实现此结果。我正在分享 demo
相同的内容,您可以根据自己的选择 customize
。
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"),
)
],
),
))));
}
}
解决了逻辑上的问题。刚刚将 if (visiblePercentage < 0)
更改为
if (visiblePercentage <= 0)