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()
)

商品详细页的大部分交互效果就已经完成了。

猜你喜欢

转载自www.cnblogs.com/joe235/p/11363965.html