在flutter中,设置了TextField的最大行数的时候,如果输入字数太多,导致文字滑动后,长按出现的选择复制弹框,会根据你所选的位置区域来上下滑动.如图所示:
查看相关源码这个弹框可以自定义,所用的属性为 selectionControls,看下注释:
/// {@macro flutter.widgets.editableText.selectionControls}
final TextSelectionControls? selectionControls;
他的子类分别为:
所以查看MaterialTextSelectionControls里的方法,看一下y轴的距离是怎么计算的
在build方法里可以发现:
复制代码到新的类里面,然后 修改代码,打印log看下是哪个值的问题:
var top = widget.globalEditableRegion.top;
var dy = startTextSelectionPoint.point.dy;
var textLineHeight = widget.textLineHeight;
debugPrint('_TextSelectionControlsToolbarState.build"\n top:$top \n dy:$dy \n textLineHeight:$textLineHeight');
final Offset anchorAbove = Offset(
widget.globalEditableRegion.left + widget.selectionMidpoint.dx,
top + dy - textLineHeight - _kToolbarContentDistance,
);
别忘了在TextField里使用自定义的控件.
I/flutter (15600): _TextSelectionControlsToolbarState.build"
I/flutter (15600): top:296.66666666666674
I/flutter (15600): dy:-55.90358289082843
I/flutter (15600): textLineHeight:19.0
I/flutter (15600): _TextSelectionControlsToolbarState.build"
I/flutter (15600): top:296.66666666666674
I/flutter (15600): dy:-52.236916224161746
I/flutter (15600): textLineHeight:19.0
I/flutter (15600): _TextSelectionControlsToolbarState.build"
I/flutter (15600): top:296.66666666666674
I/flutter (15600): dy:-50.57024955749512
I/flutter (15600): textLineHeight:19.0
I/flutter (15600): _TextSelectionControlsToolbarState.build"
I/flutter (15600): top:296.66666666666674
I/flutter (15600): dy:-48.2369162241618
I/flutter (15600): textLineHeight:19.0
I/flutter (15600): dy:8.763083775836321
I/flutter (15600): textLineHeight:19.0
I/flutter (15600): _TextSelectionControlsToolbarState.build"
I/flutter (15600): top:296.66666666666674
I/flutter (15600): dy:11.763083775836378
I/flutter (15600): textLineHeight:19.0
I/flutter (15600): _TextSelectionControlsToolbarState.build"
I/flutter (15600): top:296.66666666666674
I/flutter (15600): dy:14.09641710916975
I/flutter (15600): textLineHeight:19.0
I/flutter (15600): _TextSelectionControlsToolbarState.build"
I/flutter (15600): top:296.66666666666674
I/flutter (15600): dy:16.763083775836435
I/flutter (15600): textLineHeight:19.0
I/flutter (15600): _TextSelectionControlsToolbarState.build"
I/flutter (15600): top:296.66666666666674
I/flutter (15600): dy:19.096417109169806
I/flutter (15600): textLineHeight:19.0
I/flutter (15600): _TextSelectionControlsToolbarState.build"
I/flutter (15600): top:296.66666666666674
I/flutter (15600): dy:19.429750442504883
I/flutter (15600): textLineHeight:19.0
通过对比发现,dy值从负数到正数,对应着弹框从上到下.直接判断dy的值
if (dy < textLineHeight) {
dy = textLineHeight;
}
//弹框在下的时候判断
//2 为是最大的行数
if (dy > textLineHeight * 2) {
dy = textLineHeight * 2;
}
运行,完美解决.附上代码:
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
const double _kHandleSize = 22.0;
// Padding between the toolbar and the anchor.
const double _kToolbarContentDistanceBelow = _kHandleSize - 2.0;
const double _kToolbarContentDistance = 8.0;
/// Android Material styled text selection controls.
class MyTextSelectionControls extends TextSelectionControls {
/// Returns the size of the Material handle.
@override
Size getHandleSize(double textLineHeight) => const Size(_kHandleSize, _kHandleSize);
/// Builder for material-style copy/paste text selection toolbar.
@override
Widget buildToolbar(
BuildContext context,
Rect globalEditableRegion,
double textLineHeight,
Offset selectionMidpoint,
List<TextSelectionPoint> endpoints,
TextSelectionDelegate delegate,
ClipboardStatusNotifier clipboardStatus,
Offset? lastSecondaryTapDownPosition,
) {
return _TextSelectionControlsToolbar(
globalEditableRegion: globalEditableRegion,
textLineHeight: textLineHeight,
selectionMidpoint: selectionMidpoint,
endpoints: endpoints,
delegate: delegate,
clipboardStatus: clipboardStatus,
handleCut: canCut(delegate) ? () => handleCut(delegate, clipboardStatus) : null,
handleCopy: canCopy(delegate) ? () => handleCopy(delegate, clipboardStatus) : null,
handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null,
handleSelectAll: canSelectAll(delegate) ? () => handleSelectAll(delegate) : null,
);
}
/// Builder for material-style text selection handles.
@override
Widget buildHandle(BuildContext context, TextSelectionHandleType type, double textHeight,
[VoidCallback? onTap, double? startGlyphHeight, double? endGlyphHeight]) {
final ThemeData theme = Theme.of(context);
final Color handleColor = TextSelectionTheme.of(context).selectionHandleColor ?? theme.colorScheme.primary;
final Widget handle = SizedBox(
width: _kHandleSize,
height: _kHandleSize,
child: CustomPaint(
painter: _TextSelectionHandlePainter(
color: handleColor,
),
child: GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.translucent,
),
),
);
// [handle] is a circle, with a rectangle in the top left quadrant of that
// circle (an onion pointing to 10:30). We rotate [handle] to point
// straight up or up-right depending on the handle type.
switch (type) {
case TextSelectionHandleType.left: // points up-right
return Transform.rotate(
angle: math.pi / 2.0,
child: handle,
);
case TextSelectionHandleType.right: // points up-left
return handle;
case TextSelectionHandleType.collapsed: // points up
return Transform.rotate(
angle: math.pi / 4.0,
child: handle,
);
}
}
/// Gets anchor for material-style text selection handles.
///
/// See [TextSelectionControls.getHandleAnchor].
@override
Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight,
[double? startGlyphHeight, double? endGlyphHeight]) {
switch (type) {
case TextSelectionHandleType.left:
return const Offset(_kHandleSize, 0);
case TextSelectionHandleType.right:
return Offset.zero;
case TextSelectionHandleType.collapsed:
return const Offset(_kHandleSize / 2, -4);
}
}
@override
bool canSelectAll(TextSelectionDelegate delegate) {
// Android allows SelectAll when selection is not collapsed, unless
// everything has already been selected.
final TextEditingValue value = delegate.textEditingValue;
return delegate.selectAllEnabled &&
value.text.isNotEmpty &&
!(value.selection.start == 0 && value.selection.end == value.text.length);
}
}
// The label and callback for the available default text selection menu buttons.
class _TextSelectionToolbarItemData {
const _TextSelectionToolbarItemData({
required this.label,
required this.onPressed,
});
final String label;
final VoidCallback onPressed;
}
// The highest level toolbar widget, built directly by buildToolbar.
class _TextSelectionControlsToolbar extends StatefulWidget {
const _TextSelectionControlsToolbar({
Key? key,
required this.clipboardStatus,
required this.delegate,
required this.endpoints,
required this.globalEditableRegion,
required this.handleCut,
required this.handleCopy,
required this.handlePaste,
required this.handleSelectAll,
required this.selectionMidpoint,
required this.textLineHeight,
}) : super(key: key);
final ClipboardStatusNotifier clipboardStatus;
final TextSelectionDelegate delegate;
final List<TextSelectionPoint> endpoints;
final Rect globalEditableRegion;
final VoidCallback? handleCut;
final VoidCallback? handleCopy;
final VoidCallback? handlePaste;
final VoidCallback? handleSelectAll;
final Offset selectionMidpoint;
final double textLineHeight;
@override
_TextSelectionControlsToolbarState createState() => _TextSelectionControlsToolbarState();
}
class _TextSelectionControlsToolbarState extends State<_TextSelectionControlsToolbar> with TickerProviderStateMixin {
void _onChangedClipboardStatus() {
setState(() {
// Inform the widget that the value of clipboardStatus has changed.
});
}
@override
void initState() {
super.initState();
widget.clipboardStatus.addListener(_onChangedClipboardStatus);
widget.clipboardStatus.update();
}
@override
void didUpdateWidget(_TextSelectionControlsToolbar oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.clipboardStatus != oldWidget.clipboardStatus) {
widget.clipboardStatus.addListener(_onChangedClipboardStatus);
oldWidget.clipboardStatus.removeListener(_onChangedClipboardStatus);
}
widget.clipboardStatus.update();
}
@override
void dispose() {
super.dispose();
// When used in an Overlay, it can happen that this is disposed after its
// creator has already disposed _clipboardStatus.
if (!widget.clipboardStatus.disposed) {
widget.clipboardStatus.removeListener(_onChangedClipboardStatus);
}
}
@override
Widget build(BuildContext context) {
// If there are no buttons to be shown, don't render anything.
if (widget.handleCut == null &&
widget.handleCopy == null &&
widget.handlePaste == null &&
widget.handleSelectAll == null) {
return const SizedBox.shrink();
}
// If the paste button is desired, don't render anything until the state of
// the clipboard is known, since it's used to determine if paste is shown.
if (widget.handlePaste != null && widget.clipboardStatus.value == ClipboardStatus.unknown) {
return const SizedBox.shrink();
}
// Calculate the positioning of the menu. It is placed above the selection
// if there is enough room, or otherwise below.
final TextSelectionPoint startTextSelectionPoint = widget.endpoints[0];
final TextSelectionPoint endTextSelectionPoint =
widget.endpoints.length > 1 ? widget.endpoints[1] : widget.endpoints[0];
var top = widget.globalEditableRegion.top;
var dy = startTextSelectionPoint.point.dy;
var textLineHeight = widget.textLineHeight;
// 弹框在上的时候 判断
if (dy < textLineHeight) {
dy = textLineHeight;
}
//弹框在下的时候判断
//2 为是最大的行数
if (dy > textLineHeight * 2) {
dy = textLineHeight * 2;
}
debugPrint('_TextSelectionControlsToolbarState.build"\n top:$top \n dy:$dy \n textLineHeight:$textLineHeight');
final Offset anchorAbove = Offset(
widget.globalEditableRegion.left + widget.selectionMidpoint.dx,
top + dy - textLineHeight - _kToolbarContentDistance,
);
final Offset anchorBelow = Offset(
widget.globalEditableRegion.left + widget.selectionMidpoint.dx,
top + dy + _kToolbarContentDistanceBelow,
);
// Determine which buttons will appear so that the order and total number is
// known. A button's position in the menu can slightly affect its
// appearance.
assert(debugCheckHasMaterialLocalizations(context));
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final List<_TextSelectionToolbarItemData> itemDatas = <_TextSelectionToolbarItemData>[
if (widget.handleCut != null)
_TextSelectionToolbarItemData(
label: localizations.cutButtonLabel,
onPressed: widget.handleCut!,
),
if (widget.handleCopy != null)
_TextSelectionToolbarItemData(
label: localizations.copyButtonLabel,
onPressed: widget.handleCopy!,
),
if (widget.handlePaste != null && widget.clipboardStatus.value == ClipboardStatus.pasteable)
_TextSelectionToolbarItemData(
label: localizations.pasteButtonLabel,
onPressed: widget.handlePaste!,
),
if (widget.handleSelectAll != null)
_TextSelectionToolbarItemData(
label: localizations.selectAllButtonLabel,
onPressed: widget.handleSelectAll!,
),
];
// If there is no option available, build an empty widget.
if (itemDatas.isEmpty) {
return const SizedBox(width: 0.0, height: 0.0);
}
return TextSelectionToolbar(
anchorAbove: anchorAbove,
anchorBelow: anchorBelow,
children: itemDatas.asMap().entries.map((MapEntry<int, _TextSelectionToolbarItemData> entry) {
return TextSelectionToolbarTextButton(
padding: TextSelectionToolbarTextButton.getPadding(entry.key, itemDatas.length),
onPressed: entry.value.onPressed,
child: Text(entry.value.label),
);
}).toList(),
);
}
}
/// Draws a single text selection handle which points up and to the left.
class _TextSelectionHandlePainter extends CustomPainter {
_TextSelectionHandlePainter({required this.color});
final Color color;
@override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()..color = color;
final double radius = size.width / 2.0;
final Rect circle = Rect.fromCircle(center: Offset(radius, radius), radius: radius);
final Rect point = Rect.fromLTWH(0.0, 0.0, radius, radius);
final Path path = Path()
..addOval(circle)
..addRect(point);
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(_TextSelectionHandlePainter oldPainter) {
return color != oldPainter.color;
}
}
/// Text selection controls that follow the Material Design specification.
final TextSelectionControls materialTextSelectionControls = MyTextSelectionControls();