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


猜你喜欢
- Android开发过程中,经常遇到一个项目需要重复的定义相同样式的标题栏,Android相继推出了actionBar, toolBar, 相
- 问题分析疑惑满满小枫听到这个面试题的时候,心想这是什么水面试官,怎么问这么简单的题目,心想一个for循环加上equal判断再删除不就完事了吗
- 以前的Android(4.1之前的版本)中,SDcard路径通过“/sdcard”或者“/mnt/sdcard”来表示,而在JellyBea
- 根据上下文环境,Java 的关键字 final 的含义有些微的不同,但通常它指的是“这是不能被改变的”。防止改变有两个原因:设计或效率。因为
- MVC三层架构我们在刚刚成为程序员的时候,就会被前辈们 “教育” 说系统的设计要遵循 MVC(Model-View-Controller)架
- 摘要今天用compose来构建一个气泡上升粘连动画和水滴下坠动画,Github源码点击这里知识点compose动画贝塞尔曲线缓动函数comp
- 曾经做过一个项目,其中登录界面的交互令人印象深刻。交互设计师给出了一个非常作的设计,要求做出包含根据情况可变色的下划线,左侧有可变图标,右侧
- AndroidStudio kotlin配置安装插件File -> Settings -> Plugins -> Brow
- c#判断代码是否执行超时一、使用委托delegate void dg();dg dgCase;void method(){ &n
- 项目地址:https://github.com/JeasonWong/SlackLoadingView老规矩,先上效果。图好大。。说下第一眼
- 前言本节我们将学习一下@PostConstruct的用法。概述@PostContruct是spring框架的注解,在方法上加该注解会在项目启
- 详解Kotlin的空指针处理Kotlin的空指针处理相比于java有着极大的提高,可以说是不用担心出现NullPointerExceptio
- 简介在有些场景中,我们会需要绘制一些高度定制化的组件,比如 UI 设计师给我们出了个难题 —— 弄一
- 一、简介使用了static 修饰符的方法为静态方法,反之则是非静态方法。 静态方法是一种特殊的成员方法,它不属于类的某一个具体的实
- 学C#的原因其实挺简单的,因为一直对游戏挺感兴趣,查了下比较流行的游戏引擎Unity的主要开发语言是C#,所以就决定从C#入手,学学面向对象
- https://www.jb51.net/article/191716.htm 此篇博文对flyway讲解的很清楚了,我在这只是稍
- 本文实例讲述了C#自定义处理xml数据类。分享给大家供大家参考。具体分析如下:这个C#类专门用户处理xml数据,可以大大简化xml的操作,类
- 这个是设置定时提醒的功能,即设置几点几分后提醒,用的是给系统设置个时间点,当系统时间到达设置的时间点的时候就会给我们发送一个广播,然后达到时
- 第一步:引入jar包 <dependency> <gro
- 一、封装一个工具类1、简易版package net.aexit.construct.acceptance.websky.utils;impo