С перефразированным вопросом у меня есть более четкое понимание того, что вы пытаетесь сделать. У вас есть список виджетов, и вы хотите решить, отображать ли кнопку с плавающим действием в зависимости от того, отображаются ли эти виджеты в окне просмотра.
Я написал простой пример, который показывает это в действии. Я опишу различные элементы ниже, но учтите, что:
- Он использует GlobalKey, который не слишком эффективен
- Он работает непрерывно и выполняет неоптимальные вычисления каждый кадр во время прокрутки.
Следовательно, это может привести к замедлению работы вашего приложения. Я оставлю это кому-то другому, чтобы оптимизировать или написать лучший ответ, который использует лучшее знание дерева рендеринга, чтобы сделать то же самое.
В любом случае, вот код. Сначала я дам вам относительно более простой способ сделать это - использовать 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, чтобы убедиться, что каждый раз создается только соответствующая часть. Вы также можете избавиться от затухания / затухания, просто напрямую установив value
animationController и выполнив вычисление непрозрачности самостоятельно (т. Е. Вы можете захотеть, чтобы оно стало непрозрачным, когда оно начнет прокручиваться, что необходимо учитывать высота вашего объекта):
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),
);
}
}