Я хочу создать текстовый редактор. Вышеуказанный экран можно реализовать с помощью Flutter.
Вот требования компании fxxxing.
sample1:
образец2:
- Этот белый курсор следует за пальцем пользователя.
- Текст на экране выделяется путем выбора его курсором.
- И выбранный текст может быть свободно отредактировано.
Вот мой код.
import 'package:exampleapp/common/fonts.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
const String TAG_PREFIX_CURSOR = 'START_CURSOR';
const String TAG_SUFFIX_CURSOR = 'END_CURSOR';
class TestPage extends StatefulWidget {
@override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
final _prefixCursor = Cursor(
offset: Offset(16.0, 16.0),
width: 32.0,
height: 32.0,
color: Colors.red,
tag: TAG_PREFIX_CURSOR,
);
final _suffixCursor = Cursor(
offset: Offset(16.0, 16.0),
width: 32.0,
height: 32.0,
color: Colors.blue,
tag: TAG_SUFFIX_CURSOR,
);
/// 화면에 보여질 데이터 리스트.
final List<String> _data = [
'동해 물과 백두산이 마르고 닳도록',
'하느님이 보우하사 우리나라 만세.',
'무궁화 삼천리 화려 강산',
'대한 사람, 대한으로 길이 보전하세.',
'대한 사람, 대한으로 길이 보전하세. 대한 사람, 대한으로 길이 보전하세.',
];
/// 현재 화면에 보여지고 있는 텍스트 위젯 리스트.
List<Widget> _texts = [];
/// 현재 커서가 가리키고 있는 값.
String _selectedValue = '';
@override
void initState() {
_texts = _data.map((e) => _buildItem(e)).toList();
super.initState();
}
Widget _buildItem(String text) {
return Container(
key: GlobalKey(),
padding: const EdgeInsets.fromLTRB(40.0, 8.0, 40.0, 8.0),
color: text == _selectedValue ? Colors.blue.withOpacity(0.2) : null,
child: SelectableText(
text,
style: TextStyle(
color: Colors.black,
fontSize: 24.0,
fontFamily: Fonts.yache,
),
),
);
}
/// Param [key]를 상속 중인 [Widget]의 [Offset]을 리턴.
Offset getOffset(GlobalKey key) {
final RenderBox renderBox = key.currentContext.findRenderObject();
return renderBox.localToGlobal(Offset.zero);
}
/// Param [key]를 상속 중인 [Widget]의 [Size]를 리턴.
Size getSize(GlobalKey key) {
final RenderBox renderBox = key.currentContext.findRenderObject();
return renderBox.size;
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Scaffold(
resizeToAvoidBottomPadding: false,
body: SingleChildScrollView(
child: SizedBox(
width: size.width,
height: size.height,
child: Stack(
children: [
Center(
child: Column(
children: [
SizedBox(height: 48.0),
..._texts,
],
),
),
_buildCursor(_prefixCursor),
_buildCursor(_suffixCursor),
],
),
),
),
);
}
/// 커서 위젯 생성.
Widget _buildCursor(Cursor item) {
final cursor = ({bool isDragging = false}) {
return Padding(
padding: EdgeInsets.only(top: item.offset.dy, left: item.offset.dx),
child: Icon(
Icons.create,
size: item.width,
color: isDragging ? item.feedback : item.color,
),
);
};
return Draggable(
data: item.tag,
child: cursor(),
feedback: cursor(isDragging: true),
childWhenDragging: cursor(),
onDragEnd: (DraggableDetails result) => _setCursorPosition(result, item),
);
}
/// 커서 위젯을 움직임에 따라 커서 객체의 좌표를 다시 계산하는 함수.
void _setCursorPosition(DraggableDetails result, Cursor cursor) {
_selectedValue = '';
final num dx = cursor.offset.dx + result.offset.dx;
final num dy = cursor.offset.dy + result.offset.dy;
cursor.offset = Offset(dx < 0.0 ? 0.0 : dx, dy < 0.0 ? 0.0 : dy);
final checkOffset = (Cursor cursor, Offset offset, Offset endOffset) {
final cursorPoint = Offset(cursor.offset.dx, cursor.offset.dy + cursor.height);
final bool checkedDx = (cursor.offset.dx >= offset.dx && endOffset.dx >= cursor.offset.dx);
final bool checkedDy = (cursorPoint.dy >= offset.dy && endOffset.dy >= cursorPoint.dy);
return (checkedDx && checkedDy);
};
_texts.map((Widget e) {
final Container container = e;
final SelectableText selectableText = container.child;
final Size size = getSize(e.key);
final Offset offset = getOffset(e.key);
final Offset endOffset = Offset(offset.dx + size.width, offset.dy + size.height);
// Checked offset.
if (checkOffset(_prefixCursor, offset, endOffset) && checkOffset(_suffixCursor, offset, endOffset)) {
_selectedValue = selectableText.data;
print(selectableText.data);
}
}).toList();
_texts = _data.map((e) => _buildItem(e)).toList();
setState(() => null);
}
}
class Cursor {
Offset offset = Offset(0.0, 0.0);
double width;
double height;
Color color;
Color feedback;
String tag;
Cursor({this.offset, this.width, this.height, this.color, this.tag}) {
this.feedback = color.withOpacity(0.5);
}
}
В моей голове больше нет методов реализации. Я знаю, что эта задача сложна для трепета, дайте совет, если у вас есть хорошая идея !!