在我个人以为学习一门新的语言(快速高效学习) 一定是经过实践,最好的便是做项目,这儿我会简略写一个京东的Demo。
第一天 搭建项目框架,完成主页的功能:/editor/draf…
第二天完成 分类和产品列表页面: /post/704471…
Flutter-混合工程的持续集成实践: /post/704209…
Dart2.15版别发布了:mp.weixin.qq.com/s/g-1uCl3up…
前面两篇文章分别完结了主页和分类及产品列表页面功能,这篇文章完结产品概况页的功能,这儿用到了以下知识点:
用到的知识点
1. Provider 状况办理
什么是Provider 状况办理?
当咱们想在多个页面(组件/Widget)之间共享状况(数据),或许一个页面(组 件/Widget)中的多个子组件之间共享状况(数据),这个时分咱们就能够用 Flutter 中的状况办理来办理一致的状况(数据),完成不同组件直接的传值和数据共享。provider
是Flutter官方团队
推出的状况办理模式。
详细的运用:
- 装备
provider: ^6.0.1
, - 新建一个文件夹叫provider,在provider文件夹里边放咱们对于的状况办理类
- 在provider里边新建counter.dart
- counter.dart里边新建一个类承继
minxins
的ChangeNotifier
代码如下
import 'package:provider/provider.dart';
class Counter with ChangeNotifier {
int _count;
Counter(this._count);
void add() {
_count++;
notifyListeners();//2
}
get count => _count;//3
}
notifyListeners();
这个方法是告诉用到Counter
目标的widget刷新用的
- 找到main.dart修改代码,添加
MultiProvider
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
//装备设计稿的宽度高度
designSize: Size(750, 1334),
builder:()=> MultiProvider(
providers:[
ChangeNotifierProvider(create: (_) => Counter()),
],
child: MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate, // 指定本地化的字符串和一些其他的值
GlobalCupertinoLocalizations.delegate, // 对应的Cupertino风格
GlobalWidgetsLocalizations.delegate //指定默许的文本摆放方向, 由左到右或由右到左
],
supportedLocales: [
Locale("en"),
Locale("zh")
],
initialRoute: '/',
onGenerateRoute: onGenerateRoute)));
}
}
- 获取值、以及设置值
import 'package:provider/provider.dart';
import '../../provider/Counter.dart';
Widget build(BuildContext context) {
final counter = Provider.of<Counter>(context);
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: (){
counter.add();
},
),
body: Text("counter 的值:${counter.count}")
);
}
用Provider.of(context).count获取_count的值,Provider.of(context)相当于Provider去查找它办理的Counter(1);
用Provider.of(context).add();调用Counter()中的add()方法;
2. eventBus 播送
-
装备
event_bus: ^2.0.0
-
新建 event_bus.dart 类一致办理
//引进 eventBus 包文件
import 'package:event_bus/event_bus.dart';
//创立EventBus
EventBus eventBus = new EventBus();
//event 监听
class EventFn{
//想要接收的数据时什么类型的,就定义相同类型的变量
dynamic obj;
EventFn(this.obj);
}
- 在需求播送事情的页面引进上面的 EventBus.dart 类 然后装备如下代码
eventBus.fire(new EventFn('数据'));
- 在需求监听播送的当地引进上面的
event_bus.dart
类 然后装备如下代码
void initState() {
super.initState();
//监听播送
eventBus.on<EventFn>().listen((event){
print(event);
});
}
- event_bus撤销事情监听
@override
void dispose() {
super.dispose();
//撤销订阅
eventBusFn.cancel();
}
3. flutter_inappwebview 加载网页
- 装备
flutter_inappwebview: ^5.3.2
- 引进包文件
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
- 初始化特点
initialUrl: 被加载的初始URL。
initialOptions:将被加载的初始URL; initialOptions:将被运用的初始WebView选项。即将运用的初始WebView选项。
gestureRecognizers:指定哪些手势应该被WebView耗费。
initialData:初始InAppWebViewInitial数据。即将加载的InAppWebViewInitialData的初始数据,比方一个HTML字符串。
initialFile:将被加载的初始财物文件。
initialHeaders: 即将运用的初始头文件。即将运用的初始头文件。
contextMenu:上下文菜单,包含自定义菜单项。上下文菜单,包含自定义菜单项。
- 常用触发的事情
onLoadStart:当WebView开始加载一个URL时被触发的事情。
onLoadStop:当WebView完结加载一个URL时触发的事情。
onLoadHttpError:当WebView主页面收到一个HTTP错误时被触发的事情。
onConsoleMessage:当WebView收到JavaScript操控台音讯(如console.log ,console.error等)时触发的事情。
shouldOverrideUrlLoading:当当时WebView中的URL即将被加载时,给主机应用程序一个操控的机会。
onDownloadStart:当WebView识别到一个可下载的文件时发射的事情。
onReceivedHttpAuthRequest:当WebView接收到HTTP认证恳求时触发的事情。默许行为是撤销该恳求。
onReceivedServerTrustAuthRequest:当WebView需求执行服务器信任认证(证书验证)时被触发的事情。
onPrint:当window.print()从JavaScript端被调用时被触发的事情,默许行为是撤销恳求;onCreateWindow:当WebView需求进行服务器信任验证(证书验证)时被触发的事情。
onCreateWindow: 当InAppWebView恳求主机应用程序创立一个新窗口时,例如当企图翻开一个target="_blank"的链接或当window.open()被JavaScript端调用时,事情被触发。
- 简略运用
Expanded(
child: InAppWebView(
initialUrlRequest: URLRequest(url: Uri.parse("https://jdmall.itying.com/pcontent?id=${_id}")),
onProgressChanged: (InAppWebViewController controller, int progress){
if (progress / 100 > 0.9999) {
setState(() {
this._flag = false;
});
}
},
)
)
更多更详细用法的能够参阅这篇文章:/post/686929…
4. DefaultTabController和TabController
这两个都能够完成顶部导航选项卡,差异便是TabController一般放在有状况组件中运用,而DefaultTabController一般放在无状况组件中运用,这儿没有做成上下拉刷新,在这个页面用的是DefaultTabController。
TabController介绍
- 常见的特点
- 常用方法介绍
TabBar特点介绍
const TabBar({
Key key,
@required this.tabs,//有必要完成的,设置需求展现的tabs,最少需求两个
this.controller,
this.isScrollable = false,//是否需求翻滚,true为需求
this.indicatorColor,//选中下划线的色彩
this.indicatorWeight = 2.0,//选中下划线的高度,值越大高度越高,默许为2
this.indicatorPadding = EdgeInsets.zero,
this.indicator,//用于设定选中状况下的展现款式
this.indicatorSize,//选中下划线的长度,label时跟文字内容长度相同,tab时跟一个Tab的长度相同
this.labelColor,//设置选中时的字体色彩,tabs里边的字体款式优先级最高
this.labelStyle,//设置选中时的字体款式,tabs里边的字体款式优先级最高
this.labelPadding,
this.unselectedLabelColor,//设置未选中时的字体色彩,tabs里边的字体款式优先级最高
this.unselectedLabelStyle,//设置未选中时的字体款式,tabs里边的字体款式优先级最高
this.dragStartBehavior = DragStartBehavior.start,
this.onTap,//点击事情
})
DefaultTabController的运用
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: ScreenAdapter.width(400),
child: TabBar(
indicatorColor: Colors.red,
indicatorSize: TabBarIndicatorSize.label,
labelColor: Colors.red,
unselectedLabelColor: Colors.white,
tabs: [
Tab(
child: Text('产品', style: TextStyle(fontSize: 18,),),
),
Tab(
child: Text('概况', style: TextStyle(fontSize: 18),),),
Tab(
child: Text('点评', style: TextStyle(fontSize: 18),),
)
],
),
)
],
),
),
body:Stack(
children: [
TabBarView(children:
[
ProductContentFirst(_productContentList),
ProductContentSecond(_productContentList),
ProductContentThrid(),
]),
],
),
))
showModalBottomSheet 底部面板
ModalBottomSheet
底部面板,相当于弹出了一个新页面,有点类似于 ActionSheet
ModalBottomSheet的特点:
- context:BuildContext
- builder:WidgetBuilder
- backgroundColor:布景色
- elevation:暗影
- shape:形状
- barrierColor:遮盖布景色彩
- isDismissible:点击遮盖布景是否可消失
- enableDrag:下滑消失
BoxDecoration 的运用说明
BoxDecoration一般用于给Widget组件设置边框作用、暗影作用、渐变色等作用;常用特点如下:
完成作用
详细完成代码
创立product_content_model.dart
类
class ProductContentModel {
late ProductContentitem result;
ProductContentModel({
required this.result,
});
ProductContentModel.fromJson(Map<String, dynamic> json) {
result = ProductContentitem.fromJson(json['result']);
}
Map<String, dynamic> toJson() {
final _data = <String, dynamic>{};
_data['result'] = result.toJson();
return _data;
}
}
class ProductContentitem {
//可为空的字段就设置成可为空空
String? sId;
String? title;
String? cid;
Object? price;
Object? oldPrice;
Object? isBest;
Object? isHot;
Object? isNew;
late List<Attr> attr; //不可为空
Object? status;
late String pic; //不可为空
String? content;
String? cname;
int? salecount;
String? subTitle;
int count=1;
ProductContentitem({
this.sId,
this.title,
this.cid,
this.price,
this.oldPrice,
this.isBest,
this.isHot,
this.isNew,
required this.attr,
this.status,
required this.pic,
this.content,
this.cname,
this.salecount,
this.subTitle,
});
ProductContentitem.fromJson(Map<String, dynamic> json) {
sId = json['_id'];
title = json['title'];
cid = json['cid'];
price = json['price'];
oldPrice = json['old_price'];
isBest = json['is_best'];
isHot = json['is_hot'];
isNew = json['is_new'];
attr =
List<dynamic>.from(json['attr']).map((e) => Attr.fromJson(e)).toList();
status = json['status'];
pic = json['pic'];
content = json['content'];
cname = json['cname'];
salecount = json['salecount'];
subTitle = json['sub_title'];
}
Map<String, dynamic> toJson() {
final _data = <String, dynamic>{};
_data['_id'] = sId;
_data['title'] = title;
_data['cid'] = cid;
_data['price'] = price;
_data['old_price'] = oldPrice;
_data['is_best'] = isBest;
_data['is_hot'] = isHot;
_data['is_new'] = isNew;
_data['attr'] = attr.map((e) => e.toJson()).toList();
_data['status'] = status;
_data['pic'] = pic;
_data['content'] = content;
_data['cname'] = cname;
_data['salecount'] = salecount;
_data['sub_title'] = subTitle;
return _data;
}
}
class Attr {
late String cate;
late List<String> list;
Attr({
required this.cate,
required this.list,
});
Attr.fromJson(Map<String, dynamic> json) {
cate = json['cate'];
list = List<String>.from(json['list']);
}
Map<String, dynamic> toJson() {
final _data = <String, dynamic>{};
_data['cate'] = cate;
_data['list'] = list;
return _data;
}
}
产品概况页的框架页面
class ProductContentPage extends StatefulWidget {
final Map arguments;
ProductContentPage({Key? key, required this.arguments}) : super(key: key);
@override
_ProductContentPageState createState() => _ProductContentPageState();
}
class _ProductContentPageState extends State<ProductContentPage> {
List _productContentList=[];
@override
void initState() {
// TODO: implement initState
super.initState();
_getContentData();
}
//恳求产品数据
_getContentData() async{
var api ='${Config.domain}api/pcontent?id=${widget.arguments['id']}';
print(api);
var result = await Dio().get(api);
var productContent = new ProductContentModel.fromJson(result.data);
setState(() {
_productContentList.add(productContent.result);
});
}
@override
Widget build(BuildContext context) {
//完成顶部导航选项卡
return DefaultTabController(length: 3, child: Scaffold(
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: ScreenAdapter.width(400),
child: TabBar(
indicatorColor: Colors.red,
indicatorSize: TabBarIndicatorSize.label,
labelColor: Colors.red,
unselectedLabelColor: Colors.white,
tabs: [
Tab(
child: Text('产品', style: TextStyle(fontSize: 18,),),
),
Tab(
child: Text('概况', style: TextStyle(fontSize: 18),),
),
Tab(
child: Text('点评', style: TextStyle(fontSize: 18),),
)
],
),
)
],
),
actions: [
IconButton(onPressed: (){
//完成菜单选项栏
showMenu(context: context, position: RelativeRect.fromLTRB(ScreenAdapter.width(600), ScreenUtil().statusBarHeight+40, 10, 0), items:
[
PopupMenuItem(
child: Row(
children: [
Icon(Icons.home),
SizedBox(width: 10,),
Text('主页')
],
),
),
PopupMenuItem(
child: Row(
children: [
Icon(Icons.search),
SizedBox(width: 10,),
Text('查找')
],
),
)
]
);
}, icon: Icon(Icons.more_horiz)),
],
),
body: _productContentList.length > 0 ?
Stack(
children: [
TabBarView(children:
[
//产品页面
ProductContentFirst(_productContentList),
//概况页面
ProductContentSecond(_productContentList),
//点评页面
ProductContentThrid(),
]
),
//页面底部的购物车、参加购物车、当即购买
Positioned(
width: ScreenAdapter.width(750),
height: ScreenAdapter.width(100)+ScreenAdapter.bottomBarHeight+10,
bottom: 0,
child: Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(
width: 1,
color: Colors.black26
)
),
color: Colors.white
),
child: Container(
margin: EdgeInsets.only(top: 10, bottom: ScreenAdapter.bottomBarHeight),
child: Row(
children: [
Container(
width: 100,
height: ScreenAdapter.height(100),
child: Column(
children: [
Icon(Icons.shopping_cart, size: ScreenAdapter.width(38),),
Text('购物车', style: TextStyle(fontSize:ScreenAdapter.size(24))),
],
),
),
Expanded(
flex: 1,
child: CircleButton(
color: Color.fromRGBO(253, 1, 0, 0.9),
text: '参加购物车',
callBack: (){
print('参加购物车');
},
)
),
Expanded(
flex: 1,
child: CircleButton(
color: Color.fromRGBO(255, 165, 0, 0.9),
text: '当即购买',
callBack: (){
print('当即购买');
},
)
)
],
),
),
),
),
],
) : LoadingWidget(),
));
}
}
产品概况页的产品页面
class ProductContentFirst extends StatefulWidget {
final List _productContentList;
ProductContentFirst(this._productContentList, {Key? key}) : super(key: key);
@override
_ProductContentFirstState createState() => _ProductContentFirstState();
}
class _ProductContentFirstState extends State<ProductContentFirst> {
late ProductContentitem _productContent;
List _attr = [];
String _selectedValue='';
var cartProvider;
@override
void initState() {
// TODO: implement setState
super.initState();
_productContent = widget._productContentList[0];
_attr = _productContent.attr;
_selectedValue = _attr.first.list.first;
}
//完成选项卡功能
_attrBottomSheet(){
showModalBottomSheet(
context: context,
builder: (context){
return Stack(
children: [
Container(
padding: EdgeInsets.only(left: 10),
child: ListView(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: _getAttrWidget(),
),
Divider(),
Container(
margin: EdgeInsets.only(top: 10),
height: ScreenAdapter.height(80),
child: Row(
children: <Widget>[
Text("数量: ",
style: TextStyle(
fontWeight: FontWeight.bold)),
SizedBox(width: 10),
CartNum(this._productContent)
],
),
)
],
),
),
Positioned(
bottom: 0,
width: ScreenAdapter.width(750),
height: ScreenAdapter.height(76)+ScreenAdapter.bottomBarHeight,
child: Container(
color: Colors.white,
padding: EdgeInsets.only(bottom: ScreenAdapter.bottomBarHeight),
child: Row(
children: <Widget>[
Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.fromLTRB(10, 0, 0, 0),
child: CircleButton(
color: Color.fromRGBO(253, 1, 0, 0.9),
text: "参加购物车",
callBack: () async {
print('豪杰是八点就把手');
await CartServices.addCart(this._productContent);
//关闭底部挑选特点
Navigator.of(context).pop();
//调用Provider 更新数据
this.cartProvider.updateCartList();
Fluttertoast.showToast( msg: '参加购物车成功', toastLength: Toast.LENGTH_SHORT,gravity: ToastGravity.CENTER,);
},
),
),
),
Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.fromLTRB(10, 0, 10, 0),
child: CircleButton(
color: Color.fromRGBO(255, 165, 0, 0.9),
text: "当即购买",
callBack: () {
print('当即购买');
},
)),
)
],
),
),
)
],
);
}
);
}
List<Widget> _getAttrWidget(){
List<Widget> attrList = [];
_attr.forEach((attrItem) {
attrList.add(
Wrap(
children: [
Container(
width: ScreenAdapter.width(100),
child: Padding(
padding: EdgeInsets.only(top: ScreenAdapter.height(28)),
child: Text('${attrItem.cate}:', style: TextStyle(fontWeight: FontWeight.bold),textAlign: TextAlign.left),
),
),
Container(
width: ScreenAdapter.width(580),
child: Wrap(
children: _getAttrItemWidget(attrItem),
),
)
],
)
);
});
return attrList;
}
List<Widget> _getAttrItemWidget(attrItem) {
List<Widget> attrItemList = [];
attrItem.list.forEach((item) {
attrItemList.add(Container(
margin: EdgeInsets.all(10),
child: Chip(
label: Text("${item}"),
padding: EdgeInsets.all(10),
),
));
});
return attrItemList;
}
@override
Widget build(BuildContext context) {
this.cartProvider = Provider.of<Cart>(context);
//处理图片
String pic = Config.domain + this._productContent.pic;
pic = pic.replaceAll('\', '/');
//产品页面内容
return Container(
padding: EdgeInsets.all(10),
child: ListView(
children: [
AspectRatio(
aspectRatio: 16/9,
child: Image.network(pic, fit: BoxFit.cover,),
),
Container(
padding: EdgeInsets.only(top: 10),
child: Text(_productContent.title!,
style: TextStyle(color: Colors.black87, fontSize: ScreenAdapter.size(36), fontWeight: FontWeight.bold),),
),
Container(
padding: EdgeInsets.only(top: 10),
child: Text(
_productContent.subTitle!,
style: TextStyle(
color: Colors.black54,
fontSize: ScreenAdapter.size(28))
)
),
SizedBox(height: 10,),
Container(
child: Row(
children: [
Expanded(
child: Row(
children: [
Text('特价:'),
Text('${_productContent.price}',style: TextStyle(
color: Colors.red,
fontSize: ScreenAdapter.size(46))),
],
)
),
Expanded(
flex: 1,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text('原价:'),
Text('${_productContent.oldPrice}',style: TextStyle(
color: Colors.black38,
fontSize: ScreenAdapter.size(28),
decoration: TextDecoration.lineThrough)),
],
),
)
],
),
),
//挑选
_attr.length > 0
? Container(
margin: EdgeInsets.only(top: 10),
height: ScreenAdapter.height(80),
child: InkWell(
onTap: () {
_attrBottomSheet();
},
child: Row(
children: <Widget>[
Text("已选: ",
style: TextStyle(fontWeight: FontWeight.bold)),
Text("${_selectedValue}")
],
),
),
)
: Text(""),
Divider(),
Container(
height: ScreenAdapter.height(80),
child: Row(
children: <Widget>[
Text("运费: ", style: TextStyle(fontWeight: FontWeight.bold)),
Text("免运费")
],
),
),
Divider(),
],
),
);
}
}
这个便是代码中说的选项卡,也是经过接口返回数据生成的
产品概况页面
class ProductContentSecond extends StatefulWidget {
final List _productContentList;
const ProductContentSecond(this._productContentList, {Key? key}) : super(key: key);
@override
_ProductContentSecondState createState() => _ProductContentSecondState();
}
class _ProductContentSecondState extends State<ProductContentSecond> {
var _flag=true;
var _id;
@override
void initState() {
// TODO: implement initState
super.initState();
_id = widget._productContentList[0].sId;
}
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
_flag ? LoadingWidget() : Text(''),
Expanded(
child: InAppWebView(
initialUrlRequest: URLRequest(url: Uri.parse("https://jdmall.itying.com/pcontent?id=${_id}")),
onProgressChanged: (InAppWebViewController controller, int progress){
if (progress / 100 > 0.9999) {
setState(() {
this._flag = false;
});
}
},
)
)
],
),
);
}
}
后面我会把整个项目的代码放到github.