Flutter 项目中常用的布局详情,及封装和使用,快速开发项目.

以及手势事件和滚动事件的使用

Scaffold 导航栏的实现,有些路由页可能会有抽屉菜单(Drawer)以及底部Tab导航菜单等

const Scaffold({
    Key key,
    this.appBar,//标题栏
    this.body,//内容
    this.floatingActionButton,//悬浮按钮
    this.persistentFooterButtons,//底部持久化现实按钮
    this.drawer,//侧滑菜单左
    this.endDrawer,//侧滑菜单右
    this.bottomNavigationBar,//底部导航
    this.backgroundColor,//背景颜色
    this.resizeToAvoidBottomPadding: true,//自动适应底部padding
    this.primary: true,//使用primary主色
  })

Flutter 中自带的material样式的标题栏,首先看一下AppBar具有哪些属性,代码如下:

AppBar({
    Key key,
    this.leading,//主导Widget
    this.automaticallyImplyLeading: true,
    this.title,//标题
    this.actions,//其他附加功能
    this.flexibleSpace,//伸缩空间,显示在title上面
    this.bottom,//显示在title下面
    this.elevation: 4.0,//阴影高度
    this.backgroundColor,//背景颜色
    this.brightness,//明暗模式
    this.iconTheme,//icon主题
    this.textTheme,//text主题
    this.primary: true,//是否是用primary
    this.centerTitle,//标题是否居中
    this.titleSpacing: NavigationToolbar.kMiddleSpacing,//title与leading的间隔
    this.toolbarOpacity: 1.0,//title级文字透明度
    this.bottomOpacity: 1.0,//底部文字透明度
  })

悬浮button 属性详解

const FloatingActionButton({
    Key key,
    this.child,//button的显示样式
    this.tooltip,//提示,长按按钮提示文字
    this.backgroundColor,//背景颜色
    this.heroTag: const _DefaultHeroTag(),//页面切换动画Tag
    this.elevation: 6.0,//阴影
    this.highlightElevation: 12.0,//高亮阴影
     this.onPressed,//点击事件
    this.mini: false//是否使用小图标
  })

底部导航栏BottomNavigationBar的实现,与经常搭配的PageView实现项目中常用的tab切换
image.png

Scaffold(
      body: PageView(
        controller: _controller,
        children: <Widget>[//page的页面
          HomePage(),
          SearchPage(),
          TravelPage(),
          MinePage(),
        ],
        onPageChanged: (int index) {//滑动page的监听
          setState(() {//改变tab状态
            _controllerIndex = index;
          });
        },
      ),
      bottomNavigationBar: BottomNavigationBar(
          currentIndex: _controllerIndex, //当前的index
          onTap: (index) {//点击tab
            _controller.jumpToPage(index); //跳转到具体的页面
            //注意改变_controllerIndex的状态
            setState(() {
              _controllerIndex = index;
            });
          },
          type: BottomNavigationBarType.fixed,//固定
          items: [//底部tab图片、字体及颜色
            homeItem(),
            searchItem(),
            travelItem(),
            mineItem(),
          ]),
    );

BottomNavigationBarItem的实现

BottomNavigationBarItem mineItem() {
  return BottomNavigationBarItem(
      icon: Icon(
        //定义默认状态下的图片以及颜色
        Icons.supervised_user_circle,
        color: _defaultColor,
      ),
      activeIcon: Icon(
        //定义选中状态下的图片以及颜色
        Icons.supervised_user_circle,
        color: _activityColor,
      ),
      title: Text(
        //定义文字
        '我的',
        style: TextStyle(
          color: _controllerIndex != 3 ? _defaultColor : _activityColor,
        ),
      ));
}

Container

Container({
       Key key,
       this.alignment,//内部widget对齐方式
    this.padding,//内边距
       Color color,//背景颜色,与decoration只能存在一个
    Decoration decoration,//背景装饰,与decoration只能存在一个
    this.foregroundDecoration//前景装饰,
    double width,//容器的宽
       double height,//容器的高
       BoxConstraints constraints//,
       this.margin,//外边距
       this.transform,//倾斜
       this.child,//子widget
})

