[Flutter 实战] TextField Selection Bar (选择条) 滚动出界

在此记录项目中所遇到问题及解决方案

这几天发现TextField Selection Bar 竟然随着滚动条滚出界了,Goo 了 半天发现是Flutter 系统BUG。

但没有找到具体解决方案,只能自己想办法咯。

TextField 有提供一个控制:TextSelectionControls, 但不直接提供 Selection Bar 的 Show 和 Hide 方法,而是
放在TextSelectionDelegate里。TextSelectionControls 是 abstract 类,Android 系统是MaterialTextSelectionControls,
IOS系统是CupertinoTextSelectionControls。继承它们就可以了,并在ScrollController 里控制即可。

直接看代码更直观一点(以下代码是不完整的,请找关键代码使用)

class RecordCertificateFragmentState extends State
    with AutomaticKeepAliveClientMixin, WidgetsBindingObserver{

  final _scrollController = ScrollController();

  TextSelectionControls? _selectionControls;
  MyMaterialTextSelectionControls? _myTextSelectionControls;
  MyCupertinoTextSelectionControls? _myIOSTextSelectionControls;

  @override
  bool get wantKeepAlive => true;

  @override
  void initState() {
    super.initState();

  ///这里只是简单控制,只有滚动了就隐藏
    _scrollController?.addListener(() {
      _myIOSTextSelectionControls?.hide();
      _myTextSelectionControls?.hide();
    });

    if(Platform.isAndroid){
      _myTextSelectionControls = MyMaterialTextSelectionControls();
      _selectionControls = _myTextSelectionControls;
    }else if(Platform.isIOS){
      _myIOSTextSelectionControls = MyCupertinoTextSelectionControls();
      _selectionControls = _myIOSTextSelectionControls;
    }
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }


  @override
  Widget build(BuildContext context) {
    super.build(context);
    final padding = 40.w;
    final imageWidth = MediaQuery.of(context).size.width - padding * 2;
    final imageHeight = (imageWidth / 3) * 4;

    return AnnotatedRegion(
      value: SystemUiOverlayStyle.light,
      child: Scaffold(
        body: Stack(
          children: [

            Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Padding(
                  padding: EdgeInsets.only(top: 94.w, left: padding, right: padding),
                  child: Text(AppLocalizations.of(context)!.recordYourCertificate,
                    style: TextStyle(
                        color: Colors.white,
                        fontSize: 40.sp,
                        fontWeight: FontWeight.w600,height: 1.2
                    ),
                  ),
                ),

                SizedBox(height: 45.w,),

                Expanded(
                  child: SingleChildScrollView(
                   ///这里是滚动条的控制
                    controller: _scrollController,
                    child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Padding(
                            padding: EdgeInsets.symmetric(horizontal: 40.w),
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children:  _buildDetail(),
                            ),
                          ),
                        ]
                    ),
                  ),
                )
              ],
            ),

          ],
        ),
      ),
    );
  }

  List _buildDetail(){
    final children = [];

    _addEditRow(children, AppLocalizations.of(context)!.recordYourCertificateTopic, '',
        _topicController,false,0,focusNode: _topicFocusNode,selectionControls: _selectionControls);
    _addEditRow(children,
        AppLocalizations.of(context)!.recordYourCertificateCategory,
        AppLocalizations.of(context)!.editProfilePleaseSelect,
      _cateController, true,1
    );
    _addEditRow(children,
        AppLocalizations.of(context)!.recordYourCertificateName,
        '',
        _organNameController, false,0,focusNode: _organNameFocusNode,
        selectionControls: _selectionControls
    );
    _addEditRow(children,
        AppLocalizations.of(context)!.recordYourCertificateIssue,
        AppLocalizations.of(context)!.editProfilePleaseSelect,
        _issueDateController, true, 3
    );

    children.add(Text(AppLocalizations.of(context)!.recordYourCertificateUse,
      style: TextStyle(
        color: editTitleColor,
        fontSize: 12.sp,
        fontWeight: FontWeight.w700
    ),));
    children.add(SizedBox(height: 20.w,));
    children.add(Wrap(
      spacing: 7.w,
      runSpacing: 15.w,
      children: _buildTag(_useInformations),
    ));

    children.add(SizedBox(height: 22.w,));
    _addEditRow(children,
        AppLocalizations.of(context)!.recordYourCertificateDescription, '',
        _descriptionController,false,0,textFormFieldHeight: 105.w,alignment: Alignment.topLeft,
        focusNode: _descriptionFocusNode,selectionControls: _selectionControls);

    children.add(
      buildButton(AppLocalizations.of(context)!.recordYourCertificateCreate,
        margin: EdgeInsets.symmetric(vertical: 31.w),
        height: 72.w,
        fontSize: 20.sp,
        onPressed: () {

          //_submit();
          _intervalClick(1100,_submit);
        },
      ),
    );

    return children;
  }

  
  _addEditRow(List children,String title,String value,
      TextEditingController? controller,bool isSelected,int type,
      {double? textFormFieldHeight,Alignment? alignment,FocusNode? focusNode,
        TextSelectionControls? selectionControls,}){
    children.add(Text(title, style: TextStyle(
        color: editTitleColor,
        fontSize: 12.sp,
        fontWeight: FontWeight.w700
    ),));
    children.add(SizedBox(height: 6.w,));
    children.add(
        Stack(
          children: [
            Container(
              alignment: alignment??Alignment.center,
              width: double.infinity,
              height: textFormFieldHeight??56.w,
              decoration: BoxDecoration(
                color:  Colors.white,
                border: Border.all(width: 1.w, color: Color.fromARGB(255, 222, 222, 224)),
              ),
              padding: EdgeInsets.symmetric(horizontal:16.w),
              child: TextFormField(
                ///这里就是 selectionControls 
                selectionControls: selectionControls,
                controller: controller,
                focusNode: focusNode,
                enabled: !isSelected,
                style: TextStyle(
                  fontSize: 16.sp,
                  color: editValueColor,
                ),
                keyboardType: alignment!=null? TextInputType.multiline:null,
                minLines: alignment!=null? 3: null,
                maxLines: alignment!=null? 30: 1,
                decoration: InputDecoration(
                  border: InputBorder.none,
                ),
                buildCounter: (BuildContext context, { int? currentLength, int? maxLength, bool? isFocused }) => null,
              ),
            ),

            if(isSelected)
              Positioned(
                top: 8.w,
                right: 5.w,
                bottom: 8.w,
                child: Material(
                  color: Colors.transparent,
                  child: InkWell(
                    onTap: (){
                      _unfocusAll();

                      switch(type){
                        case 1:
                          _showCountiesCupertinoPicker();
                          break;
                        case 3:
                          _showCupertinoDatePicker();
                          break;
                      }
                    },
                    child: Image(
                      image: AssetImage('files/icon_chevron_down.png'),
                      width: 40.w,
                      height: 40.w,
                    ),
                  ),
                ),
              ),
          ],
        )
    );
    children.add(SizedBox(height: 15.w,));
  }


