Flutter实现自定义搜索框AppBar的示例代码
作者:老李code 发布时间:2021-10-26 02:37:54
介绍
开发中,页面头部为搜索样式的设计非常常见,为了可以像系统AppBar
那样使用,这篇文章记录下在Flutter中自定义一个通用的搜索框AppBar记录。
功能点: 搜索框、返回键、清除搜索内容功能、键盘处理。
效果图
实现步骤
首先我们先来看下AppBar的源码,实现了PreferredSizeWidget
类,我们可以知道这个类主要是控制AppBar的高度的,Scaffold
脚手架里的AppBar的参数类型就是PreferredSizeWidget
类型。
class AppBar extends StatefulWidget implements PreferredSizeWidget{
...
preferredSize = _PreferredAppBarSize(toolbarHeight, bottom?.preferredSize.height),
...
/// {@template flutter.material.appbar.toolbarHeight}
/// Defines the height of the toolbar component of an [AppBar].
///
/// By default, the value of `toolbarHeight` is [kToolbarHeight].
/// {@endtemplate}
final double? toolbarHeight;
...
/// The height of the toolbar component of the [AppBar].
const double kToolbarHeight = 56.0;
}
abstract class PreferredSizeWidget implements Widget {
// 设置在不受约束下希望的大小
// 设置高度:Size.fromHeight(myAppBarHeight)
Size get preferredSize;
}
为了方便扩展,可以在Scaffold
里使用,我们需要创建AppBarSearch
类继承有状态StatefulWidget
类并实现PreferredSizeWidget
类,实现preferredSize
方法,并设置高度。
class AppBarSearch extends StatefulWidget implements PreferredSizeWidget {
@override
Size get preferredSize => Size.fromHeight(height);
}
因为Scaffold
对AppBar
实现了状态栏的适配,核心见下方源码:
//获取状态栏高度
MediaQuery.of(context).padding.top;
这里我们直接返回AppBar
,并进行改造。(当然这里也可以不返回AppBar我们自己处理状态栏的高度也行)。
思路: AppBar
title
字段自定义输入框,主要通过文本框监听实现清除搜索内容和显示清除按钮的功能,通过输入框是否有焦点监听进行刷新布局,通过定义回调函数的方式来进行搜索内容的监听。
// 输入框控制
_controller = widget.controller ?? TextEditingController();
// 焦点控制
_focusNode = widget.focusNode ?? FocusNode();
// 焦点获取失去监听
_focusNode?.addListener(() => setState(() {}));
// 文本输入监听
_controller?.addListener(() => setState(() {}));
键盘搜素监听:
只需设置TextField
的这两个属性即可。
textInputAction: TextInputAction.search,
onSubmitted: widget.onSearch, //输入框完成触发
键盘弹出收起处理:
在iOS中键盘的处理是需要我们自己来进行处理的,我们需要的功能是点击搜索框之外的地方失去焦点从而关闭键盘,这里我使用了处理键盘的一个插件:flutter_keyboard_visibility: ^5.1.0
,在我们需要处理焦点事件页面根布局使用KeyboardDismissOnTap外部包裹即可,这个插件还可以主动控制键盘的弹出和收起,有兴趣的小伙伴可以了解下。
return KeyboardDismissOnTap(
child: Material();
完整源码
/// 搜索AppBar
class AppBarSearch extends StatefulWidget implements PreferredSizeWidget {
AppBarSearch({
Key? key,
this.borderRadius = 10,
this.autoFocus = false,
this.focusNode,
this.controller,
this.height = 40,
this.value,
this.leading,
this.backgroundColor,
this.suffix,
this.actions = const [],
this.hintText,
this.onTap,
this.onClear,
this.onCancel,
this.onChanged,
this.onSearch,
this.onRightTap,
}) : super(key: key);
final double? borderRadius;
final bool? autoFocus;
final FocusNode? focusNode;
final TextEditingController? controller;
// 输入框高度 默认40
final double height;
// 默认值
final String? value;
// 最前面的组件
final Widget? leading;
// 背景色
final Color? backgroundColor;
// 搜索框内部后缀组件
final Widget? suffix;
// 搜索框右侧组件
final List<Widget> actions;
// 输入框提示文字
final String? hintText;
// 输入框点击回调
final VoidCallback? onTap;
// 清除输入框内容回调
final VoidCallback? onClear;
// 清除输入框内容并取消输入
final VoidCallback? onCancel;
// 输入框内容改变
final ValueChanged<String>? onChanged;
// 点击键盘搜索
final ValueChanged<String>? onSearch;
// 点击右边widget
final VoidCallback? onRightTap;
@override
_AppBarSearchState createState() => _AppBarSearchState();
@override
Size get preferredSize => Size.fromHeight(height);
}
class _AppBarSearchState extends State<AppBarSearch> {
TextEditingController? _controller;
FocusNode? _focusNode;
bool get isFocus => _focusNode?.hasFocus ?? false; //是否获取焦点
bool get isTextEmpty => _controller?.text.isEmpty ?? false; //输入框是否为空
bool get isActionEmpty => widget.actions.isEmpty; // 右边布局是否为空
bool isShowCancel = false;
@override
void initState() {
_controller = widget.controller ?? TextEditingController();
_focusNode = widget.focusNode ?? FocusNode();
if (widget.value != null) _controller?.text = widget.value ?? "";
// 焦点获取失去监听
_focusNode?.addListener(() => setState(() {}));
// 文本输入监听
_controller?.addListener(() {
setState(() {});
});
super.initState();
}
// 清除输入框内容
void _onClearInput() {
setState(() {
_controller?.clear();
});
widget.onClear?.call();
}
// 取消输入框编辑失去焦点
void _onCancelInput() {
setState(() {
_controller?.clear();
_focusNode?.unfocus(); //失去焦点
});
// 执行onCancel
widget.onCancel?.call();
}
Widget _suffix() {
if (!isTextEmpty) {
return InkWell(
onTap: _onClearInput,
child: SizedBox(
width: widget.height,
height: widget.height,
child: Icon(Icons.cancel, size: 22, color: Color(0xFF999999)),
),
);
}
return widget.suffix ?? SizedBox();
}
List<Widget> _actions() {
List<Widget> list = [];
if (isFocus || !isTextEmpty) {
list.add(InkWell(
onTap: widget.onRightTap ?? _onCancelInput,
child: Container(
constraints: BoxConstraints(minWidth: 48.w),
alignment: Alignment.center,
child: MyText(
'搜索',
fontColor: MyColors.color_666666,
fontSize: 14.sp,
),
),
));
} else if (!isActionEmpty) {
list.addAll(widget.actions);
}
return list;
}
@override
Widget build(BuildContext context) {
return AppBar(
backgroundColor: widget.backgroundColor,
//阴影z轴
elevation: 0,
// 标题与其他控件的间隔
titleSpacing: 0,
leadingWidth: 40.w,
leading: widget.leading ??
InkWell(
child: Icon(
Icons.arrow_back_ios_outlined,
color: MyColors.color_666666,
size: 16.w,
),
onTap: () {
Routes.finish(context);
},
),
title: Container(
margin: EdgeInsetsDirectional.only(end: 10.w),
height: widget.height,
decoration: BoxDecoration(
color: Color(0xFFF2F2F2),
borderRadius: BorderRadius.circular(widget.borderRadius ?? 0),
),
child: Container(
child: Row(
children: [
SizedBox(
width: widget.height,
height: widget.height,
child:
Icon(Icons.search, size: 20.w, color: Color(0xFF999999)),
),
Expanded(
// 权重
flex: 1,
child: TextField(
autofocus: widget.autoFocus ?? false,
// 是否自动获取焦点
focusNode: _focusNode,
// 焦点控制
controller: _controller,
// 与输入框交互控制器
//装饰
decoration: InputDecoration(
isDense: true,
border: InputBorder.none,
hintText: widget.hintText ?? '请输入关键字',
hintStyle: TextStyle(
fontSize: 14.sp, color: MyColors.color_666666),
),
style: TextStyle(
fontSize: 14.sp,
color: MyColors.color_333333,
),
// 键盘动作右下角图标
textInputAction: TextInputAction.search,
onTap: widget.onTap,
// 输入框内容改变回调
onChanged: widget.onChanged,
onSubmitted: widget.onSearch, //输入框完成触发
),
),
_suffix(),
],
),
)),
actions: _actions(),
);
}
@override
void dispose() {
_controller?.dispose();
_focusNode?.dispose();
super.dispose();
}
}
来源:https://juejin.cn/post/7085976335398486029
猜你喜欢
- 本文实例讲述了C#实现去除Strings中空格的方法,分享给大家供大家参考。具体实现方法如下:一般来说,你或许知道你能使用String.Tr
- 前言CyclicBarrier和CountDownLatch这两个工具都是在java.util.concurrent包下,并且平时很多场景都
- 写在前面并发编程一直都存在,只不过过去的很长时间里,比较难以实现,随着互联网的发展,人口红利的释放,更加友好的支持并发编程已经成了主流编程语
- 最近部门打算优化下 APP 在低端机上的卡顿情况,既然想优化,就必须获取卡顿情况,那么如何获取卡顿情况就是本文目的。一般主线程过多的 UI
- 一、FeignClient注解FeignClient注解被@Target(ElementType.TYPE)修饰,表示FeignClient
- 本文实例讲述了C#实现农历日历的方法。分享给大家供大家参考。具体实现方法如下://天干 private static
- 背景最近让我做一个大数据的系统,分析了一下,麻烦的地方就是多数据源切换抽取数据。考虑到可以跨服务器跨数据库抽数,再整理数据,就配置了这个动态
- 用注解实现Mybatis插入数据返回自增的主键Id我们在数据库表设计的时候,一般都会在表中设计一个自增的id作为表的主键。这个id也会关联到
- 需求是需要在TextView前端加入一个标签展示。最终效果图如下:根据效果图,很容易就能想到使用SpannableStringBuilder
- 前言我曾经在一篇介绍 Compose Navigation 的文章 中提到了 Navigation 的状态保存实际是由 rememberSa
- 这一篇就着重写一下客户端的代码,客户端主要实现的有:启动后检测本地的xml文件,然后发送到服务器获取需要更新的文件以及版本列表。循环下载。下
- Android现在这么火,各种的设备也是琳琅满目,高中低等,大小屏幕都有,但是它始终未能达到iOS那样的令人称赞的卓越体验和性能,其操作的流
- 问题描述:解决:检查以上是否版本一致。不行就再检查下面的:如果上面的方法还是不行的话,就建议你改一下pom文件中的maven插件编译级别,可
- C# Path类—文件路径给定如下字符串类型filePath,表示一个文件路径:string filePath = &q
- JDK 中提供了一些对无状态协议请求(HTTP )的支持,下面我就将我所写的一个小例子(组件)进行描述:首先让我们先构建一个请求类(Http
- C#函数式程序设计之作用域在C#中,变量的作用域是严格确定的。其本质是所有代码生存在类的方法中、所有变量只生存于声明它们的模块中或者之后的代
- 方法1 :利用Struts 2的支持的可配置结果,可以达到过滤器的效果。Action的处理结果配置支持正则表达式。但是如果返回的对象是一个数
- JDK * 的过程JDK * 采用字节重组,重新生成对象来替代原始对象,以达到 * 的目的。JDK中有一个规范,在ClassPath下
- 前言spring框架作为JavaEE框架领域的一款重要的开源框架,在企业应用开发中有着很重要的作用,同时Spring框架及其子框架很多,所以
- 本文实例为大家分享了java文件读写工具类的具体代码,供大家参考,具体内容如下import java.io.BufferedInputStr