alignment: 内部Widget对齐方式,左上对齐topLeft、垂直顶部对齐,水平居中对齐topCenter、右上topRight、垂直居中水平左对齐centerLeft、居中对齐center、垂直居中水平又对齐centerRight、底部左对齐bottomLeft、底部居中对齐bottomCenter、底部右对齐bottomRight

padding: 内间距,子Widget距Container的距离。

color: 背景颜色

decoration: 背景装饰

foregroundDecoration: 前景装饰

width:容器的宽

height:容器的高

constraints:容器宽高的约束,容器最终的宽高最终都要受到约束中定义的宽高影响

margin:容器外部的间隔

transform: Matrix4变换

child:内部子Widget

可以通过decoration装饰器实现圆角和边框,渐变等

decoration: BoxDecoration(
            border: Border(
                bottom:
                    BorderSide(width: 1, color: Color(0xfff2f2f2))), //设置底部分割线
          ),
          borderRadius: BorderRadius.circular(12), //设置圆角
                    gradient: LinearGradient(
                      colors: [
                        Color(0xffff4e63),
                        Color(0xffff6cc9),
                      ],
                      begin: Alignment.centerLeft,
                      end: Alignment.centerRight,
                    ), //
                    )

设置网络图片

Image.network(
                salesBoxModel.icon,
                fit: BoxFit.fill,
                height: 15,
              ),

设置行布局

 Column({
    Key key,
    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,//主轴X 排列方式
    MainAxisSize mainAxisSize = MainAxisSize.max,
    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,//纵轴排列方式
    TextDirection textDirection,
    VerticalDirection verticalDirection = VerticalDirection.down,
    TextBaseline textBaseline,
    List<Widget> children = const <Widget>[],
  })

设置列布局

Row({
    Key key,
    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
    MainAxisSize mainAxisSize = MainAxisSize.max,
    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
    TextDirection textDirection,
    VerticalDirection verticalDirection = VerticalDirection.down,
    TextBaseline textBaseline,
    List<Widget> children = const <Widget>[],
  }) 

设置内边距Padding

Padding 也是一个Widget,它内部可以包裹一个Widget

Padding(
              padding: EdgeInsets.only(top: 10),
              child: Text(
                model.title,
                style: TextStyle(
                  fontSize: 14,
                  color: Colors.white,
                ),
              ),
            )

设置宽度/高度撑满父布局FractionallySizedBox

FractionallySizedBox({
    Key key,
    this.alignment = Alignment.center,
    this.widthFactor,//设置为1 则宽度撑满父布局
    this.heightFactor,//设置为1 则高度撑满父布局
    Widget child,//包裹的子Widget
  })

Expanded撑满整个界面

Expanded({
    Key key,
    int flex = 1,
     Widget child,
  })

Stack 可以理解为栈布局,先放入的显示在最下面,后放入的显示在上面,跟Android中的ReaviteLayout相似

Stack({
    Key key,
    this.alignment: AlignmentDirectional.topStart,//对齐方式
    this.textDirection,
    this.fit: StackFit.loose,//是否按照父类宽高处理自己大小
    this.overflow: Overflow.clip,//溢出处理方式
    List<Widget> children: const <Widget>[],
  })

我们可以用Stack来实现:请求网络中的时候,显示加载中的布局;请求网络成功后,隐藏加载中的布局,显示成功的布局.
自定义一个LoadingWidget,传递isLoading是否正在加载中,child加载成功后显示的布局.这样的好处就是我们可以在任何需要用到加载中的布局时,直接使用,统一管理.使用setState来改变isLoading,来实现状态的改变.

class LoadingWidget extends StatelessWidget {
  final bool isLoading;
  final bool cover;
  final Widget child;

  //required必须传递的参数
  const LoadingWidget(
      {Key key,
       this.isLoading,
      this.cover = false,
       this.child})
      : super(key: key);

  
  Widget build(BuildContext context) {
    return !cover
        ? !isLoading ? child : _loadingView
        : Stack(
            children: <Widget>[child, isLoading ? _loadingView : null],
          );
  }

  Widget get _loadingView {
    return Center(
      child: CircularProgressIndicator(), //圆形的进度条
    );
  }
}

看一个简单调用的例子.

class _HomePageState extends State<HomePage> {
bool isLoading = true;//默认是加载中的状态
 
