我有一个PageView,如何禁用向左或向右滚动。我知道使用NeverScrollableScrollPhysics可以禁用滚动,但是如何禁用一个方向的滚动。
答案 0 :(得分:3)
您还可以使用我最近编写的horizontal_blocked_scroll_physics库,该库将允许您阻止左右移动。
答案 1 :(得分:1)
您可以创建自己的ScrollPhysics
,仅允许其向右移动:
class CustomScrollPhysics extends ScrollPhysics {
CustomScrollPhysics({ScrollPhysics parent}) : super(parent: parent);
bool isGoingLeft = false;
@override
CustomScrollPhysics applyTo(ScrollPhysics ancestor) {
return CustomScrollPhysics(parent: buildParent(ancestor));
}
@override
double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
isGoingLeft = offset.sign < 0;
return offset;
}
@override
double applyBoundaryConditions(ScrollMetrics position, double value) {
//print("applyBoundaryConditions");
assert(() {
if (value == position.pixels) {
throw FlutterError(
'$runtimeType.applyBoundaryConditions() was called redundantly.\n'
'The proposed new position, $value, is exactly equal to the current position of the '
'given ${position.runtimeType}, ${position.pixels}.\n'
'The applyBoundaryConditions method should only be called when the value is '
'going to actually change the pixels, otherwise it is redundant.\n'
'The physics object in question was:\n'
' $this\n'
'The position object in question was:\n'
' $position\n');
}
return true;
}());
if (value < position.pixels && position.pixels <= position.minScrollExtent)
return value - position.pixels;
if (position.maxScrollExtent <= position.pixels && position.pixels < value)
// overscroll
return value - position.pixels;
if (value < position.minScrollExtent &&
position.minScrollExtent < position.pixels) // hit top edge
return value - position.minScrollExtent;
if (position.pixels < position.maxScrollExtent &&
position.maxScrollExtent < value) // hit bottom edge
return value - position.maxScrollExtent;
if (!isGoingLeft) {
return value - position.pixels;
}
return 0.0;
}
}
用法:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: PageView.builder(
itemCount: 4,
physics: CustomScrollPhysics(),
itemBuilder: (context, index) => Center(
child: Text("Item $index"),
),
));
}
答案 2 :(得分:1)
为此准备一个更好的方法。
这种方法不需要实例化新的物理实例,并且可以动态更新。
使用功能参数onAttemptDrag
允许或拒绝滑动请求。此函数中的代码应该高效,因为每秒将被调用多次(滚动时)。此外,您可能想在此函数中添加一个标志,以允许程序源请求通过。例如,在“下一个按钮”的情况下,此物理实现也将阻止功能jumpTo(..)
和animateTo
的正常工作,因此您需要具有一个标志,该标志将暂时返回默认值true
用于特定的页面过渡(如果按下了下一个按钮)。让我知道任何问题或改进方法。
class LockingPageScrollPhysics extends ScrollPhysics {
/// Requests whether a drag may occur from the page at index "from"
/// to the page at index "to". Return true to allow, false to deny.
final Function(int from, int to) onAttemptDrag;
/// Creates physics for a [PageView].
const LockingPageScrollPhysics(
{ScrollPhysics parent, @required this.onAttemptDrag})
: super(parent: parent);
@override
LockingPageScrollPhysics applyTo(ScrollPhysics ancestor) {
return LockingPageScrollPhysics(
parent: buildParent(ancestor), onAttemptDrag: onAttemptDrag);
}
double _getPage(ScrollMetrics position) {
if (position is PagePosition) return position.page;
return position.pixels / position.viewportDimension;
}
double _getPixels(ScrollMetrics position, double page) {
if (position is PagePosition) return position.getPixelsFromPage(page);
return page * position.viewportDimension;
}
double _getTargetPixels(
ScrollMetrics position, Tolerance tolerance, double velocity) {
double page = _getPage(position);
if (velocity < -tolerance.velocity)
page -= 0.5;
else if (velocity > tolerance.velocity) page += 0.5;
return _getPixels(position, page.roundToDouble());
}
@override
double applyBoundaryConditions(ScrollMetrics position, double value) {
assert(() {
if (value == position.pixels) {
throw FlutterError('$runtimeType.applyBoundaryConditions() was called redundantly.\n'
'The proposed new position, $value, is exactly equal to the current position of the '
'given ${position.runtimeType}, ${position.pixels}.\n'
'The applyBoundaryConditions method should only be called when the value is '
'going to actually change the pixels, otherwise it is redundant.\n'
'The physics object in question was:\n'
' $this\n'
'The position object in question was:\n'
' $position\n');
}
return true;
}());
/*
* Handle the hard boundaries (min and max extents)
* (identical to ClampingScrollPhysics)
*/
if (value < position.pixels && position.pixels <= position.minScrollExtent) // under-scroll
return value - position.pixels;
if (position.maxScrollExtent <= position.pixels && position.pixels < value) // over-scroll
return value - position.pixels;
if (value < position.minScrollExtent &&
position.minScrollExtent < position.pixels) // hit top edge
return value - position.minScrollExtent;
if (position.pixels < position.maxScrollExtent &&
position.maxScrollExtent < value) // hit bottom edge
return value - position.maxScrollExtent;
bool left = value < position.pixels;
int fromPage, toPage;
double overScroll = 0;
if (left) {
fromPage = position.pixels.ceil() ~/ position.viewportDimension;
toPage = value ~/ position.viewportDimension;
overScroll = value - fromPage * position.viewportDimension;
overScroll = overScroll.clamp(value - position.pixels, 0.0);
} else {
fromPage =
(position.pixels + position.viewportDimension).floor() ~/ position.viewportDimension;
toPage = (value + position.viewportDimension) ~/ position.viewportDimension;
overScroll = value - fromPage * position.viewportDimension;
overScroll = overScroll.clamp(0.0, value - position.pixels);
}
if (fromPage != toPage && !onAttemptDrag(fromPage, toPage)) {
return overScroll;
} else {
return super.applyBoundaryConditions(position, value);
}
}
这是我一直在使用的库,它使用的是经过修改的控制器https://github.com/RobluScouting/FlutterBoardView。
答案 3 :(得分:0)
这是@diegoveloper 和@wdavies973 想法的另一个版本。我有必要只能使用一些编程命令进行滚动,而忽略用户手势。与主 Widget 中的一些复杂解决方法不同,我能够使用 ScrollPhysics 行为锁定屏幕以滚动。当用户交互时,applyPhysicsToUserOffset
在 applyBoundaryConditions
之前被调用。考虑到这一点,这是我的示例,希望对某人有所帮助。
注意:一旦锁定,就可以使用 controller.animateTo(pageIndex)
/// Custom page scroll physics
// ignore: must_be_immutable
class CustomLockScrollPhysics extends ScrollPhysics {
/// Lock swipe on drag-drop gesture
/// If it is a user gesture, [applyPhysicsToUserOffset] is called before [applyBoundaryConditions];
/// If it is a programming gesture eg. `controller.animateTo(index)`, [applyPhysicsToUserOffset] is not called.
bool _lock = false;
/// Lock scroll to the left
final bool lockLeft;
/// Lock scroll to the right
final bool lockRight;
/// Creates physics for a [PageView].
/// [lockLeft] Lock scroll to the left
/// [lockRight] Lock scroll to the right
CustomLockScrollPhysics({ScrollPhysics parent, this.lockLeft = false, this.lockRight = false})
: super(parent: parent);
@override
CustomLockScrollPhysics applyTo(ScrollPhysics ancestor) {
return CustomLockScrollPhysics(parent: buildParent(ancestor), lockLeft: lockLeft, lockRight: lockRight);
}
@override
double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
if ((lockRight && offset < 0) || (lockLeft && offset > 0)) {
_lock = true;
return 0.0;
}
return offset;
}
@override
double applyBoundaryConditions(ScrollMetrics position, double value) {
assert(() {
if (value == position.pixels) {
throw FlutterError('$runtimeType.applyBoundaryConditions() was called redundantly.\n'
'The proposed new position, $value, is exactly equal to the current position of the '
'given ${position.runtimeType}, ${position.pixels}.\n'
'The applyBoundaryConditions method should only be called when the value is '
'going to actually change the pixels, otherwise it is redundant.\n'
'The physics object in question was:\n'
' $this\n'
'The position object in question was:\n'
' $position\n');
}
return true;
}());
/*
* Handle the hard boundaries (min and max extents)
* (identical to ClampingScrollPhysics)
*/
// under-scroll
if (value < position.pixels && position.pixels <= position.minScrollExtent) {
return value - position.pixels;
}
// over-scroll
else if (position.maxScrollExtent <= position.pixels && position.pixels < value) {
return value - position.pixels;
}
// hit top edge
else if (value < position.minScrollExtent && position.minScrollExtent < position.pixels) {
return value - position.pixels;
}
// hit bottom edge
else if (position.pixels < position.maxScrollExtent && position.maxScrollExtent < value) {
return value - position.pixels;
}
var isGoingLeft = value <= position.pixels;
var isGoingRight = value >= position.pixels;
if (_lock && ((lockLeft && isGoingLeft) || (lockRight && isGoingRight))) {
_lock = false;
return value - position.pixels;
}
return 0.0;
}
}
用法示例:
@override
void initState() {
super.initState();
controller.tabController = TabController(initialIndex: 0, length: 3, vsync: this);
}
void nextTab() => controller.tabController.animateTo(controller.tabController.index + 1);
@override
void build(BuildContext context)
return TabBarView(
controller: controller.tabController,
physics: CustomLockScrollPhysics(lockLeft: true),
children: [ InkWell(onTap: nextTab), InkWell(onTap: nextTab), Container() ],
),
}
最后一点:我尽一切努力避免在类中使用某些变量,因为 ScrollPhysics 是@immutable,但在这种情况下,这是成功知道输入是否为用户手势的唯一方法。
答案 4 :(得分:0)
如果你决定像我一样使用@Fabricio N. de Godoi 的回答,那就是一个错误。例如,在您阻止向右滚动后,向右滚动将无法按预期工作。但在那之后,如果您滚动相反方向页面将向右滚动。
这就是孤独; 找到这个代码
if (_lock && ((lockLeft && isGoingLeft) || (lockRight && isGoingRight))) {
_lock = false;
return value - position.pixels;
}
并替换为
if (_lock && ((lockLeft && isGoingLeft) || (lockRight && isGoingRight))) {
_lock = false;
return value - position.pixels;
} else {
_lock = true;
}