48

Flutter 常用的布局与事件

 5 years ago
source link: https://www.jakeprim.cn/2019/03/26/flutter-1-1/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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,//高亮阴影
    @required this.onPressed,//点击事件
    this.mini: false//是否使用小图标
  })

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

QzQ3ArU.png!web
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,
    @required 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,
      @required this.isLoading,
      this.cover = false,
      @required this.child})
      : super(key: key);

  @override
  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;//默认是加载中的状态
 @override
  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;
  }
  
   @override
  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,
                    ),

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK