Flutter自定义渐变色AppBar

AppBar的属性

 AppBar({
    Key key,
    this.leading,
    this.automaticallyImplyLeading = true,
    this.title,
    this.actions,
    this.flexibleSpace,
    this.bottom,
    this.elevation,
    this.backgroundColor,
    this.brightness,
    this.iconTheme,
    this.textTheme,
    this.primary = true,
    this.centerTitle,
    this.titleSpacing = NavigationToolbar.kMiddleSpacing,
    this.toolbarOpacity = 1.0,
    this.bottomOpacity = 1.0,
  }) : assert(automaticallyImplyLeading != null),
       assert(elevation == null || elevation >= 0.0),
       assert(primary != null),
       assert(titleSpacing != null),
       assert(toolbarOpacity != null),
       assert(bottomOpacity != null),
       preferredSize = Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)),
       super(key: key);

大体思路就是继承一个 PreferredSize 类,内部通过 Container + decoration 实现自己需要的效果。(在 Scaffold 类中 appBar 参数需要一个实现 PreferredSizeWidget 的对象)

文章中的代码这里贴出来

Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new PreferredSize(
        child: new Container(
          padding: new EdgeInsets.only(
            top: MediaQuery.of(context).padding.top
          ),
          child: new Padding(
            padding: const EdgeInsets.only(
              left: 30.0,
              top: 20.0,
              bottom: 20.0
            ),
            child: new Text(
              'Arnold Parge',
              style: new TextStyle(
                fontSize: 20.0,
                fontWeight: FontWeight.w500,
                color: Colors.white
              ),
            ),
          ),
          decoration: new BoxDecoration(
            gradient: new LinearGradient(
              colors: [
                Colors.red,
                Colors.yellow
              ]
            ),
            boxShadow: [
              new BoxShadow(
                color: Colors.grey[500],
                blurRadius: 20.0,
                spreadRadius: 1.0,
              )
            ]
          ),
        ),
        preferredSize: new Size(
          MediaQuery.of(context).size.width,
          150.0
        ),
      ),
      body: new Center(
        child: new Text('Hello'),
      ),
    );
  }

AppBar内部实现

class AppBar extends StatefulWidget implements PreferredSizeWidget 

Appbar 继承了 StatefulWidget 实现了 PreferredSizeWidget ,所以我们直接看它的 State -> _AppBarState 。

直接去看 build 方法的返回,从后向前,看 AppBar 是如何实现的。

 @override
  Widget build(BuildContext context) {
    // 省略部分代码,后面再看
    ...

    final Brightness brightness = widget.brightness
      ?? appBarTheme.brightness
      ?? themeData.primaryColorBrightness;
    final SystemUiOverlayStyle overlayStyle = brightness == Brightness.dark
      ? SystemUiOverlayStyle.light
      : SystemUiOverlayStyle.dark;

    return Semantics( // 辅助功能相关
      container: true, 
      child: AnnotatedRegion<SystemUiOverlayStyle>( // 处理主题相关,状态栏文字颜色
        value: overlayStyle,
        child: Material( // Material 控件,处理颜色,阴影等效果
          color: widget.backgroundColor
            ?? appBarTheme.color
            ?? themeData.primaryColor,
          elevation: widget.elevation
            ?? appBarTheme.elevation
            ?? _defaultElevation,
          child: Semantics( // child里面才是真正的内容,我们看内部的appBar的实现。
            explicitChildNodes: true,
            child: appBar,
          ),
        ),
      ),
    );
  }

返回了一个控件,处理了明暗主题,颜色,阴影,子控件,这里我们不想用这个颜色,再通过查看 child 能否设置颜色。

这里的 appBar 是在上面定义的:

Widget appBar = ClipRect( // 用矩形剪辑其子widget
      child: CustomSingleChildLayout( // 通过deleagate 来约束子widget
        delegate: const _ToolbarContainerLayout(), // 这里的布局是一个宽充满,高度为kkToolbarHeight高度
        child: IconTheme.merge( // 处理IconTheme
          data: appBarIconTheme,// 通过判断,处理iconTheme的取值
          child: DefaultTextStyle( // 文字样式
            style: sideStyle, // 通过判断传入的textTheme处理style取值
            child: toolbar,
          ),
        ),
      ),
    );

