Flutter控件偏移管理插件flutter_widget_offset的使用
Flutter控件偏移管理插件flutter_widget_offset的使用
一个类库,用于获取和观察 widget
到根布局边缘的偏移量。此类库也适用于对话框(showDialog
)。

支持
onChanged
: 支持监控widgets
相对于根布局边缘的偏移量。onKeyboard
: 支持监控软键盘状态变化(显示和隐藏)。
使用 OffsetDetector
使用 OffsetDetector
直接获取 widget
到根布局边缘的偏移量。
final OffsetDetectorController? _controller = OffsetDetectorController();
OffsetDetector(
controller: _controller,
onChanged: (Size size, EdgeInsets offset, EdgeInsets rootPadding) {
print("The widget size: ${size.width}, ${size.height}");
print(
"The offset to edge of root(ltrb): ${offset.left}, ${offset.top}, ${offset.right}, ${offset.bottom}");
print(
"the root padding: ${rootPadding.left}, ${rootPadding.top}, ${rootPadding.right}, ${rootPadding.bottom}");
},
child: TextField(
style: textFieldStyle,
onChanged: (value) => _controller.notifyStateChanged(),
));
使用 OffsetChangeObserver
使用 OffsetChangeObserver
来观察 widget
到根布局边缘的偏移量。
class OffsetDetector extends StatefulWidget {
OffsetDetector({Key? key}) : super(key: key);
[@override](/user/override)
State<StatefulWidget> createState() {
return _OffsetDetectorState();
}
}
class _OffsetDetectorState extends State<OffsetDetector> with WidgetsBindingObserver {
late OffsetChangeObserver _observer;
[@override](/user/override)
Widget build(BuildContext context) {
return TextField();
}
void onChanged(Size size, EdgeInsets offset, EdgeInsets rootPadding) {
print("The widget size: ${size.width}, ${size.height}");
print(
"The offset to edge of root(ltrb): ${offset.left}, ${offset.top}, ${offset.right}, ${offset.bottom}");
print(
"the root padding: ${rootPadding.left}, ${rootPadding.top}, ${rootPadding.right}, ${rootPadding.bottom}");
}
[@override](/user/override)
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
_observer = OffsetChangeObserver(context: context, onChanged: this.onChanged);
_observer.onInitState();
}
[@override](/user/override)
void didChangeMetrics() {
super.didChangeMetrics();
_observer.onChangeMetrics();
}
[@override](/user/override)
void didChangeDependencies() {
super.didChangeDependencies();
_observer.onChangeDependencies();
}
[@override](/user/override)
void dispose() {
_observer.onDispose();
WidgetsBinding.instance!.removeObserver(this);
super.dispose();
}
}
示例
查看 这里 的完整示例代码。
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_widget_offset/flutter_widget_offset.dart';
void main() {
runApp(MyApp());
}
/// flutter_widget_offset 示例应用
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'OffsetDetector 示例',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'OffsetDetector 示例'),
);
}
}
/// flutter_widget_offset 示例应用主页
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
/// 主页标题
final String title;
[@override](/user/override)
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final OffsetDetectorController _controller = OffsetDetectorController();
final FocusNode _focusNode = FocusNode();
final LayerLink _layerLink = LayerLink();
late OverlayEntry? _overlayEntry;
double _overlayEntryWidth = 100.0;
double _overlayEntryHeight = 100.0;
double _overlayEntryY = double.minPositive;
AxisDirection _overlayEntryDir = AxisDirection.down;
bool _isOpened = false;
bool _isAboveCursor = false;
final EdgeInsets _textFieldContentPadding = EdgeInsets.fromLTRB(3, 8, 3, 8);
late TextStyle _textFieldStyle;
[@override](/user/override)
void initState() {
super.initState();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
WidgetsBinding.instance!.addPostFrameCallback((duration) {
if (mounted) {
_overlayEntry = OverlayEntry(
builder: (context) {
final suggestionsBox = Material(
elevation: 2.0,
color: Colors.yellow[200],
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: _overlayEntryHeight,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"弹出窗口",
style: TextStyle(fontSize: 32),
),
Switch(
value: _isAboveCursor,
onChanged: _onOverlayPositionChanged,
),
],
)
],
),
),
);
return Positioned(
width: _overlayEntryWidth,
child: CompositedTransformFollower(
link: _layerLink,
showWhenUnlinked: false,
followerAnchor: _overlayEntryDir == AxisDirection.down
? Alignment.topLeft
: Alignment.bottomLeft,
targetAnchor: Alignment.bottomLeft,
offset: Offset(0.0, _overlayEntryY),
child: suggestionsBox,
));
},
);
}
});
}
void _openSuggestionBox() {
if (this._isOpened) return;
assert(this._overlayEntry != null);
Overlay.of(context)!.insert(this._overlayEntry!);
this._isOpened = true;
}
void _closeSuggestionBox() {
if (!this._isOpened) return;
assert(this._overlayEntry != null);
this._overlayEntry!.remove();
this._isOpened = false;
}
void _updateSuggestionBox() {
if (!this._isOpened) return;
assert(this._overlayEntry != null);
this._overlayEntry!.markNeedsBuild();
}
void _onKeyboardState(bool state) {
if (state) {
_focusNode.requestFocus();
} else {
_focusNode.unfocus();
}
}
void _onOffsetChanged(Size size, EdgeInsets offset, EdgeInsets rootPadding) {
_overlayEntryWidth = size.width;
if (120 < offset.bottom || offset.top < offset.bottom) {
_overlayEntryDir = AxisDirection.down;
_overlayEntryHeight = offset.bottom - 10;
_overlayEntryY = 5;
} else {
_overlayEntryDir = AxisDirection.up;
if (_isAboveCursor) {
_overlayEntryHeight = offset.top + size.height - _courseHeight - 5;
_overlayEntryY = -_courseHeight;
} else {
_overlayEntryHeight = offset.top - 5;
_overlayEntryY = -size.height;
}
}
_updateSuggestionBox();
}
double get _courseHeight =>
(_textFieldStyle.fontSize ?? 16) +
_textFieldContentPadding.bottom +
_textFieldContentPadding.top;
void _onOverlayPositionChanged(bool value) {
setState(() {
this._isAboveCursor = value;
});
_onOffsetChanged(
_controller.size, _controller.offset, _controller.rootPadding);
}
void _onTextChanged(String value) {
if (value.isEmpty) {
_closeSuggestionBox();
} else {
if (_isOpened) {
_controller.notifyStateChanged();
} else {
_openSuggestionBox();
}
}
}
[@override](/user/override)
Widget build(BuildContext context) {
_textFieldStyle = Theme.of(context).textTheme.headline5!;
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: [
Switch(
value: _isAboveCursor,
onChanged: _onOverlayPositionChanged,
),
],
),
body: Center(
child: Padding(
padding: EdgeInsets.all(3),
child: CompositedTransformTarget(
link: _layerLink,
child: Container(
child: OffsetDetector(
controller: _controller,
onChanged: _onOffsetChanged,
onKeyboard: _onKeyboardState,
child: TextField(
focusNode: _focusNode,
minLines: 1,
maxLines: 5,
onChanged: _onTextChanged,
style: _textFieldStyle,
textInputAction: TextInputAction.none,
decoration: InputDecoration(
contentPadding: _textFieldContentPadding,
hintText: "写点什么",
),
)),
),
),
),
),
);
}
[@override](/user/override)
void dispose() {
_controller.dispose();
super.dispose();
}
}
更多关于Flutter控件偏移管理插件flutter_widget_offset的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter控件偏移管理插件flutter_widget_offset的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何使用 flutter_widget_offset
插件来管理 Flutter 控件偏移的示例代码。这个插件允许你动态地调整控件的位置,非常适合创建拖拽、动画效果或者任何需要精确控制控件位置的场景。
首先,确保你已经在 pubspec.yaml
文件中添加了 flutter_widget_offset
依赖:
dependencies:
flutter:
sdk: flutter
flutter_widget_offset: ^最新版本号 # 替换为最新的版本号
然后运行 flutter pub get
来获取依赖。
以下是一个简单的示例,展示了如何使用 flutter_widget_offset
来移动一个按钮:
import 'package:flutter/material.dart';
import 'package:flutter_widget_offset/flutter_widget_offset.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Widget Offset Example'),
),
body: OffsetWidgetExample(),
),
);
}
}
class OffsetWidgetExample extends StatefulWidget {
@override
_OffsetWidgetExampleState createState() => _OffsetWidgetExampleState();
}
class _OffsetWidgetExampleState extends State<OffsetWidgetExample> {
final GlobalKey _buttonKey = GlobalKey();
Offset _offset = Offset.zero;
void _handleDragUpdate(DragUpdateDetails details) {
setState(() {
_offset += details.delta;
});
}
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Center(
child: GestureDetector(
onPanUpdate: _handleDragUpdate,
child: OffsetWidget(
key: _buttonKey,
offset: _offset,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
child: Center(child: Text('Drag Me')),
),
),
),
),
],
);
}
}
在这个示例中,我们做了以下几件事:
- 添加依赖:在
pubspec.yaml
中添加了flutter_widget_offset
依赖。 - 创建主应用:在
MyApp
中定义了一个简单的 Material 应用,包含一个Scaffold
和一个AppBar
。 - 定义状态类:在
OffsetWidgetExample
中创建了一个有状态的小部件,用于管理按钮的偏移。 - 使用
GlobalKey
:为按钮定义了一个GlobalKey
,这样我们可以确保OffsetWidget
有一个唯一的标识符。 - 处理拖拽事件:使用
GestureDetector
监听拖拽更新事件,并在_handleDragUpdate
方法中更新按钮的偏移量。 - 使用
OffsetWidget
:将按钮包裹在OffsetWidget
中,并传入当前的偏移量_offset
。
这个示例展示了如何使用 flutter_widget_offset
插件来动态调整控件的位置。你可以根据需要进一步扩展这个示例,比如添加更多的控件、处理更多的手势事件或者结合动画效果等。