FLUTTER_Я хочу создать текстовый редактор - PullRequest
0 голосов
/ 14 июля 2020

Я хочу создать текстовый редактор. Вышеуказанный экран можно реализовать с помощью Flutter.

Вот требования компании fxxxing.

sample1:

образец2:

  1. Этот белый курсор следует за пальцем пользователя.
  2. Текст на экране выделяется путем выбора его курсором.
  3. И выбранный текст может быть свободно отредактировано.

Вот мой код.

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);
  }
}

В моей голове больше нет методов реализации. Я знаю, что эта задача сложна для трепета, дайте совет, если у вас есть хорошая идея !!

...