小菜在学习 Flutter 过程中需要处理一个类似 AndroidPopupWindow 效果的分享弹框页。看似很简单的页面,里面却有很多值得尝试的地方。

      小菜测试时主要用 GridViewBottomSheet 来实现的,当然也可以不用 GridView,小菜简单介绍一下这两组 Widget

【Flutter 专题】19 图解【分享页面】底部对话框 #yyds干货盘点#

GridView

      GridView 我们都很熟悉了,是日常中最常用到的控件之一,小菜前段时间学习了一下 ListView 的基本用法,GridView 的用法基本相同,小菜不再多说,只提醒一个属性,用来设置 GridView item 基本属性。

const SliverGridDelegateWithFixedCrossAxisCount({
   @required this.crossAxisCount, // 每行 item 个数
   this.mainAxisSpacing = 0.0,    // 列间距,即 item 左右间距
   this.crossAxisSpacing = 0.0,   // 行间距,即 item 上下间距
   this.childAspectRatio = 1.0,   // item 宽高比,默认1:1
})
Tips:注意设置 item 个数与列间距的配合,如果太大可能会造成页面展示不全等异常情况。

BottomSheet

      BottomSheet 小菜理解为是从底部向上弹的工作表,主要分为两种:

  1. Persistent 式工作表:类似于一个全新的页面,完全展示 ScaffoldState.showBottomSheet
    
    @override
    Widget build(BuildContext context) {
    return new Scaffold(
    appBar: new AppBar(
      title: new Text("分享页面"),
    ),
    body: new Center(
      child: new Builder(builder: (BuildContext context) {
        return new FlatButton(
            onPressed: () {
              showBottomSheet(
                  context: context,
                  builder: (BuildContext context) {
                    return _showNomalWid(context);
                  });
            },
            child: new Text("我要分享"),
            color: Colors.blue);
      }),
    ));
    }

Widget _showNomalWid(BuildContext context) {
return new Container(
// height: 320.0,
// color: Colors.greenAccent,
child: new GridView.builder(
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4, mainAxisSpacing: 5.0, childAspectRatio: 1.0),
itemBuilder: (BuildContext context, int index) {
return new Column(
children: <Widget>[
new Padding(
padding: EdgeInsets.fromLTRB(0.0, 6.0, 0.0, 6.0),
child: new Image.asset(
'images/${urlItems[index]}', width: 50.0, height: 50.0, fit: BoxFit.fill,
),
),
new Text(nameItems[index])
],
);
},
itemCount: nameItems.length,
),
);
}

1.1 若用 **showBottomSheet** 方式开启工作表,同时内容 **Widget** 不限制宽高,效果为新打开一个页面,点击空白区不会消失,如图:

![](https://s4.51cto.com/images/blog/202111/26082356_61a0291cc4a9c62029.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)

1.2 若此时设置内容 **Widget** 宽高,会发现依旧是重新打开一个页面,高度从底向上占据所设置高度,且点击空白区不会消失,如图:

![](https://s4.51cto.com/images/blog/202111/26082356_61a0291c790bb63327.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)

2. **Modal 式工作表**:是一个半透明的页面,默认占据屏幕一半 **ScaffoldState.showModalBottomSheet**。

@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("分享页面"),
),
body: new Center(
child: new Builder(builder: (BuildContext context) {
return new FlatButton(
onPressed: () {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return _showNomalWid(context);
});
},
child: new Text("我要分享"),
color: Colors.blue);
}),
));
}

2.1 若用 **showModalBottomSheet** 方式开启工作表,同时内容 **Widget** 不限制宽高,效果为打开一个半透明页面,默认占据屏幕一半,点击空白区工作表消失,如图:

![](https://s4.51cto.com/images/blog/202111/26082356_61a0291cc737727330.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)

2.2 若此时设置内容 **Widget** 宽高,会发现依旧是打开一个半透明页面,高度从底向上占据所设置高度,且点击空白区会消失,如图:

![](https://s4.51cto.com/images/blog/202111/26082356_61a0291ca1ad529082.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)

2.3 若此时设置内容 **Widget** 数据量很多,效果如何呢,这就是小菜选择用 **GridView** 的原因,在现有宽高内进行可滑动操作即可,如图:

![](https://s4.51cto.com/images/blog/202111/26082356_61a0291cbb8a546096.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)

#### 核心源码
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;小菜稍稍修饰了一下页面效果,主要源码如下:

import 'package:flutter/material.dart';

class SharePopup extends StatelessWidget {
List<String> nameItems = <String>[
'微信', '朋友圈', 'QQ', 'QQ空间', '微博', 'FaceBook', '邮件', '链接'
];
List<String> urlItems = <String>[
'icon_wechat.png', 'icon_wechat_moments.png', 'icon_qq.png', 'icon_qzone.png',
'icon_sina.png', 'icon_facebook.png', 'icon_email.png', 'icon_copylink.png'
];

@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("分享页面"),
),
body: new Center(
child: new Builder(builder: (BuildContext context) {
return new FlatButton(
onPressed: () {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return _shareWidget(context);
});
},
child: new Text("我要分享"),
color: Colors.blue);
}),
));
}

Widget _shareWidget(BuildContext context) {
return new Container(
height: 250.0,
child: new Column(
children: <Widget>[
new Padding(
padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0),
child: new Container(
height: 190.0,
child: new GridView.builder(
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
mainAxisSpacing: 5.0,
childAspectRatio: 1.0),
itemBuilder: (BuildContext context, int index) {
return new Column(
children: <Widget>[
new Padding(
padding: EdgeInsets.fromLTRB(0.0, 6.0, 0.0, 6.0),
child: new Image.asset( 'images/${urlItems[index]}', width: 50.0, height: 50.0, fit: BoxFit.fill, ) ),
new Text(nameItems[index])
],
);
},
itemCount: nameItems.length,
),
),
),
new Container( height: 0.5, color: Colors.blueGrey, ),
new Center( child: new Padding(
padding: EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 8.0),
child: new Text( '取 消', style: new TextStyle(fontSize: 18.0, color: Colors.blueGrey), ) ), )
],
),
);
}
}


***
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;小菜刚接触 **Flutter** 时间不长,还有很多不清楚和不理解的地方,如果有不对的地方还希望多多指教。

> 来源:阿策小和尚