Flutter實(shí)現(xiàn)自定義搜索框AppBar的示例代碼
介紹
開(kāi)發(fā)中,頁(yè)面頭部為搜索樣式的設(shè)計(jì)非常常見(jiàn),為了可以像系統(tǒng)AppBar那樣使用,這篇文章記錄下在Flutter中自定義一個(gè)通用的搜索框AppBar記錄。
功能點(diǎn): 搜索框、返回鍵、清除搜索內(nèi)容功能、鍵盤處理。
效果圖

實(shí)現(xiàn)步驟
首先我們先來(lái)看下AppBar的源碼,實(shí)現(xiàn)了PreferredSizeWidget類,我們可以知道這個(gè)類主要是控制AppBar的高度的,Scaffold腳手架里的AppBar的參數(shù)類型就是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 {
// 設(shè)置在不受約束下希望的大小
// 設(shè)置高度:Size.fromHeight(myAppBarHeight)
Size get preferredSize;
}為了方便擴(kuò)展,可以在Scaffold里使用,我們需要?jiǎng)?chuàng)建AppBarSearch類繼承有狀態(tài)StatefulWidget類并實(shí)現(xiàn)PreferredSizeWidget類,實(shí)現(xiàn)preferredSize方法,并設(shè)置高度。
class AppBarSearch extends StatefulWidget implements PreferredSizeWidget {
@override
Size get preferredSize => Size.fromHeight(height);
}因?yàn)?code>Scaffold對(duì)AppBar實(shí)現(xiàn)了狀態(tài)欄的適配,核心見(jiàn)下方源碼:
//獲取狀態(tài)欄高度 MediaQuery.of(context).padding.top;

