Flutter控件TextField使⽤踩坑记
selection⾃动跳转
问题描述:
Column(
children: <Widget>[
RaisedButton(
onPressed: () {
_ = "newText";
},
child: Text("click me"),
),
TextField(flutter开发app
controller: _controller,
autofocus: true,
),
],
)
复制代码
当点击按钮通过TextEditingController去修改TextField内容时TextField的游标会⾃动移动到最前端。
问题解决:
Column(
children: <Widget>[
RaisedButton(
onPressed: () {
_ = "newText";
//每次修改内容的时候需要再⼿动修改selection
_controller.selection = TextSelection.fromPosition(
TextPosition(offset: _length));
},
child: Text("click me"),
),
TextField(
controller: _controller,
autofocus: true,
),
],
)
复制代码
键盘覆盖输⼊框
问题描述:
笔者所在的项⽬采⽤的是混合开发模式(原⽣+Flutter),在Android端配置了⼀个FlutterActivity⽤于承载Flutter页⾯,但是在开发时发现在有TextField的界⾯键盘弹出时总是会覆盖输⼊框,于是笔者开启了漫长的踩坑之旅:
⾸先是去相关issue,⽆果
google后发现有开源⼤神写了⼀个辅助类, 于是发挥CV技能,⽆果。但是发现作者写了⼀⾏内容:
DEPRECATED. (Now integrated Into Flutter). Ensure Visible for Flutter. Makes sure TextField or other widgets are scrolled into view when they receive input focus. Just pass the focusNode provided to your TextField inside the builder.
意思是:不⽤再折腾了,Flutter已经⽀持了该特性....
因为笔者是Android开发,⾃然想到了Activity的adjustResize特性,于是尝试了⼀下,结果发现效果很棒,于是问题成功解决。
在默认创建FlutterApplication时系统默认创建的Activity配置如下:
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
复制代码
可以看到是配置了windowSoftInputMode的,所以采⽤官⽅配置不会遇到我的问题。
之后我把上⾯提到的EnsureVisible类仔细研究了⼀下,看看它究竟解决了什么问题以及怎么解决的:
⾸先是解决了什么问题:众所周知,adjustResize实现的效果其实是压缩了键盘上⽅的布局⾼度,但是布局的改变可能导致输⼊框部分或全部被挤到屏幕之外,这个时候它就会调⽤Scrollable的ensureVisible⽅法把输⼊框滚动到可见区
然后是如何做到的:EnsureVisible⽤到了WidgetsBinding这个类,当页⾯布局发⽣改变的时候通过WidgetsBindingObserver的回调获取新的页⾯状态,看代码:
class _EnsureVisibleState extends State<EnsureVisible> with WidgetsBindingObserver {  final FocusNode _focusNode = new FocusNode();
bool _alreadyScrolling = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void didChangeMetrics() {
WidgetsBinding.instance.addPostFrameCallback((_) {
//布局改变时回调
if (_focusNode.hasFocus && !_alreadyScrolling) {
final alignment = resolveAlignment();
if (alignment != null) {
_alreadyScrolling = true;
alignment: alignment,
duration: widget.duration,
curve: widget.curve,
).whenComplete(() => _alreadyScrolling = false);
}
}
});
}
@override
void dispose() {
veObserver(this);
super.dispose();
}
}
复制代码
在didChangeMetrics⽅法中获取需要滚动的参数resolveAlignment:
double resolveAlignment() {
if (widget.alignment == null) {
final RenderObject object = context.findRenderObject();
final RenderAbstractViewport viewport = RenderAbstractViewport.of(object);
if (viewport == null) {
// If we have no viewport we don't attempt to scroll.
return null;
}
ScrollableState scrollableState = Scrollable.of(context);
if (scrollableState == null) {
// If we can't find a ancestor Scrollable we don't attempt to scroll.
return null;
}
ScrollPosition position = scrollableState.position;
if (position.pixels > OffsetToReveal(object, 0.0).offset) {
// Move down to the top of the viewport
return 0.0;
}
else if (position.pixels < OffsetToReveal(object, 1.0).offset) {
// Move up to the bottom of the viewport
return 1.0;
}
else {
// No scrolling is necessary to reveal the child
return null;
}
}
else {
// Use supplied Alignment parameter.
return 0.5 + (0.5 * widget.alignment.y);
}
}
复制代码
当然,作者还引⼊了FocusNode,在键盘获取焦点的时候⼿动调⽤didChangeMetrics⽅法,但是我觉得并没有必要再多处理⼀次。扩展:根据上⾯的思路笔者写了⼀个监听键盘弹出隐藏事件的Widget:
import 'package:flutter/material.dart';
typedef KeyboardShowCallback = void Function(bool isKeyboardShowing); class KeyboardDetector extends StatefulWidget {
KeyboardShowCallback keyboardShowCallback;
Widget content;
KeyboardDetector({this.keyboardShowCallback, @t});
@override
_KeyboardDetectorState createState() => _KeyboardDetectorState();
}
class _KeyboardDetectorState extends State<KeyboardDetector>
with WidgetsBindingObserver {
@override
void initState() {
WidgetsBinding.instance.addObserver(this);
super.initState();
}
@override
void didChangeMetrics() {
super.didChangeMetrics();
WidgetsBinding.instance.addPostFrameCallback((_) {
print(MediaQuery.of(context).viewInsets.bottom);
setState(() {
widget.keyboardShowCallback
.call(MediaQuery.of(context).viewInsets.bottom > 0);
});
});
}
@override
void dispose() {
veObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
t;
}
}
复制代码
使⽤不再赘述~