搭建詳細頁。會把一個詳細頁分為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, width:ScreenUtil().setWidth(740) ); }
商品名稱方法
//商品名稱 Widget _goodsName(name){ return Container( width: 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( width: 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), width: 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, width: ScreenUtil().setWidth(375), decoration: BoxDecoration( color: Colors.white, border: Border( bottom: BorderSide( width: 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, width: ScreenUtil().setWidth(375), decoration: BoxDecoration( color: Colors.white, border: Border( bottom: BorderSide( width: 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, width: ScreenUtil().setWidth(375), decoration: BoxDecoration( color: Colors.white, border: Border( bottom: BorderSide( width: 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, width: ScreenUtil().setWidth(375), decoration: BoxDecoration( color: Colors.white, border: Border( bottom: BorderSide( width: 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( width: 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( width:ScreenUtil().setWidth(750), color: Colors.white, height: ScreenUtil().setHeight(80), child: Row( children: <Widget>[ InkWell( onTap: (){}, child: Container( width: ScreenUtil().setWidth(110) , alignment: Alignment.center, child:Icon( Icons.shopping_cart, size: 35, color: Colors.red, ), ) , ), InkWell( onTap: (){}, child: Container( alignment: Alignment.center, width: 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, width: 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() )
商品詳細頁的大部分交互效果就已經完成了。