  void initState() {
    super.initState();
    _handleRefresh();
  }

  Future<Null> _handleRefresh() async {
    try {
      HomeModel model = await HomeDao.fetch();
      setState(() {
        gridNavList = model.localNavList;
        girdModeList = model.gridNav;
        subNavList = model.subNavList;
        salesBoxModel = model.salesBox;
        bannerList = model.bannerList;
        isLoading = false;
      });
    } catch (e) {
      print(e.toString());
      setState(() {
        isLoading = false;
      });
    }
    return null;
  }

   
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Color(0xfff2f2f2),
        body: LoadingWidget(//使用自定义的布局
          isLoading: isLoading,
          //加载成功后显示的View
          child: Stack(
          .......
          )
          )
          );
          }
}

当然,Stack还有很多其他的使用场景,可自行翻阅文档Stack

IndexedStack

只不过IndexedStack只显示指定位置的Widget,其他的位置的Widget不会显示。

PageView 类似Android中的ViewPage组件,他还可以实现底部导航栏的效果
Flutter官网PageView
首先看一下PageView有哪些属性,代码如下:

PageView({
    Key key,
    this.scrollDirection = Axis.horizontal,
    this.reverse = false,
    PageController controller,
    this.physics,
    this.pageSnapping = true,
    this.onPageChanged,
    List<Widget> children = const <Widget>[],
    this.dragStartBehavior = DragStartBehavior.down,
  }) : controller = controller ?? _defaultPageController,
       childrenDelegate = SliverChildListDelegate(children),
       super(key: key);

来看一下各个属性的意思
this.scrollDirection = Axis.horizontal,Axis.vertical//设置滚动方向 横向和竖向
pageSnapping true 带有阻力的滑动,如果设置为false滑动到哪就停止到哪
controller 页面控制器,通过调用jumpToPage 实现页面的跳转

BottomNavigationBar

 BottomNavigationBar({
    Key key,
    @required this.items,
    this.onTap,//点击事件
    this.currentIndex = 0,//当前的位置
    BottomNavigationBarType type,//底部固定和隐藏类型
    this.fixedColor,
    this.iconSize = 24.0,//图片的大小
  })

final List<BottomNavigationBarItem> items;

BottomNavigationBarItem 定义底部的icon 选中的icon 文字

 const BottomNavigationBarItem({
    @required this.icon,
    this.title,
    Widget activeIcon,
    this.backgroundColor,
  }) : activeIcon = activeIcon ?? icon,
       assert(icon != null);

底部固定

enum BottomNavigationBarType {
  /// The [BottomNavigationBar]'s [BottomNavigationBarItem]s have fixed width, always
  /// display their text labels, and do not shift when tapped.
  fixed,

  /// The location and size of the [BottomNavigationBar] [BottomNavigationBarItem]s
  /// animate and labels fade in when they are tapped. Only the selected item
  /// displays its text label.
  shifting,
}

手势事件GestureDetector

GestureDetector 手势监听,它可以包裹任何Widget并处理包裹Widget的点击、滑动、双击等事件,GestureDetector extends StatelessWidget 可以直接return Widget

来看一个Widget触发点击事件的例子

GestureDetector(
            onTap: () {
              CommonModel model = bannerList[index];
              Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (context) => WebView(
                            url: model.url,
                            title: model.title,
                            statusBarColor: model.statusBarColor,
                            hideAppBar: model.hideAppBar,
                          )));
            },
            child: Image.network(bannerList[index].icon,
                fit: BoxFit.fill), //加载网络图片,
          );

另外关于其他的双击、滑动等事件可自行翻阅文档.GestureDetector

滚动事件NotificationListener

NotificationListener 可用于监听所有Widget的滚动事件,不管使用何种Widget都可以很方便的进行处理

NotificationListener(
                      //滚动监听 list view
                      onNotification: (scrollNotification) {
                        //监听滚动的距离ScrollUpdateNotification 滚动时在进行回调
                        if (scrollNotification is ScrollUpdateNotification &&
                            scrollNotification.depth == 0) {
                          //只检测listview的滚动第0个元素widget时候才开始滚动
                          _scroll(scrollNotification.metrics.pixels);
                        }
                      },
                      child: _buildListView,
                    ),