TextSelectionControls 子类:


import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

class MyMaterialTextSelectionControls extends MaterialTextSelectionControls {
  TextSelectionDelegate? myTextSelectionDelegate;
  @override
  Widget buildHandle(BuildContext context, TextSelectionHandleType type, double textLineHeight) {
    return Container();//super.buildHandle(context,type,textLineHeight);
  }

  @override
  Widget buildToolbar(BuildContext context, Rect globalEditableRegion, double textLineHeight, Offset position,
      List endpoints, TextSelectionDelegate delegate, ClipboardStatusNotifier clipboardStatus,
      Offset? lastSecondaryTapDownPosition) {
    myTextSelectionDelegate = delegate;
    return super.buildToolbar(context,globalEditableRegion,textLineHeight,position,endpoints,delegate,clipboardStatus,
        lastSecondaryTapDownPosition);
  }

  void hide(){
    debugPrint("myTextSelectionDelegate hideToolbar");
    myTextSelectionDelegate?.hideToolbar();
  }

  @override
  Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight) {
    return super.getHandleAnchor(type,textLineHeight);
  }

  @override
  Size getHandleSize(double textLineHeight) {
    //debugPrint("MyTextSelectionControls getHandleSize $textLineHeight");
    return super.getHandleSize(textLineHeight);
  }
}

class MyCupertinoTextSelectionControls extends CupertinoTextSelectionControls {
  TextSelectionDelegate? myTextSelectionDelegate;
  @override
  Widget buildHandle(BuildContext context, TextSelectionHandleType type, double textLineHeight) {
    return Container();//super.buildHandle(context,type,textLineHeight);
  }

  @override
  Widget buildToolbar(BuildContext context, Rect globalEditableRegion, double textLineHeight, Offset position,
      List endpoints, TextSelectionDelegate delegate, ClipboardStatusNotifier clipboardStatus,
      Offset? lastSecondaryTapDownPosition) {
    myTextSelectionDelegate = delegate;
    return super.buildToolbar(context,globalEditableRegion,textLineHeight,position,endpoints,delegate,clipboardStatus,
        lastSecondaryTapDownPosition);
  }

  void hide(){
    //debugPrint("myTextSelectionDelegate hideToolbar");
    myTextSelectionDelegate?.hideToolbar();
  }

  @override
  Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight) {
    //debugPrint("MyTextSelectionControls getHandleAnchor $textLineHeight");
    return super.getHandleAnchor(type,textLineHeight);
  }

  @override
  Size getHandleSize(double textLineHeight) {
    //debugPrint("MyTextSelectionControls getHandleSize $textLineHeight");
    return super.getHandleSize(textLineHeight);
  }
}

你可能感兴趣的:([Flutter 实战] TextField Selection Bar (选择条) 滚动出界)