0x0 前言

在制作 Flutter App 新建一个抽屉的实例后,发现一个问题:打开抽屉发现状态栏是灰色的,主要抽屉头部我用图片展示,理想效果应该是抽屉头部图片偏移上面状态栏的高度。

flutter_drawer

0x1 思路

除了查看文档之外,更好的方法直接查看 Drawer 源码,看看它是如何实现的。由于头部灰色原因我想到可能发生在 Drawer 的第一个子级参数:DrawerHeader ,他的作用类似容器展示,直接查看它的源码:

// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/widgets.dart';

import 'debug.dart';
import 'divider.dart';
import 'theme.dart';

const double _kDrawerHeaderHeight = 160.0 + 1.0; // bottom edge

/// The top-most region of a material design drawer. The header's [child]
/// widget, if any, is placed inside a [Container] whose [decoration] can be
/// passed as an argument, inset by the given [padding].
///
/// Part of the material design [Drawer].
///
/// Requires one of its ancestors to be a [Material] widget. This condition is
/// satisfied by putting the [DrawerHeader] in a [Drawer].
///
/// See also:
///
///  * [UserAccountsDrawerHeader], a variant of [DrawerHeader] that is
///    specialized for showing user accounts.
///  * <https://material.io/design/components/navigation-drawer.html>
class DrawerHeader extends StatelessWidget {
  /// Creates a material design drawer header.
  ///
  /// Requires one of its ancestors to be a [Material] widget.
  const DrawerHeader({
    Key key,
    this.decoration,
    this.margin = const EdgeInsets.only(bottom: 8.0),
    this.padding = const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 8.0),
    this.duration = const Duration(milliseconds: 250),
    this.curve = Curves.fastOutSlowIn,
    @required this.child,
  }) : super(key: key);

  /// Decoration for the main drawer header [Container]; useful for applying
  /// backgrounds.
  ///
  /// This decoration will extend under the system status bar.
  ///
  /// If this is changed, it will be animated according to [duration] and [curve].
  final Decoration decoration;

  /// The padding by which to inset [child].
  ///
  /// The [DrawerHeader] additionally offsets the child by the height of the
  /// system status bar.
  ///
  /// If the child is null, the padding has no effect.
  final EdgeInsetsGeometry padding;

  /// The margin around the drawer header.
  final EdgeInsetsGeometry margin;

  /// The duration for animations of the [decoration].
  final Duration duration;

  /// The curve for animations of the [decoration].
  final Curve curve;

  /// A widget to be placed inside the drawer header, inset by the [padding].
  ///
  /// This widget will be sized to the size of the header. To position the child
  /// precisely, consider using an [Align] or [Center] widget.
  ///
  /// {@macro flutter.widgets.child}
  final Widget child;

  @override
  Widget build(BuildContext context) {
    assert(debugCheckHasMaterial(context));
    assert(debugCheckHasMediaQuery(context));
    final ThemeData theme = Theme.of(context);
    final double statusBarHeight = MediaQuery.of(context).padding.top;
    return Container(
      height: statusBarHeight + _kDrawerHeaderHeight,
      margin: margin,
      decoration: BoxDecoration(
        border: Border(
          bottom: Divider.createBorderSide(context),
        ),
      ),
      child: AnimatedContainer(
        padding: padding.add(EdgeInsets.only(top: statusBarHeight)),
        decoration: decoration,
        duration: duration,
        curve: curve,
        child: child == null ? null : DefaultTextStyle(
          style: theme.textTheme.body2,
          child: MediaQuery.removePadding(
            context: context,
            removeTop: true,
            child: child,
          ),
        ),
      ),
    );
  }
}

在第 79 行指示出这边这边有个获取状态栏高度的方法,然后再加其他一些边距,这边逻辑走一通没什么问题,那应该问题不出现在这边,那么则判断父类容器的问题,直接修改成:

Drawer buildDrawer(BuildContext context, String currentRoute) {
  return new Drawer(
    child: new Text('test')
  );
}

发现不会出现灰色状态栏的问题,初步判断原因在这里面,继续查看 ListView 里面的源码:

@override
List<Widget> buildSlivers(BuildContext context) {
  Widget sliver = buildChildLayout(context);
  EdgeInsetsGeometry effectivePadding = padding;
  if (padding == null) {
    final MediaQueryData mediaQuery = MediaQuery.of(context, nullOk: true);
    if (mediaQuery != null) {
      // Automatically pad sliver with padding from MediaQuery.
      final EdgeInsets mediaQueryHorizontalPadding =
          mediaQuery.padding.copyWith(top: 0.0, bottom: 0.0);
      final EdgeInsets mediaQueryVerticalPadding =
          mediaQuery.padding.copyWith(left: 0.0, right: 0.0);
      // Consume the main axis padding with SliverPadding.
      effectivePadding = scrollDirection == Axis.vertical
          ? mediaQueryVerticalPadding
          : mediaQueryHorizontalPadding;
      // Leave behind the cross axis padding.
      sliver = MediaQuery(
        data: mediaQuery.copyWith(
          padding: scrollDirection == Axis.vertical
              ? mediaQueryHorizontalPadding
              : mediaQueryVerticalPadding,
        ),
        child: sliver,
      );
    }
  }

  if (effectivePadding != null)
    sliver = SliverPadding(padding: effectivePadding, sliver: sliver);
  return <Widget>[ sliver ];
}
  • ListView 的属性 padding为空时,获取 MediaQueryData 的信息
  • 因为 ListView 的滚动方向默认为垂直,会使用 mediaQueryVerticalPadding
  • sliver 添加一层 MediaQuery,这个表明 sliver 的子部件会使用该 MediaQuery 的值,根据判断,子部件会使用 mediaQueryHorizontalPadding ,而上面的两个复制:
    mediaQueryHorizontalPadding => 将原有的 MediaQuerypadding 复制为 topbottom 都为 0 ,该值会被子部件使用,所以 DrawerHeader 使用了该值,导致 statusBarHeader0
    mediaQueryVerticalPadding => 将原有的 MediaQuerypadding 复制为 leftright 都为 0

所以添加 ListViewpadding 属性即可:

new Drawer(
    child: ListView(
      padding: EdgeInsets.zero,
      children: <Widget>[]
    )
)

flutter_drawer_fixed

安装Flutter环境
Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。
编写第一个Flutter App
从编写这篇博文就有个写个App的计划,大概是Flutter功能展示方向的App,相信在Github有了一大堆轮子项目,且当对我来说是一个锻炼技术的项目吧,希望在这个项目过程中能够给自己带来App开发思考和设计理念。