初学Flutter,为了完成一个带有搜索栏、TabBar以及多个图标按钮的布局可以说是绞尽脑汁。原生的AppBar虽然拥有bottom属性,常用的方法也是配合TabBar一起使用,但是并没有能修改顶部的对应的属性,只能设置一个标题栏而已。对于flexibleSpace这个属性,是指处于AppBar和bottom之间的一个和AppBar等高的控件,大概关系是这样的:
而为了达到这种效果:
显然是不太合适的,除非能把AppBar的title替换掉。出于种种原因,还是下定决心自定义一个自己的AppBar,无非就是跟源码照猫画虎,改改属性布局嘛
大概分解一下这个AppBar,有这么几部分,首先是一个一列两行的结构,第一行分为两列,左侧是一个按钮,图标也可以,反正也是随时都可以添加手势识别,右侧是一个TextField,在TextField的右侧覆盖了一个图标(按钮),反正我的第一反应是TextField和右侧图标是个Stack布局,这样实现起来应该也比较容易。
第二行就比较简单了,可以直接用TabBar。那来看看代码。
class MyAppBarShow extends StatefulWidget implements PreferredSizeWidget{
//tabbar的controller,应与TabBarView的是同一个保证同时滑动
final TabController tabController;
//tabbar标题数据
final List tabs;
//根据TabBar页面变化选择是否显示筛选按钮
final bool showButton;
//左侧 "中"
final Image leftIcon;
//搜索栏叠加的麦克风
final Image rightIcon;
//筛选按钮,叠加在tabbar上
final Image tabRightIcon;
//输入框controller,获取输入文字和预设文字
final TextEditingController textEditingController;
MyAppBarShow({
Key key,
this.tabController,
this.tabs,
this.showButton,
@required this.textEditingController,
this.leftIcon,
this.rightIcon,
this.tabRightIcon,
}) : super(key: key);
@override
_MyAppBarState2 createState() => _MyAppBarState2();
@override
Size get preferredSize => new Size.fromHeight(kToolbarHeight);
}
首先肯定是一个StatefulWidget,要根据各种动作改变自身状态。而实现PreferredSizeWidget是因为在Scaffold中appBar的要求。对应的属性都有注释,需要注意的是,除了TextEditingController是必要属性外,其余都是可选的,而只传入一个TextEditingController的效果就是整个AppBar只有单独的一个搜索栏,根据传入的参数不同,布局也就会有变化,好在整体的布局并不复杂,实现起来也比较容易,其中Expanded起到了非常大的作用。最后不仅能实现上面的效果,也可以根据传入的值有所变化,比如
在布局上实现起来并不难,按照一开始的思路基本上还是比较容易完成的,由于Expanded的存在,使的这种Column和Row布局中子控件的延展性很强,我们要做的就是根据传入值进行判空然后返回一个大小为0的Container或者是对应的控件就可以了。以其中的第一行,也就是左侧图标、搜索栏以及搜索栏上的小图标为例
Padding(
padding: EdgeInsets.only(top(MediaQuery.of(context).padding.top+7)),
child: Row(
children: <Widget>[
widget.leftIcon == null?Container(height: 0.0,width: 0.0,):Padding(
padding: EdgeInsets.only(left: 15.0),
child: widget.leftIcon),
Expanded(
child: Container(
width: 305,
height: 40,
child: Stack(
alignment: AlignmentDirectional.centerEnd,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(12.0, 0.0, 15.0, 0.0),
child: TextField(
controller: widget.textEditingController,
onSubmitted: (text){
Navigator.pushNamed(context, "SerachResultActivity",arguments: text); },
cursorColor: Colors.black,
textInputAction: TextInputAction.search,//键盘右下角改为搜索
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius:
BorderRadius.all(Radius.circular(30)),
borderSide: BorderSide(
color: Color.fromRGBO(51, 51, 51, 0.5),
width: 1)),
focusedBorder: OutlineInputBorder(
borderRadius:
BorderRadius.all(Radius.circular(30)),
borderSide: BorderSide(
color: Color.fromRGBO(51, 51, 51, 0.5),
width: 1)),
contentPadding: EdgeInsets.fromLTRB(15.0, 12.0, 0.0, 12.0)),),),
Positioned(
child: widget.rightIcon == null?Container(height: 0.0,width: 0.0,):widget.rightIcon,right: 30.0,
)],)
)
),
],
),
)
不可避免的一堆缩进和括号…大概的情况就是,如果左侧图标为空,TextField外包裹的Expanded就会让TextField占据父控件尽可能大的空间,在这里就是填满整行。而TextField和右侧图标的关系我使用了Stack布局,让右侧的图标悬浮在TextField上面。关于TextField,提供了非常多的属性,由于没有提供搜索按钮的空间,所以必须要将键盘上右下角的按键改为搜索键,这就需要设置textInputAction为search,当然也有其他选择。
onSubmitted是确认按钮点击后的回调,可以进行传值,且点击后会关闭键盘。还有一个onEditingComplete也是确认按钮点击后的回调,但不同之处在于其不能传值,而且不会关闭键盘,这个方法可以用于在输入完毕后在两个不同的TextField间切换焦点。
对于第二行的TabBar就比较简单了,直接只用就可以了。值得一提的是在TabBar的右侧也有一个叠加的图标,根据TabBar的index的不同选择显示或者隐藏。而TabBar的显示与否是根据是否传入了标签数组也就是代码里面的tabs来决定的,而不是根据TabController。
而要使用这个AppBar,就像普通的AppBar一样使用即可。如果要进行一些额外的修饰,比如添加一些动画(AnimatedSwitcher)等等,需要在最外层再套一层PreferredSize才能作为Scaffold的appBar属性来使用。
这个问题其实比较奇怪,情况也有些特殊,我的解决方法也算是投机取巧吧,并没有普适性,但是单单对于这种情况,我认为还算是比较好的解决方法(主要是因为实在想不出其他的解决方法了)
情况是这样的,从网上获取的json数据中的一个字段包含了html标签,其中的字符会变成斜体,而需求是要将其中的字符颜色改为红色,常规体。如果单纯的解析html标签的话,flutter中可以使用三方库来解决,重新定义对应标签的style就可以,但是问题是其并不是与普通Text一样可以随便设置字体大小等等属性,只能使用固定的数值。由于数据中只有这样一组的标签,所以还是决定想想办法自己解析。
flutter中提供了富文本也就是RichText与TextSpan,可以设置不同的风格,TextSpan拥有一个children数组,存放TextSpan,也就是可以包含其他的TextSpan,那么其实只要把普通文本与要变红的文本分隔开,存入数组中再返回就解决了这个问题。还是看看代码吧
static List<String> getRichText(String str){
List<String> list;
RegExp regExp = new RegExp(r"|");
list = str.split(regExp);
return list;
}
static List<TextSpan> getResultTitle(List<String> title,double fontSize,int normalColor){
TextStyle normal = TextStyle(color: Color(normalColor),fontSize: fontSize,fontWeight: FontWeight.bold);
TextStyle red = TextStyle(color: Color(0xFFe71f26),fontSize: fontSize,fontWeight: FontWeight.bold);
List<TextSpan> widgetList = [];
for(int i = 0; i < title.length;i++){
if(i%2 == 0){
widgetList.add(new TextSpan(
style: normal,
text: title[i]
));
}else{
widgetList.add(new TextSpan(
style: red,
text: title[i]
));
}
}
return widgetList;
}
这部分其实没有进行过多的封装。在文本的分割部分,使用正则表达式根据或者来进行分割,存入一个String数组。然后根据位置一个是红色文本,一个是普通文本。在最开始我还想到了一个问题,如果,原文本的最开始就是需要变红的字,还需要判断一下是按照红黑红黑来做,还是黑红黑红来做,比如
其实是不用做判断的,由于在分割的时候如果首位就是分割标记,分割后数组的0位置会是null,大概是这样的
也就是说只要按照黑红黑红的顺序进行style赋值就能实现,这一点一开始我是没有意识得到的
大概就这么多吧,其实回过头来看并没有很有特色的东西,也没有啥干货,虽然走了不少弯路,不过对于我来说也算是有了不错的收获。