搭建详细页。会把一个详细页分为6个主要部分来编写,也就是说把一个页面拆成六个大组件,并在不同的页面中。
1详细页_首屏自定义Widget编写
把详细页首屏独立出来,这样业务逻辑更具体,以后也会降低维护成本。最主要的是主UI文件不会变的臃肿不堪。
建立文件和引入资源
在/lib/pages/
文件夹下面,新建一个文件夹,命名为details_page
,然后进入文件夹,新建立文件details_top_area.dart
。意思是商品详细页的顶部区域。
然后用import
引入如下文件:
import 'package:flutter/material.dart'; import 'package:provide/provide.dart'; import '../../provide/details_info.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
然后用快速生成的方法,新建一个StatelessWidget
的类。
class DetailsTopArea extends StatelessWidget { }
先不管build方法,通过分析,我们把这个首屏页面进行一个组件方法的拆分。
商品图片方法
直接写一个内部方法,然后返回一个商品图片就可以了,代码如下:
//商品图片 Widget _goodsImage(url){ return Image.network( url, ScreenUtil().setWidth(740) ); }
商品名称方法
//商品名称 Widget _goodsName(name){ return Container( ScreenUtil().setWidth(730), padding: EdgeInsets.only(left:15.0), child: Text( name, maxLines: 1, style: TextStyle( fontSize: ScreenUtil().setSp(30) ), ), ); }
编号方法
Widget _goodsNum(num){ return Container( ScreenUtil().setWidth(730), padding: EdgeInsets.only(left:15.0), margin: EdgeInsets.only(top:8.0), child: Text( '编号:${num}', style: TextStyle( color: Colors.black26 ), ), ); }
Build方法编写
再build方法的最外层,使用了Provde Widget
,目的就是当状态发生变化时页面也进行变化。在Provide
的构造器里,声明了一个goodsInfo
变量,再通过Provide得到变量。然后进行UI的组合编写。
代码如下:
Widget build(BuildContext context) { return Provide<DetailsInfoProvide>( builder:(context,child,val){ var goodsInfo=Provide.value<DetailsInfoProvide>(context).goodsInfo.data.goodInfo; if(goodsInfo != null){ return Container( color: Colors.white, padding: EdgeInsets.all(2.0), child: Column( children: <Widget>[ _goodsImage( goodsInfo.image1), _goodsName( goodsInfo.goodsName ), _goodsNum(goodsInfo.goodsSerialNumber), _goodsPrice(goodsInfo.presentPrice,goodsInfo.oriPrice) ], ), ); }else{ return Text('正在加载中......'); } } ); }
加入到UI当中
现在这个首屏组件算是编写好,就可以在主UI文件中lib/pages/details_page.dart
中进行引入,并展现出来了。
import './details_page/details_top_area.dart';
引入后,在build方法里的column部件中进行加入下面的代码.
body:FutureBuilder( future: _getBackInfo(context) , builder: (context,snapshot){ if(snapshot.hasData){ return Container( child:Column( children: <Widget>[ //关键代码------start DetailsTopArea(), //关键代码------end ], ) ); }else{ return Text('加载中........'); } } )
2详细页_说明区域UI编写
下面把说明区域给制作出来,当然这部分也单独的独立出来。然后再自己学一个tabBar Widget
。自己写,不用官方自带的。
说明区域制作
首先在lib/pages/details_page
文件夹下,建立details_explain
文件。建立好后,先引入所需要的文件,代码如下:
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
然后生成一个StatelessWidget
,然后就是编写UI样式了,整体代码如下。
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; class DetailsExplain extends StatelessWidget { @override Widget build(BuildContext context) { return Container( color:Colors.white, margin: EdgeInsets.only(top: 10), ScreenUtil().setWidth(750), padding: EdgeInsets.all(10.0), child: Text( '说明:> 急速送达 > 正品保证', style: TextStyle( color:Colors.red, fontSize:ScreenUtil().setSp(30) ), ) ); } }
编写好以后,可以到details_page.dart
里进行引用和使用,先进行引用。
import './details_page/details_explain.dart';
然后在build方法body区域的Column中引用,代码如下,关注关键代码即可。
body:FutureBuilder( future: _getBackInfo(context) , builder: (context,snapshot){ if(snapshot.hasData){ return Container( child:Column( children: <Widget>[ DetailsTopArea(), //关键代码----------start DetailsExplain(), //关键代码----------end ], ) ); }else{ return Text('加载中........'); } } )
完成后就可以进行预览效果了,看看效果是不是自己想要的。
3详细页_自建TabBar Widget
现在自己建一个tabBar Widget
,而不用Flutter自带的tabBar widget
。
tabBar编写技巧
在lib/pages/details_page
文件夹下,新建一个details_tabbar.dart
文件。
这个文件主要是写bar区域的UI和交互效果,就算这样简单的业务逻辑,也进行了分离。
先打开provide
文件夹下的details_info.dart
文件,进行修改。需要增加两个变量,用来控制那个Tab被选中。
bool isLeft = true; bool isRight = false;
然后在文件的最下方加入一个方法,用来改变选中的值,这个方法先这样写,以后会随着业务的增加而继续补充和改变.
//改变tabBar的状态 changeLeftAndRight(String changeState){ if(changeState=='left'){ isLeft=true; isRight=false; }else{ isLeft=false; isRight=true; } notifyListeners(); }
Provide文件编写好以后,就可以打开刚才建立好的details_tabbar.dart
文件进行编写了。
先把所需要的文件进行引入:
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:provide/provide.dart'; import '../../provide/details_info.dart';
然后用快捷方法生成一个StatelessWidget
,在build方法的下方,写入一个返回Widget的方法,代码如下:
Widget _myTabBarLeft(BuildContext context,bool isLeft){ return InkWell( onTap: (){ Provide.value<DetailsInfoProvide>(context).changeLeftAndRight('left'); }, child: Container( padding:EdgeInsets.all(10.0), alignment: Alignment.center, ScreenUtil().setWidth(375), decoration: BoxDecoration( color: Colors.white, border: Border( bottom: BorderSide( 1.0, color: isLeft?Colors.pink:Colors.black12 ) ) ), child: Text( '详细', style: TextStyle( color:isLeft?Colors.pink:Colors.black ), ), ), ); }
这个方法就是详细的bar,然后再复制这段代码,修改成右边的bar。
Widget _myTabBarRight(BuildContext context,bool isRight){ return InkWell( onTap: (){ Provide.value<DetailsInfoProvide>(context).changeLeftAndRight('right'); }, child: Container( padding:EdgeInsets.all(10.0), alignment: Alignment.center, ScreenUtil().setWidth(375), decoration: BoxDecoration( color: Colors.white, border: Border( bottom: BorderSide( 1.0, color: isRight?Colors.pink:Colors.black12 ) ) ), child: Text( '评论', style: TextStyle( color:isRight?Colors.pink:Colors.black ), ), ), ); }
两个方法当然是一个合并成一个方法的,这样会放到所有代码实现之后,我们进行代码的优化。现在要作的是把build方法写好。代码如下:
Widget build(BuildContext context) { return Provide<DetailsInfoProvide>( builder: (context,child,val){ var isLeft= Provide.value<DetailsInfoProvide>(context).isLeft; var isRight =Provide.value<DetailsInfoProvide>(context).isRight; return Container( margin: EdgeInsets.only(top: 15.0), child: Column( children: <Widget>[ Row( children: <Widget>[ _myTabBarLeft(context,isLeft), _myTabBarRight(context,isRight) ], ), ], ), ) ; }, ); }
完整的details_tabbar.dart
文件,代码如下:
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:provide/provide.dart'; import '../../provide/details_info.dart'; class DetailsTabBar extends StatelessWidget { Widget build(BuildContext context) { return Provide<DetailsInfoProvide>( builder: (context,child,val){ var isLeft= Provide.value<DetailsInfoProvide>(context).isLeft; var isRight =Provide.value<DetailsInfoProvide>(context).isRight; return Container( margin: EdgeInsets.only(top: 15.0), child: Column( children: <Widget>[ Row( children: <Widget>[ _myTabBarLeft(context,isLeft), _myTabBarRight(context,isRight) ], ), ], ), ) ; }, ); } Widget _myTabBarLeft(BuildContext context,bool isLeft){ return InkWell( onTap: (){ Provide.value<DetailsInfoProvide>(context).changeLeftAndRight('left'); }, child: Container( padding:EdgeInsets.all(10.0), alignment: Alignment.center, ScreenUtil().setWidth(375), decoration: BoxDecoration( color: Colors.white, border: Border( bottom: BorderSide( 1.0, color: isLeft?Colors.pink:Colors.black12 ) ) ), child: Text( '详细', style: TextStyle( color:isLeft?Colors.pink:Colors.black ), ), ), ); } Widget _myTabBarRight(BuildContext context,bool isRight){ return InkWell( onTap: (){ Provide.value<DetailsInfoProvide>(context).changeLeftAndRight('right'); }, child: Container( padding:EdgeInsets.all(10.0), alignment: Alignment.center, ScreenUtil().setWidth(375), decoration: BoxDecoration( color: Colors.white, border: Border( bottom: BorderSide( 1.0, color: isRight?Colors.pink:Colors.black12 ) ) ), child: Text( '评论', style: TextStyle( color:isRight?Colors.pink:Colors.black ), ), ), ); } }
把TabBar引入项目
打开details_page.dart
文件,然后把detals_tabbar.dart
文件进行引入。
import './details_page/details_tabBar.dart';
然后再coloumn部分加入就可以了
child:Column( children: <Widget>[ DetailsTopArea(), DetailsExplain(), DetailsTabBar() ], )
首次进入详细页Bug处理
在第一次进入进入详细页的时候,会有错误出现,页面也会变成一篇红色,当然这只是一瞬间。所以很多小伙伴没有看出来,但是如果你注意控制台,就会看出这个错误提示。
这个问题的主要原因是没有使用异步方法,所以在Provide里使用一下异步就可以解决。代码如下:
//从后台获取商品数据 getGoodsInfo(String id) async{ var formData = {'goodId':id}; await request('getGoodDetailById',formData:formData).then((val){ var responseData= json.decode(val.toString()); goodsInfo = DetailsModle.fromJson(responseData); notifyListeners(); }); }
4详细页_Flutter_html插件的使用
在详细页里的商品详细部分,是由图片和HTML组成的。但是Flutter本身是不支持Html的解析的,flutter_webView_plugin
效果不太好。经过大神网友推荐,最终选择了flutter_html。
flutter_html介绍
flutter_html
是一个可以解析静态html标签的Flutter Widget,现在支持超过70种不同的标签。
github地址:https://github.com/Sub6Resources/flutter_html
也算是目前支持html标签比较多的插件了,先进行插件的依赖注册,打开pubspec.yaml
文件。在dependencies里边,加入下面的代码:
flutter_html: ^0.9.6
注意选择最新的版本(最新版0.10.4会报错,所以用回到0.9.6版)
代码的编写
当依赖和包下载好以后,直接在lib/pages/details_page
文件夹下建立一个detals_web.dart
文件。
建立好后,先引入依赖包。
import 'package:flutter/material.dart'; import 'package:provide/provide.dart'; import '../../provide/details_info.dart'; import 'package:flutter_html/flutter_html.dart';
然后写一个StatelessWidget
,在他的build方法里,声明一个变量goodsDetail,然后用Provide
的获得值。有了值之后直接使用Html Widget 就可以显示出来了。
import 'package:flutter/material.dart'; import 'package:provide/provide.dart'; import '../../provide/details_info.dart'; import 'package:flutter_html/flutter_html.dart'; class DetailsWeb extends StatelessWidget { @override Widget build(BuildContext context) { var goodsDetail=Provide.value<DetailsInfoProvide>(context).goodsInfo.data.goodInfo.goodsDetail; //从Provide内获取详情 return Container( child: Html( data:goodsDetail //注意这里是data,而不是child ), ); } }
加入到details_page.dart中
先引入刚才编写的details_web.dart
文件。
import './details_page/details_web.dart';
然后在column
的children
数组中加入DetailsWeb()
。
children: <Widget>[ DetailsTopArea(), DetailsExplain(), DetailsTabBar(), //关键代码-------------start DetailsWeb() //关键代码-------------end ],
如果出现溢出问题,那直接把Column
换成ListView
就可以了。这些都做完了,就可以简单看一下效果了。
5详细页_详情和评论切换效果制作
商品详情和评论页面的切换交互效果,思路是利用Provide
进行业务处理,然后根据状态进行判断返回不同的Widget。
嵌套Provide组件
在build返回里,的return部分,嵌套一个Provide
组件。然后在builder里取得isLieft
的值,如果值为true
,那说明点击了商品详情,如果是false
,那说明点击了评论的tabBar.
代码如下:
import 'package:flutter/material.dart'; import 'package:provide/provide.dart'; import '../../provide/details_info.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_html/flutter_html.dart'; class DetailsWeb extends StatelessWidget { @override Widget build(BuildContext context) { var goodsDetails = Provide.value<DetailsInfoProvide>(context).goodsInfo.data.goodInfo.goodsDetail; return Provide<DetailsInfoProvide>( builder: (context,child,val){ var isLeft=Provide.value<DetailsInfoProvide>(context).isLeft; if(isLeft){ //详情页 return Container( child: Html( data: goodsDetails//注意这里是data,而不是child了!!!! ), ); }else{ return Container( ScreenUtil().setWidth(750),//和我们的页面等宽的 padding: EdgeInsets.all(10.0), alignment: Alignment.center,//居中显示 child: Text('暂时没有数据') ); } }, ); } }
这里先写成固定的内容,有时间再后期开发。
6详细页页_Stack作底部操作栏
在详细页面底部是有一个操作栏一直在底部的,主要用于进行加入购物车、直接购买商品和进入购物车页面。制作这个只要需要使用Stack
组件就可以了。
Stack组件介绍
Stack组件是层叠组件,里边的每一个子控件都是定位或者不定位,定位的子控件是被Positioned Widget
进行包裹的。
比如现在改写之前的details_page.dart
文件,在ListView
的外边包裹Stack Widget
。
修改return返回值的这个地方,代码如下。
import 'package:flutter/material.dart'; import 'package:provide/provide.dart'; import '../provide/details_info.dart'; import './details_page/details_top_area.dart'; import './details_page/details_explain.dart'; import './details_page/details_tabBar.dart'; import './details_page/details_web.dart'; class DetailsPage extends StatelessWidget { final String goodsId; DetailsPage(this.goodsId); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( //返回按钮 onPressed: (){ Navigator.pop(context); //返回上级页面 }, icon: Icon(Icons.arrow_back), ), title: Text('商品详细页'), ), body: FutureBuilder( future: _getBackInfo(context), builder: (context, snapshot){ if(snapshot.hasData){ return Stack( children: <Widget>[ ListView( children: <Widget>[ DetailsTopArea(), DetailsExplain(), DetailsTabBar(), DetailsWeb(), ], ), Positioned( bottom: 0, left: 0, child: Text('测试') ) ], ); }else{ return Text('加载中......'); } }, ), ); } Future _getBackInfo(BuildContext context) async{ await Provide.value<DetailsInfoProvide>(context).getGoodsInfo(goodsId); return '完成加载'; } }
修改完成后,就可以看一下效果了。
制作底部工具栏
这个工具栏我们使用Flutter自带的bottomNavBar
是没办法实现的,所以,我们才用了Stack,把他固定在页面底部。然后我们还需要新建立一个页面,在lib/pages/details_page
文件夹下,新建立一个details_bottom.dart
文件。
在这个文件中,我们才用了Row
布局,然后使用Containter
进行了精准的控制,在布局用三个InkWell 因为都是可以点击的。最终实现了想要的结果。代码如下:
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; class DetailsBottom extends StatelessWidget { @override Widget build(BuildContext context) { return Container( ScreenUtil().setWidth(750), color: Colors.white, height: ScreenUtil().setHeight(80), child: Row( children: <Widget>[ InkWell( onTap: (){}, child: Container( ScreenUtil().setWidth(110) , alignment: Alignment.center, child:Icon( Icons.shopping_cart, size: 35, color: Colors.red, ), ) , ), InkWell( onTap: (){}, child: Container( alignment: Alignment.center, ScreenUtil().setWidth(320), height: ScreenUtil().setHeight(80), color: Colors.green, child: Text( '加入购物车', style: TextStyle(color: Colors.white,fontSize: ScreenUtil().setSp(28)), ), ) , ), InkWell( onTap: (){}, child: Container( alignment: Alignment.center, ScreenUtil().setWidth(320), height: ScreenUtil().setHeight(80), color: Colors.red, child: Text( '马上购买', style: TextStyle(color: Colors.white,fontSize: ScreenUtil().setSp(28)), ), ) , ), ], ), ); } }
加入到页面中
写完这个Widget后,需要在商品详细页里先用import
引入。
import './details_page/details_bottom.dart';
然后把组件放到Positioned
里,代码如下:
Positioned( bottom: 0, left: 0, child: DetailsBottom() )
商品详细页的大部分交互效果就已经完成了。