• 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,
            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';

    然后在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(
                 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()
    )

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

  • 相关阅读:
    Java-常用类、接口关系图谱
    Java基础语法09-面向对象下-内部类-注解-异常
    Java基础语法08-面向对象-枚举-包装类-接口
    Java-常用类、接口API
    Docker学习系列(三)Docker搭建gitlab的两种方式
    Docker学习系列(二)Docker初体验
    Docker学习系列(一)Docker简介
    ZooKeeper介绍与环境搭建
    JDK工具系列之jps
    Mycat数据库中间件对Mysql读写分离和分库分表配置
  • 原文地址:https://www.cnblogs.com/joe235/p/11363965.html
Copyright © 2020-2023  润新知