123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- import 'package:flutter/material.dart';
- import 'package:flyinsono/lab/color/lab_colors.dart';
- import 'package:fis_theme/theme.dart';
- /// 可选的放置位置
- enum DisplayPosition {
- top,
- right,
- bottom,
- left,
- topRight,
- bottomRight,
- bottomLeft,
- topLeft,
- }
- /// 支持自定义的纯文本提示框组件
- class LabTextTooltip extends StatefulWidget {
- const LabTextTooltip({
- super.key,
- required this.message,
- required this.child,
- this.height = 26,
- this.position = DisplayPosition.top,
- this.margin = const EdgeInsets.all(0),
- this.padding = const EdgeInsets.symmetric(horizontal: 10),
- this.offset = const Offset(0, 0),
- this.decoration = defaultDecoration,
- this.textStyle = defaultTextStyle,
- this.disable = false,
- }) : super();
- final String message;
- final Widget child;
- final double height;
- final DisplayPosition position;
- final EdgeInsetsGeometry margin;
- final EdgeInsetsGeometry padding;
- final Offset offset;
- final Decoration decoration;
- final TextStyle textStyle;
- final bool disable;
- static const defaultTextStyle = TextStyle(
- color: LabColors.text200,
- fontSize: 12,
- );
- static const defaultDecoration = BoxDecoration(
- color: LabColors.hoverTextBgColor,
- borderRadius: BorderRadius.all(Radius.circular(4)),
- );
- @override
- State<LabTextTooltip> createState() => LabTextTooltipState();
- }
- class LabTextTooltipState extends State<LabTextTooltip> {
- OverlayEntry? _entry;
- /// 需要靠Bottom定位的情况
- static const List<DisplayPosition> NEED_BOTTOM = [
- DisplayPosition.top,
- DisplayPosition.topRight,
- DisplayPosition.topLeft,
- ];
- /// 需要靠Right定位的情况
- static const List<DisplayPosition> NEED_RIGHT = [
- DisplayPosition.left,
- DisplayPosition.topLeft,
- DisplayPosition.bottomLeft,
- ];
- /// 将容器扩宽来适应上下显示时的文本居中
- static const List<DisplayPosition> NEED_ENLARGE_WIDTH = [
- DisplayPosition.top,
- DisplayPosition.bottom,
- ];
- /// 默认情况下的文本大小
- static const double DEFAULT_TEXT_SIZE = 12;
- double get _maxMessageWidth =>
- widget.message.length * widget.textStyle.fontSize!;
- double get _paddingWidth => widget.padding.horizontal;
- double get _marginWidth => widget.margin.horizontal;
- double get _enlargeWidth => _maxMessageWidth + _paddingWidth + _marginWidth;
- /// 显示 Tooltip
- void _showTooltip() {
- _createNewEntry();
- }
- /// 隐藏 Tooltip
- void _hideTooltip() {
- _entry?.remove();
- _entry = null;
- }
- /// 创建新的 OverlayEntry
- void _createNewEntry() {
- final OverlayState overlayState = Overlay.of(
- context,
- debugRequiredFor: widget,
- );
- final RenderBox target = context.findRenderObject()! as RenderBox;
- final RenderBox targetConatiner =
- overlayState.context.findRenderObject() as RenderBox;
- final Offset targetOffset = target.localToGlobal(
- target.size.center(Offset.zero),
- ancestor: overlayState.context.findRenderObject(),
- );
- final Offset overlayOffset = _countOffset(
- targetConatiner.size, target.size, targetOffset, widget.position);
- final Offset edgeOffset =
- _edgeCorrection(targetConatiner.size, target.size, overlayOffset);
- final Widget overlay = _buildOverlay(edgeOffset, widget.position);
- _entry = OverlayEntry(builder: (BuildContext context) => overlay);
- overlayState.insert(_entry!);
- }
- /// 创建 Overlay 组件
- Widget _buildOverlay(Offset offset, DisplayPosition position) {
- return Directionality(
- textDirection: Directionality.of(context),
- child: Positioned.directional(
- top: NEED_BOTTOM.contains(position) ? null : offset.dy,
- start: NEED_RIGHT.contains(position) ? null : offset.dx,
- bottom: NEED_BOTTOM.contains(position) ? offset.dy : null,
- end: NEED_RIGHT.contains(position) ? offset.dx : null,
- textDirection: Directionality.of(context),
- child: SizedBox(
- width: NEED_ENLARGE_WIDTH.contains(position) ? _enlargeWidth : null,
- child: Center(
- child: UnconstrainedBox(
- child: Transform(
- transform: Matrix4.translationValues(
- widget.offset.dx,
- widget.offset.dy,
- 0,
- ),
- child: Container(
- decoration: widget.decoration,
- height: widget.height,
- padding: widget.padding,
- margin: widget.margin,
- child: Center(
- child: DefaultTextStyle(
- style: TextStyle(
- decoration: TextDecoration.none,
- fontFamily: FTheme.ins.localeSetting.fontFamily,
- ),
- child: Text(widget.message, style: widget.textStyle),
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- );
- }
- /// 计算位置布局
- Offset _countOffset(Size containerSize, Size targetSize, Offset targetOffset,
- DisplayPosition position) {
- final double x = targetOffset.dx;
- final double y = targetOffset.dy;
- final double w = targetSize.width;
- final double h = targetSize.height;
- final double cw = containerSize.width;
- final double ch = containerSize.height;
- switch (position) {
- case DisplayPosition.left:
- return Offset(cw - x + w / 2, y - widget.height / 2);
- case DisplayPosition.top:
- return Offset(x - _enlargeWidth / 2, ch - y + h / 2);
- case DisplayPosition.right:
- return Offset(x + w / 2, y - widget.height / 2);
- case DisplayPosition.bottom:
- return Offset(x - _enlargeWidth / 2, y + h / 2);
- case DisplayPosition.topRight:
- return Offset(x + w / 2, ch - y + h / 2);
- case DisplayPosition.bottomRight:
- return Offset(x + w / 2, y + h / 2);
- case DisplayPosition.bottomLeft:
- return Offset(cw - x + w / 2, y + h / 2);
- case DisplayPosition.topLeft:
- return Offset(cw - x + w / 2, ch - y + h / 2);
- }
- }
- /// 边缘矫正
- Offset _edgeCorrection(
- Size containerSize, Size targetSize, Offset targetOffset) {
- final double x = targetOffset.dx;
- final double y = targetOffset.dy;
- final double w = targetSize.width;
- final double h = targetSize.height;
- final double cw = containerSize.width;
- final double ch = containerSize.height;
- if (x < 0) {
- return Offset(0, y);
- }
- if (x + w > cw) {
- return Offset(cw - w, y);
- }
- if (y < 0) {
- return Offset(x, 0);
- }
- if (y + h > ch) {
- return Offset(x, ch - h);
- }
- return targetOffset;
- }
- @override
- Widget build(BuildContext context) {
- if (widget.disable) {
- return widget.child;
- }
- return MouseRegion(
- onEnter: (event) {
- _showTooltip();
- },
- onExit: (event) {
- _hideTooltip();
- },
- child: Container(
- child: widget.child,
- ),
- );
- }
- /// 组件入参更新时,如果 disable 为 true,则隐藏 Tooltip
- @override
- void didUpdateWidget(LabTextTooltip oldWidget) {
- super.didUpdateWidget(oldWidget);
- if (widget.disable) {
- _hideTooltip();
- }
- }
- @override
- void dispose() {
- super.dispose();
- if (_entry != null) {
- _entry?.remove();
- _entry = null;
- }
- }
- }
|