这里可以看到,这里就是包装了一个 toolbar ,我们继续看 toolbar :

 // 这里是一个NavigationToolbar,我们设置的leading,title在这里使用
    final Widget toolbar = NavigationToolbar(
      leading: leading,
      middle: title,
      trailing: actions,
      centerMiddle: widget._getEffectiveCenterTitle(themeData),
      middleSpacing: widget.titleSpacing,
    );

关于 appBar 内部还进行一些处理,如处理 bottom ,增加 SafeArea 等处理,这里不做展开了

   if (widget.bottom != null) { // bottom
      appBar = Column(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: <Widget>[
          Flexible(
            child: ConstrainedBox(
              constraints: const BoxConstraints(maxHeight: kToolbarHeight),
              child: appBar,
            ),
          ),
          widget.bottomOpacity == 1.0 ? widget.bottom : Opacity(
            opacity: const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn).transform(widget.bottomOpacity),
            child: widget.bottom,
          ),
        ],
      );
    }

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

    appBar = Align( // Alignment.topCenter
      alignment: Alignment.topCenter,
      child: appBar,
    );

    if (widget.flexibleSpace != null) { // flexibleSpace效果
      appBar = Stack(
        fit: StackFit.passthrough,
        children: <Widget>[
          widget.flexibleSpace,
          appBar,
        ],
      );
    }

通过这里我们知道了,其实 AppBar 中,颜色是在 Material 中设置的,我们常用的设置是在 toolbar 中进行使用的,所以最简单的渐变色处理方式就是将 Material 的child 包一层做颜色处理,不去修改现有部分。
代码实现
代码很简单,将AppBar的代码拷贝出来进行修改,这里的类名为GradientAppBar。
在自定义的 GradientAppBar 的构造方法中增加渐变颜色的初始值,和终止值。

 GradientAppBar({
    ...
    this.gradientStart,
    this.gradientEnd,
  })  : assert(automaticallyImplyLeading != null),
        ...
        super(key: key);
  
  final Color gradientStart;
  final Color gradientEnd;

再将 _AppBarState 类的代码拷贝出来,这里的类名是 _GradientAppBarState (记得修改 createState 方法)。

然后在修改对 build 方法 return 中 child 进行包装,使用传入的颜色作为渐变色背景。

// 添加到build方法最后,return之前,通过使用decoration实现颜色的渐变
    if (widget.gradientStart != null && widget.gradientEnd != null) {
      appBar = Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
              colors: [widget.gradientStart, widget.gradientEnd]),
        ),
        child: appBar,
      );
    }

再进行处理 Material 的 颜色

 return Material(
      // 判断是否使用渐变色
      color: widget.gradientStart != null && widget.gradientEnd != null
          ? Colors.transparent
          : widget.backgroundColor ??
              appBarTheme.color ??
              themeData.primaryColor,
      elevation: widget.elevation ?? appBarTheme.elevation ?? _defaultElevation,
      child: appBar, // 使用包装后的appBar 
    );

这样就实现了渐变效果。

使用 GradientAppBar ,就是将原来使用 AppBar 替换为 GradientAppBar 。

return Scaffold(
      appBar: PreferredSize(
        child: GradientAppBar(
          gradientStart: Color(0xFF49A2FC),
          gradientEnd: Color(0xFF2171F5),
          title: Text(widget.title),
          leading: Icon(Icons.ac_unit),
        ),
        preferredSize: Size.fromHeight(400),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容

  • 自己总结的Android开源项目及库。 github排名https://github.com/trending,g...
    passiontim阅读 2,536评论 1 26
  • 各种帮助类汇总:https://github.com/Blankj/AndroidUtilCode 常用的 ios...
    懦弱的me阅读 1,221评论 0 51
  • 原文地址:http://www.android100.org/html/201606/06/241682.html...
    AFinalStone阅读 920评论 0 1
  • 年少吋,雨中漫步,或者与心仪的异性狂奔,总会吹出够美的长篇大论。矫情的话,其实就两字,浪漫!今天的我可不想与它亲密...
    张杏均阅读 703评论 2 1
  • 1、上午有2个面试 2、下午把产品分享的页面原型完成 3、然后花时间看混沌的课,写作业
    Katrina程阅读 149评论 0 1