flutter 封装评论回复弹出键盘输入框组件
本来打算开启下一段作死旅行的,后来想了想还是先把flutter这段不堪回首的作死往事总结一下吧,好了,如之前的博文一样,不知不觉又开始废话连篇了。(ps:这么爱讲废话,不知道自己去日记)。
这次flutter重构之旅包含部分社区的功能,社区又怎么能少了正常的用户交流呢,所以一个评论回复而且能发图不发种,xx万人捅的发布组件呼之欲出。思来想去,似乎并没有想出更好的交互,只能学学你们口中不共戴天,水火不容的产品,所有的产品都是互相借鉴,于是我也去稍微借鉴借鉴目前流行的交互方式,无非就是点击弹出键盘和输入框,其他就没什么了。能不能设计得酷炫点,是看不起我们开发实现不了吗?
翻来翻去,手机上app都翻烂了,果然千篇一律,不亏为借鉴,可能借的人比较多哈。好了玩笑的话就不多说了,直接开始。
。。。。。。。。。思考再三,无从下手,此时的脸啪啪的疼。
搞错了,重来。
首先
先封装入口方法吧,用showDialog这个类往路由栈里插入一条记录,对于原生开发来说不比h5,原生开发所有的视图都是叠在一起的,只需要把背景搞透明就感觉像在一个页面一样,而h5往dom上插dom容易出事。总结:遇事不决跳页面。
void Function(BuildContext, Map<String, dynamic>, [dynamic]) comment =
(BuildContext context, Map<String, dynamic> params, [dynamic callback]) {
showDialog<Null>(
context: context, //BuildContext对象
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
router.back(context); //点击背景透明层,退出弹出框
},
child: CommentDialog(params: params, callback: callback),
);
});
};
然后
开始写输入框类,由于我这里需要用setData来更新视图,所以先提取CommentDialog继承与无状态StatelessWidget用作父组件,把有状态组件尽量封存在最小小部件CommentContent 里面。
class CommentDialog extends StatelessWidget {
final dynamic callback;
final Map<String, dynamic> params;
CommentDialog({Key key, @required this.callback, this.params})
: assert(callback != null),
super(key: key);
@override
Widget build(BuildContext context) {
return Material(
//创建透明层
type: MaterialType.transparency, //透明类型
child: GestureDetector(
onTap: () {
return false;
},
child: Stack(children: <Widget>[
Positioned(
left: 0,
right: 0,
bottom: MediaQuery.of(context).viewInsets.bottom > 0
? MediaQuery.of(context).viewInsets.bottom
: 0,
child: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 10.0, bottom: 6.0),
decoration: BoxDecoration(color: Colors.white),
child: CommentContent(
params: params,
callback: callback,
)))
])),
);
}
}
class CommentContentState extends State<CommentContent> {
String text;
String currentValue = '';
List<File> imgList = [];
List<String> imgPaths = [];
bool pending = false;
@override
void initState() {
super.initState();
}
addImage() {
pickImageFromCameraOrAlbum(context, 3 - imgList.length,
(PickImageResponse r) {
if (r.files != null) {
imgList.addAll(r.files);
}
if (r.paths != null) {
imgPaths.addAll(r.paths);
}
setState(() {});
});
}
comment() async {
if (pending == true) return;
pending = true;
final Map<String, dynamic> params = widget.params;
params['content'] = currentValue;
// 先上传七牛云
if (imgPaths != null && imgPaths.length != 0) {
final rr = await uploadQiNiu(imgPaths);
if (rr == null || rr.length == 0) return;
params['resource'] = json.encode(rr);
}
toast(context, '资源提交中,请勿重复点击或退出');
// 再评论
final r = await api.commentOrReply(params);
pending = false;
if (r.code == 1) {
toast(context, '评论成功');
if (widget.callback != null) {
widget.callback();
}
}
// 回复
router.back(context);
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
imgList.length == 0
? Padding(padding: EdgeInsets.only(right: 0.0))
: Container(
padding: EdgeInsets.only(bottom: 8.0),
child: Row(
children: imgList
.map(
(e) => Container(
height: 80.0,
width: 80.0,
margin: EdgeInsets.only(right: 8.0),
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(8.0)),
image: DecorationImage(
fit: BoxFit.cover,
image: FileImage(
e,
),
),
color: Color(0xFFF0F0F0),
),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
child: Align(
alignment: Alignment.topRight,
child: Opacity(
opacity: 0.3,
child: Container(
height: 13.0,
width: 13.0,
margin: EdgeInsets.all(4.0),
padding: EdgeInsets.all(1.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(0xff000000)),
child: Image.asset(
'lib/images/cancle-image.png',
height: 10.0,
width: 10.0,
),
),
)),
onTap: () {
imgList.remove(e);
setState(() {});
},
),
),
)
.toList(),
),
),
ConstrainedBox(
constraints: BoxConstraints(
minHeight: 40.0,
),
child: Container(
padding: EdgeInsets.only(left: 6.0, right: 6.0, bottom: 4.0),
decoration: BoxDecoration(color: Color(0xfff0f0f0)),
child: TextFormField(
decoration: InputDecoration(
hintText: "说点什么",
border: InputBorder.none,
),
style: TextStyle(fontSize: 14.0, color: Color(0xff606266)),
autofocus: true,
cursorColor: Color(0xff00c295),
scrollPadding: EdgeInsets.only(top: 0.0, bottom: 6.0),
minLines: 2,
maxLines: 3,
onChanged: (v) {
currentValue = v;
setState(() {});
},
),
),
),
Padding(padding: EdgeInsets.only(bottom: 4.0)),
Row(
children: <Widget>[
Container(
height: 30.0,
width: 32.0,
child: FlatButton.icon(
onPressed: addImage,
padding: EdgeInsets.all(0.0),
focusColor: Colors.white,
hoverColor: Colors.white,
highlightColor: Colors.white,
splashColor: Colors.white,
icon: Icon(
Icons.image,
color: Colors.grey,
),
label: Text(''))),
Expanded(child: Text('')),
Container(
height: 30.0,
width: 40.0,
child: FlatButton(
padding: EdgeInsets.all(0.0),
focusColor: Colors.white,
hoverColor: Colors.white,
highlightColor: Colors.white,
splashColor: Colors.white,
child: Text(
'发布',
style: TextStyle(
color: Color(currentValue.length == 0
? 0xffbcbcbc
: 0xff00c295)),
),
onPressed: () {
if (currentValue == '') return;
comment();
},
))
],
)
],
);
}
}
class CommentContent extends StatefulWidget {
final dynamic callback;
final Map<String, dynamic> params;
CommentContent({Key key, @required this.callback, this.params})
: assert(callback != null),
super(key: key);
@override
CommentContentState createState() => CommentContentState();
}
界面大概就是下面这个样子吧。
图片给我整的大大的,都好好欣赏下我大幂幂。
客人来了,服务肯定要做全套,舒舒服服的。
拍照+相册选择多张图,请看这一篇。
上传七牛云方法,请看这一篇。
仓库地址:传送门。
好了,废话也说完了,东西也实现了,广告也打完了,是时候说再见了。
难忘今宵,难忘~今宵。