這里我們直接返回AppBar,并進(jìn)行改造。(當(dāng)然這里也可以不返回AppBar我們自己處理狀態(tài)欄的高度也行)。
思路: AppBar title字段自定義輸入框,主要通過(guò)文本框監(jiān)聽(tīng)實(shí)現(xiàn)清除搜索內(nèi)容和顯示清除按鈕的功能,通過(guò)輸入框是否有焦點(diǎn)監(jiān)聽(tīng)進(jìn)行刷新布局,通過(guò)定義回調(diào)函數(shù)的方式來(lái)進(jìn)行搜索內(nèi)容的監(jiān)聽(tīng)。
// 輸入框控制
_controller = widget.controller ?? TextEditingController();
// 焦點(diǎn)控制
_focusNode = widget.focusNode ?? FocusNode();
// 焦點(diǎn)獲取失去監(jiān)聽(tīng)
_focusNode?.addListener(() => setState(() {}));
// 文本輸入監(jiān)聽(tīng)
_controller?.addListener(() => setState(() {}));鍵盤搜素監(jiān)聽(tīng):
只需設(shè)置TextField的這兩個(gè)屬性即可。
textInputAction: TextInputAction.search, onSubmitted: widget.onSearch, //輸入框完成觸發(fā)
鍵盤彈出收起處理:
在iOS中鍵盤的處理是需要我們自己來(lái)進(jìn)行處理的,我們需要的功能是點(diǎn)擊搜索框之外的地方失去焦點(diǎn)從而關(guān)閉鍵盤,這里我使用了處理鍵盤的一個(gè)插件:flutter_keyboard_visibility: ^5.1.0,在我們需要處理焦點(diǎn)事件頁(yè)面根布局使用KeyboardDismissOnTap外部包裹即可,這個(gè)插件還可以主動(dòng)控制鍵盤的彈出和收起,有興趣的小伙伴可以了解下。
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;
// 輸入框高度 默認(rèn)40
final double height;
// 默認(rèn)值
final String? value;
// 最前面的組件
final Widget? leading;
// 背景色
final Color? backgroundColor;
// 搜索框內(nèi)部后綴組件
final Widget? suffix;
// 搜索框右側(cè)組件
final List<Widget> actions;
// 輸入框提示文字
final String? hintText;
// 輸入框點(diǎn)擊回調(diào)
final VoidCallback? onTap;
// 清除輸入框內(nèi)容回調(diào)
final VoidCallback? onClear;
// 清除輸入框內(nèi)容并取消輸入
final VoidCallback? onCancel;
// 輸入框內(nèi)容改變
final ValueChanged<String>? onChanged;
// 點(diǎn)擊鍵盤搜索
final ValueChanged<String>? onSearch;
// 點(diǎn)擊右邊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; //是否獲取焦點(diǎn)
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 ?? "";
// 焦點(diǎn)獲取失去監(jiān)聽(tīng)
_focusNode?.addListener(() => setState(() {}));
// 文本輸入監(jiān)聽(tīng)
_controller?.addListener(() {
setState(() {});
});
super.initState();
}
// 清除輸入框內(nèi)容
void _onClearInput() {
setState(() {
_controller?.clear();
});
widget.onClear?.call();
}
// 取消輸入框編輯失去焦點(diǎn)
void _onCancelInput() {
setState(() {
_controller?.clear();
_focusNode?.unfocus(); //失去焦點(diǎn)
});
// 執(zhí)行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,
// 標(biāo)題與其他控件的間隔
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(
// 權(quán)重
flex: 1,
child: TextField(
autofocus: widget.autoFocus ?? false,
// 是否自動(dòng)獲取焦點(diǎn)
focusNode: _focusNode,
// 焦點(diǎn)控制
controller: _controller,
// 與輸入框交互控制器
//裝飾
decoration: InputDecoration(
isDense: true,
border: InputBorder.none,
hintText: widget.hintText ?? '請(qǐng)輸入關(guān)鍵字',
hintStyle: TextStyle(
fontSize: 14.sp, color: MyColors.color_666666),
),
style: TextStyle(
fontSize: 14.sp,
color: MyColors.color_333333,
),
// 鍵盤動(dòng)作右下角圖標(biāo)
textInputAction: TextInputAction.search,
onTap: widget.onTap,
// 輸入框內(nèi)容改變回調(diào)
onChanged: widget.onChanged,
onSubmitted: widget.onSearch, //輸入框完成觸發(fā)
),
),
_suffix(),
],
),
)),
actions: _actions(),
);
}
@override
void dispose() {
_controller?.dispose();
_focusNode?.dispose();
super.dispose();
}
}總結(jié)
整體設(shè)計(jì)思路還是非常簡(jiǎn)單的,主要就是通過(guò)兩個(gè)監(jiān)聽(tīng)來(lái)控制我們想要達(dá)到的交互效果,還有就是對(duì)dart中函數(shù)Funcation作為對(duì)象的加深理解,通過(guò)自定義搜索AppBar可以了解系統(tǒng)到AppBar的一些設(shè)計(jì)思路,這里主要還是記錄下我個(gè)人在做這個(gè)組件過(guò)程中的一個(gè)思路,希望對(duì)大家有所幫助~
到此這篇關(guān)于Flutter實(shí)現(xiàn)自定義搜索框AppBar的示例代碼的文章就介紹到這了,更多相關(guān)Flutter搜索框內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android開(kāi)發(fā)實(shí)現(xiàn)在Wifi下獲取本地IP地址的方法
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)在Wifi下獲取本地IP地址的方法,涉及Android編程Wifi的調(diào)用及IP地址的獲取與轉(zhuǎn)換相關(guān)操作技巧,需要的朋友可以參考下2017-09-09
Android App中用Handler實(shí)現(xiàn)ViewPager頁(yè)面的自動(dòng)切換
這篇文章主要介紹了Android App中用Handler實(shí)現(xiàn)ViewPager頁(yè)面的自動(dòng)切換的方法,類似于相冊(cè)自動(dòng)播放,主要是切換后要提示當(dāng)前頁(yè)面所在的位置,需要的朋友可以參考下2016-05-05
Android實(shí)現(xiàn)后臺(tái)服務(wù)拍照功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)后臺(tái)服務(wù)拍照功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05
Android實(shí)現(xiàn)雷達(dá)View效果的示例代碼
這篇文章主要介紹了Android實(shí)現(xiàn)雷達(dá)View效果,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06
Android實(shí)現(xiàn)app應(yīng)用多語(yǔ)言切換功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)app應(yīng)用多語(yǔ)言切換功能的相關(guān)資料,類似于微信的語(yǔ)言切換,感興趣的小伙伴們可以參考一下2016-08-08
Android 詳解ThreadLocal及InheritableThreadLocal
這篇文章主要介紹了Android 詳解ThreadLocal及InheritableThreadLocal的相關(guān)資料,需要的朋友可以參考下2017-01-01
Android 軟鍵盤出現(xiàn)不適應(yīng)的解決辦法總結(jié)
這篇文章主要介紹了Android 軟鍵盤出現(xiàn)不適應(yīng)的解決辦法總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-03-03
Android開(kāi)發(fā)Kotlin語(yǔ)言協(xié)程的依賴及使用示例
這篇文章主要為大家介紹了Android開(kāi)發(fā)Kotlin語(yǔ)言協(xié)程的依賴及使用示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
一個(gè)Activity中多個(gè)Fragment的切換
經(jīng)常會(huì)遇到在一個(gè)activity界面上布局多個(gè)fragment,但是如何從一個(gè)fragment跳轉(zhuǎn)到另一個(gè)fragment呢?本文主要對(duì)一個(gè)Activity中多個(gè)Fragment的切換進(jìn)行介紹,下面跟著小編一起來(lái)看下吧2017-01-01

