我有一个视图,它的主体由Scaffold
和单个ListView
组成,列表的每个子级都是一个不同的小部件,表示视图的各个"部分"(部分范围从简单的TextView到Column
和Row
的排列),我想只在用户滚动到特定的Widgets
上时才显示FloatingActionButon
(因为它们最初是不可见的,因为它们位于列表的下方太远).
我有一个视图,它的主体由Scaffold
和单个ListView
组成,列表的每个子级都是一个不同的小部件,表示视图的各个"部分"(部分范围从简单的TextView到Column
和Row
的排列),我想只在用户滚动到特定的Widgets
上时才显示FloatingActionButon
(因为它们最初是不可见的,因为它们位于列表的下方太远).
有了这个重新措辞的问题,我对你想要做的事情有了更清楚的理解.您有一个小部件列表,并希望根据这些小部件当前是否显示在视口中来决定是否显示浮动操作按钮.
我已经编写了一个基本的示例,它在实际操作中展示了这一点.我将在下面描述各种元素,但请注意:
因此,它可能会导致你的应用程序速度减慢.我将把它留给其他人来优化或编写更好的答案,使用更好的渲染树知识来完成同样的事情.
不管怎样,这是代码.首先,我将介绍一种相对简单的方法——直接在变量上使用setState,因为它更简单:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
GlobalKey<State> key = new GlobalKey();
double fabOpacity = 1.0;
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Scrolling."),
),
body: NotificationListener<ScrollNotification>(
child: new ListView(
itemExtent: 100.0,
children: [
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
new MyObservableWidget(key: key),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder()
],
),
onNotification: (ScrollNotification scroll) {
var currentContext = key.currentContext;
if (currentContext == null) return false;
var renderObject = currentContext.findRenderObject();
RenderAbstractViewport viewport = RenderAbstractViewport.of(renderObject);
var offsetToRevealBottom = viewport.getOffsetToReveal(renderObject, 1.0);
var offsetToRevealTop = viewport.getOffsetToReveal(renderObject, 0.0);
if (offsetToRevealBottom.offset > scroll.metrics.pixels ||
scroll.metrics.pixels > offsetToRevealTop.offset) {
if (fabOpacity != 0.0) {
setState(() {
fabOpacity = 0.0;
});
}
} else {
if (fabOpacity == 0.0) {
setState(() {
fabOpacity = 1.0;
});
}
}
return false;
},
),
floatingActionButton: new Opacity(
opacity: fabOpacity,
child: new FloatingActionButton(
onPressed: () {
print("YAY");
},
),
),
),
);
}
}
class MyObservableWidget extends StatefulWidget {
const MyObservableWidget({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() => new MyObservableWidgetState();
}
class MyObservableWidgetState extends State<MyObservableWidget> {
@override
Widget build(BuildContext context) {
return new Container(height: 100.0, color: Colors.green);
}
}
class ContainerWithBorder extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Container(
decoration: new BoxDecoration(border: new Border.all(), color: Colors.grey),
);
}
}
它有几个容易修复的问题-它没有隐藏按钮,只是使其透明,它每次都呈现整个小部件,并在每一帧计算小部件的位置.
这是一个更优化的版本,如果不需要,它不会进行计算.如果您的列表发生变化,您可能需要添加更多的逻辑(或者您可以每次只做计算,如果性能足够好,则不必担心).请注意它是如何使用AnimationController和AnimatedBuilder来确保每次只构建相关部分的.您还可以通过直接设置AnimationController的value
并自己计算不透明度来消除淡入/淡出(即,您可能希望它在开始滚动到视图中时变得不透明,这必须考虑对象的高度):
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => new MyAppState();
}
class MyAppState extends State<MyApp> with TickerProviderStateMixin<MyApp> {
GlobalKey<State> key = new GlobalKey();
bool fabShowing = false;
// non-state-managed variables
AnimationController _controller;
RenderObject _prevRenderObject;
double _offsetToRevealBottom = double.infinity;
double _offsetToRevealTop = double.negativeInfinity;
@override
void initState() {
super.initState();
_controller = new AnimationController(vsync: this, duration: Duration(milliseconds: 300));
_controller.addStatusListener((val) {
if (val == AnimationStatus.dismissed) {
setState(() => fabShowing = false);
}
});
}
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Scrolling."),
),
body: NotificationListener<ScrollNotification>(
child: new ListView(
itemExtent: 100.0,
children: [
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
new MyObservableWidget(key: key),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder()
],
),
onNotification: (ScrollNotification scroll) {
var currentContext = key.currentContext;
if (currentContext == null) return false;
var renderObject = currentContext.findRenderObject();
if (renderObject != _prevRenderObject) {
RenderAbstractViewport viewport = RenderAbstractViewport.of(renderObject);
_offsetToRevealBottom = viewport.getOffsetToReveal(renderObject, 1.0).offset;
_offsetToRevealTop = viewport.getOffsetToReveal(renderObject, 0.0).offset;
}
final offset = scroll.metrics.pixels;
if (_offsetToRevealBottom < offset && offset < _offsetToRevealTop) {
if (!fabShowing) setState(() => fabShowing = true);
if (_controller.status != AnimationStatus.forward) {
_controller.forward();
}
} else {
if (_controller.status != AnimationStatus.reverse) {
_controller.reverse();
}
}
return false;
},
),
floatingActionButton: fabShowing
? new AnimatedBuilder(
child: new FloatingActionButton(
onPressed: () {
print("YAY");
},
),
builder: (BuildContext context, Widget child) => Opacity(opacity: _controller.value, child: child),
animation: this._controller,
)
: null,
),
);
}
}
class MyObservableWidget extends StatefulWidget {
const MyObservableWidget({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() => new MyObservableWidgetState();
}
class MyObservableWidgetState extends State<MyObservableWidget> {
@override
Widget build(BuildContext context) {
return new Container(height: 100.0, color: Colors.green);
}
}
class ContainerWithBorder extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Container(
decoration: new BoxDecoration(border: new Border.all(), color: Colors.grey),
);
}
}