Flutter 商城實例 詳細頁


搭建詳細頁。會把一個詳細頁分為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';

然后在columnchildren數組中加入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()
)

商品詳細頁的大部分交互效果就已經完成了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM