源计划--自定义AppBar

422 阅读2分钟

目前Flutter官方提供的AppBar很是强大放在国外应该够用,但是放在国内肯定如下图

内心毫无波浪,我们还是来看看目标和实现效果
当时的想法就是,这不是很简单嘛,AppBar里面不是有leading里面可传参数就是一个widget那我直接一个row放过去不就行了吗?

Scaffold(
      appBar: AppBar(
        title: const Text('标题'),
        leading: Row(
          children: <Widget>[Icon(Icons.keyboard_arrow_left), Text("广州")],
        ),
      ),
    );

一运行

根据leading我们找到了AppBar源码里面的一段

if (leading != null) {
      leading = ConstrainedBox(
        constraints: const BoxConstraints.tightFor(width: _kLeadingWidth),
        child: leading,
      );
    }
    
const double _kLeadingWidth = kToolbarHeight; // So the leading button is square.

可以说应为Google遵循Material风格把这个宽度写死了。

接下来我们分析一下AppBar的页面源码

class AppBar extends StatefulWidget implements PreferredSizeWidget {
...

@override
  Widget build(BuildContext context) {
  ...
  return Semantics(
      container: true,
      child: AnnotatedRegion<SystemUiOverlayStyle>(
        value: overlayStyle,
        child: Material(
          color: widget.backgroundColor
            ?? appBarTheme.color
            ?? theme.primaryColor,
          elevation: widget.elevation
            ?? appBarTheme.elevation
            ?? _defaultElevation,
          shape: widget.shape,
          child: Semantics(
            explicitChildNodes: true,
            child: appBar,
          ),
        ),
      ),
    );
  }
}

如上图我忽略一些暂时我们用不到的代码,这样一看其实就跟我们写的普通页面一模一样只是其中多了一些Semantics语义标签,Semantics语义标签这里先不做讲解。那么根据上面的图我们先建一个AppTitleBar的去实现PreferredSizeWidget

class AppTitleBar extends StatefulWidget implements PreferredSizeWidget {
  @override
  _AppTitleBarState createState() => _AppTitleBarState();

  @override
  // TODO: implement preferredSize
  Size get preferredSize => null;
}

class _AppTitleBarState extends State<AppTitleBar> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

preferredSize代表着限制子widget的大小,我们现在就拿着这个preferredSize回到AppBar里面搜索一下发现了这一句

preferredSize = Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)),

当bottom没有值传进来的时候直接就拿了kToolbarHeight,我们再点击进kToolbarHeight里面

/// The height of the toolbar component of the [AppBar].
const double kToolbarHeight = 56.0;

发现这就是一个写死的导航栏高度,那这就好办了

class AppTitleBar extends StatefulWidget implements PreferredSizeWidget {
  @override
  _AppTitleBarState createState() => _AppTitleBarState();

  @override
  Size get preferredSize => Size.fromHeight(kToolbarHeight);
}

class _AppTitleBarState extends State<AppTitleBar> {
  @override
  Widget build(BuildContext context) {
    return Container(color: Colors.red, child: Text("我是标题"), height: double.infinity);
  }
}

现在我们就完成了一个简易的AppBar,来运行一下

这怎么把我的标题放到了状态栏上面呢,我们再回去看看AppBar代码发现了下面这句

// The padding applies to the toolbar and tabbar, not the flexible space.
    if (widget.primary) {
      appBar = SafeArea(
        top: true,
        child: appBar,
      );
    }

只要我们把SafeArea套上去就ok了,接下来就是同志们自己根据喜好自定义AppBar咯