search_input.dart 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import 'package:flutter/material.dart';
  2. class VSearchInput extends StatelessWidget {
  3. /// 占位提示
  4. final String? placeholder;
  5. /// 搜索回调
  6. final ValueChanged<String>? onSearch;
  7. /// 清空回调
  8. final VoidCallback? onClear;
  9. /// 是否可一键清空
  10. final bool? clearable;
  11. final TextEditingController? textEditingController;
  12. const VSearchInput({
  13. super.key,
  14. this.placeholder,
  15. this.onSearch,
  16. this.clearable,
  17. this.onClear,
  18. this.textEditingController,
  19. });
  20. @override
  21. Widget build(BuildContext context) {
  22. return LayoutBuilder(
  23. builder: (_, c) {
  24. final controller = textEditingController ?? TextEditingController();
  25. final radius = c.maxHeight / 2;
  26. final border = OutlineInputBorder(
  27. borderRadius: BorderRadius.all(Radius.circular(radius)),
  28. borderSide: BorderSide.none,
  29. );
  30. return TextField(
  31. style: const TextStyle(fontSize: 20),
  32. controller: controller,
  33. decoration: InputDecoration(
  34. enabledBorder: border,
  35. focusedBorder: border,
  36. fillColor: const Color.fromRGBO(238, 238, 238, 1),
  37. filled: true,
  38. hintText: placeholder,
  39. hintStyle: const TextStyle(
  40. fontSize: 20,
  41. color: Colors.black54,
  42. ),
  43. contentPadding: EdgeInsets.symmetric(
  44. horizontal: radius * 1.2,
  45. vertical: (c.maxHeight - 24) / 2,
  46. ),
  47. // isDense: true,
  48. isCollapsed: false,
  49. // prefixIcon: prefixIcon,
  50. // suffixIcon: suffixIcon,
  51. suffixIcon: Row(
  52. mainAxisSize: MainAxisSize.min,
  53. crossAxisAlignment: CrossAxisAlignment.center,
  54. children: [
  55. if (clearable == true)
  56. _ClearableSuffixIcon(
  57. controller: controller,
  58. onTap: onClear,
  59. ),
  60. IconButton(
  61. icon: const Icon(Icons.search, size: 36),
  62. onPressed: () {
  63. onSearch?.call(controller.text);
  64. },
  65. ),
  66. const SizedBox(width: 8),
  67. ],
  68. ),
  69. ),
  70. onSubmitted: (value) {
  71. onSearch?.call(value);
  72. },
  73. );
  74. },
  75. );
  76. }
  77. }
  78. class _ClearableSuffixIcon extends StatefulWidget {
  79. final TextEditingController controller;
  80. final VoidCallback? onTap;
  81. const _ClearableSuffixIcon({required this.controller, this.onTap});
  82. @override
  83. State<StatefulWidget> createState() => _ClearableSuffixIconState();
  84. }
  85. class _ClearableSuffixIconState extends State<_ClearableSuffixIcon> {
  86. late TextEditingController controller;
  87. bool isShowIcon = false;
  88. @override
  89. void initState() {
  90. controller = widget.controller;
  91. WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
  92. if (mounted) {
  93. controller.addListener(onTextUpdate);
  94. }
  95. });
  96. super.initState();
  97. }
  98. @override
  99. void dispose() {
  100. controller.removeListener(onTextUpdate);
  101. super.dispose();
  102. }
  103. void onTextUpdate() {
  104. final isNeedShow = controller.text.isNotEmpty;
  105. if (isShowIcon != isNeedShow) {
  106. setState(() {
  107. isShowIcon = isNeedShow;
  108. });
  109. }
  110. }
  111. @override
  112. Widget build(BuildContext context) {
  113. if (isShowIcon) {
  114. return IconButton(
  115. color: Colors.black,
  116. icon: Container(
  117. padding: const EdgeInsets.all(4),
  118. decoration: BoxDecoration(
  119. borderRadius: BorderRadius.circular(16),
  120. color: Colors.black26,
  121. ),
  122. child: const Icon(Icons.close, size: 22, color: Colors.white),
  123. ),
  124. onPressed: () {
  125. controller.clear();
  126. widget.onTap?.call();
  127. },
  128. );
  129. } else {
  130. return const SizedBox();
  131. }
  132. }
  133. }