Flutter如何实现ListView.builder动态跟随RichText指定TextSpan显示
解决方案
实现思路
- 用
WidgetSpan包裹需要对齐的目标文本,绑定GlobalKey用于获取组件坐标 - 页面渲染完成后通过
GlobalKey读取目标文本的屏幕位置,动态更新列表的定位参数 - 文本内容变化重绘时会自动重新计算位置,适配动态RichText的使用场景
完整可运行代码
import 'package:flutter/material.dart'; class ListViewBuilderOverRichText extends StatefulWidget { ListViewBuilderOverRichText({Key? key}) : super(key: key); @override _ListViewBuilderOverRichTextState createState() => _ListViewBuilderOverRichTextState(); } class _ListViewBuilderOverRichTextState extends State<ListViewBuilderOverRichText> { final FocusNode _focusNode = FocusNode(); // 用于定位目标文本的GlobalKey final GlobalKey _targetTextKey = GlobalKey(); // 列表的定位偏移 double _listTop = 0; double _listLeft = 0; // 列表固定宽高,可根据需求调整 final double _listWidth = 200; final double _listHeight = 200; @override void dispose() { _focusNode.dispose(); super.dispose(); } // 更新列表位置 void _updateListPosition() { final RenderBox? renderBox = _targetTextKey.currentContext?.findRenderObject() as RenderBox?; if (renderBox == null) return; // 获取目标文本在屏幕上的坐标 final targetOffset = renderBox.localToGlobal(Offset.zero); setState(() { _listLeft = targetOffset.dx; // 若要显示在文本上方,改为 targetOffset.dy - _listHeight 即可 _listTop = targetOffset.dy + renderBox.size.height; }); } @override Widget build(BuildContext context) { // 每帧渲染完成后更新列表位置,适配文本动态变化的场景 WidgetsBinding.instance.addPostFrameCallback((_) => _updateListPosition()); final List<String> entries = <String>['A', 'B', 'C']; final List<int> colorCodes = <int>[100, 500, 600]; return Scaffold( appBar: AppBar( title: const Text('ListView.builder over RichText'), ), body: Stack( clipBehavior: Clip.none, children: [ Column( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 5.0), child: RichText( textAlign: TextAlign.left, text: TextSpan( text: 'dynamicaly changing text dynamicaly changing text dynamicaly changing text dynamicaly changing text dynamicaly changing text dynamicaly changing text dynamicaly changing text dynamicaly changing text dynamicaly changing text dynamicaly changing text dynamicaly changing text dynamicaly changing text', style: const TextStyle(color: Colors.black, fontSize: 18.0), children: <InlineSpan>[ // 目标文本用WidgetSpan包裹,绑定GlobalKey WidgetSpan( child: Text( ' ListView.builder', key: _targetTextKey, style: const TextStyle(color: Colors.red, fontSize: 18.0), ), ) ], ), ), ), ], ), Positioned( top: _listTop, left: _listLeft, width: _listWidth, height: _listHeight, child: Container( color: Colors.white, child: ListView.builder( padding: const EdgeInsets.all(8), itemCount: entries.length, itemBuilder: (BuildContext context, int index) { return Container( height: 50, color: Colors.amber[colorCodes[index]], child: Center(child: Text('Entry ${entries[index]}')), ); } ), ), ) ], ), ); } }
核心说明
WidgetSpan支持在RichText的文本树中插入普通Widget,是实现文本内特定片段定位的常用方案addPostFrameCallback会在当前帧渲染完成后触发,此时才能获取到组件的真实渲染坐标- 文本内容更新触发重绘时,会自动重新计算列表位置,无需额外手动处理适配逻辑
内容的提问来源于stack exchange,提问作者Jan
相关产品推荐
相关产品推荐

