Flutter自定义AppBar&自定义解析单标签HTML富文本

Flutter自定义AppBar&自定义解析单标签HTML富文本

初学Flutter,为了完成一个带有搜索栏、TabBar以及多个图标按钮的布局可以说是绞尽脑汁。原生的AppBar虽然拥有bottom属性,常用的方法也是配合TabBar一起使用,但是并没有能修改顶部的对应的属性,只能设置一个标题栏而已。对于flexibleSpace这个属性,是指处于AppBar和bottom之间的一个和AppBar等高的控件,大概关系是这样的:
Flutter自定义AppBar&自定义解析单标签HTML富文本_第1张图片而为了达到这种效果:
Flutter自定义AppBar&自定义解析单标签HTML富文本_第2张图片

显然是不太合适的,除非能把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起到了非常大的作用。最后不仅能实现上面的效果,也可以根据传入的值有所变化,比如
Flutter自定义AppBar&自定义解析单标签HTML富文本_第3张图片Flutter自定义AppBar&自定义解析单标签HTML富文本_第4张图片在布局上实现起来并不难,按照一开始的思路基本上还是比较容易完成的,由于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属性来使用。

单标签HTML文本转富文本(投机方法

这个问题其实比较奇怪,情况也有些特殊,我的解决方法也算是投机取巧吧,并没有普适性,但是单单对于这种情况,我认为还算是比较好的解决方法(主要是因为实在想不出其他的解决方法了)
情况是这样的,从网上获取的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数组。然后根据位置一个是红色文本,一个是普通文本。在最开始我还想到了一个问题,如果,原文本的最开始就是需要变红的字,还需要判断一下是按照红黑红黑来做,还是黑红黑红来做,比如
Flutter自定义AppBar&自定义解析单标签HTML富文本_第5张图片Flutter自定义AppBar&自定义解析单标签HTML富文本_第6张图片其实是不用做判断的,由于在分割的时候如果首位就是分割标记,分割后数组的0位置会是null,大概是这样的
Flutter自定义AppBar&自定义解析单标签HTML富文本_第7张图片也就是说只要按照黑红黑红的顺序进行style赋值就能实现,这一点一开始我是没有意识得到的
大概就这么多吧,其实回过头来看并没有很有特色的东西,也没有啥干货,虽然走了不少弯路,不过对于我来说也算是有了不错的收获。

你可能感兴趣的:(分享)