Flutter学习-项目实战

Posted GY-93

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter学习-项目实战相关的知识,希望对你有一定的参考价值。

首先我们可以使用flutter create xxx指令在终端创建项目, 也可以使用工具来创建一个新的 项目, 我这里是在终端直接创建的,以防使用IDE创建又自动配置一些内容

1 项目的配置

我们创建完项目之后,这里我们需要配置一些基本的信息(Flutter移动端-> App)
我们准备对以下内容配置:

  • appid: 程序唯一id
  • 应用名称
  • icon: App的icon图标
  • lanuncher: App的启动图

由于androidios端的配置是不一样的,这里我们需要分开配置

1.1 Android的基本配置

由于我们是使用Android studio工具来开发的,所以对于Android的配置我们可以直接在工具中配置

  • 配置Appid:

  • 应用名称、icon图标

  • 启动图
    Android中默认的启动图是一片空白的,这是Flutter的默认设置效果。
    根据手机的分辨率不同去加载不同分辨率的图片,如果该分辨率下没有图片则会去高分辨率的文件中找

初始文件如上图, 是直接启动是白色,我们如果需要修改启动图可以进行如下修改

  • 注意: 由于本人是一名IOS开发,所以这里关于IOS端的基本信息配置,我这里就不做介绍了, 如果其他人需要

IOS端配置:

  1. 项目的目录结构划分
  2. 主题相关

  3. 6.路由配置

2. 项目注意事项点

2.1 字符串颜色转换成颜色

//1.将得到的颜色字符串,转换成16进制的数字
    if (color != null) 
      /**
       * int.Parse()抛出了异常,原因是int.Parse()是一种类容转换;表示将数字内容的字符串转为int类型。
       * 如果字符串为空,则抛出ArgumentNullException异常
       * 如果字符串内容不是数字,则抛出FormatException异常;
       * 如果字符串内容所表示数字超出int类型可表示的范围,则抛出OverflowException异常
       */
      /**
       *  int中有一个parse方法来解析字符串 会抛出FormatException异常 radix:默认基数10进制,我们需要指定是16进制
       */
      final colorInt = int.parse(color!, radix: 16);
      //把透明度直接通过 或  的方式加进去 ,然后调用Color的构造方法得到一个有透明度的颜色
      backgroundColor = Color(colorInt | 0xFF000000);
    

2.2 使用FutureBuilder在某些情况下代理StatefulWidgets

我们首页展示这美食分类界面:

我们发现首页其实是一直存在的,这个时候我们使用StatefulWidgets来实现需要再initState方法中加载网络数据, 在使用setState方法来更新界面

这里我们完全可以使用FutureBuilder来代替,因为首页不需要频繁的更新

2.2.1 StatefulWidget实现

class GYHomeContent extends StatefulWidget 
  const GYHomeContent(Key? key) : super(key: key);

  @override
  _GYHomeContentState createState() => _GYHomeContentState();


class _GYHomeContentState extends State<GYHomeContent> 
  List<GYCategoryModel> items = [];
  @override
  void initState() 
    // TODO: implement initState
    super.initState();

    //加载网络数据,这里是加载本地json文件数据
    GYJsonParse.getCategryData().then((value) 
      //加载数据完成之后,刷新数据显示
      setState(() 
        items = value;
      );
    );
  


  @override
  Widget build(BuildContext context) 
    return GridView.builder(
        padding: EdgeInsets.all(20.px),
        itemCount: items.length,
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2, //交叉轴方向显示2个item
            childAspectRatio: 1.5, //宽高比
            crossAxisSpacing: 20.px,//交叉轴item之间的距离
            mainAxisSpacing: 20.px//主轴方向item之间的距离
        ),
        itemBuilder: (context, index) 
          final bgColor = items[index].backgroundColor;
          return Container(
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(12),
              //设置item的颜色为渐变色
              gradient: LinearGradient(
                colors: [
                  bgColor.withOpacity(.5),
                  bgColor
                ]
              )
            ),
            alignment: Alignment.center,
            child: Text(items[index].title ?? "", style: Theme.of(context).textTheme.headline3?.copyWith(
              fontWeight: FontWeight.bold
            ),),
          );
        );
  

2.2 FutureBuilder实现

class GYHomeContent extends StatelessWidget 

  @override
  Widget build(BuildContext context) 
    return FutureBuilder<List<GYCategoryModel>>(
        future: GYJsonParse.getCategryData(),
        /**
         * 在某些情况下FutureBuilder这个widget可以代替StatefulWidget来使用,使用代码看起来更加简洁
         * 但是该widget有局限性:
         * 1、如果该页面需要经常刷新数据,可能需要缓存数据, 那么该widget就无法满足, 如果该页面多次刷新,那么使用FutureBuilder会造成多次发送请求
         * 2、如果该页面需要实现 上拉加载功能,  该widget也无法完成
         */

        builder: (context, snapshotData) 
//hasData: 表示,如果请求加载的数据回来了,返回true ,否则返回false
          if (!snapshotData.hasData)
            return Center(
              child: CircularProgressIndicator(),
            );
//如果请求错误,则显示错误页面
          if (snapshotData.error != null) return Center(child: Text("请求失败"));
          final items = snapshotData.data;

          return GridView.builder(
              padding: EdgeInsets.all(20.px),
              itemCount: items!.length,
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2, //交叉轴方向显示2个item
                  childAspectRatio: 1.5, //宽高比
                  crossAxisSpacing: 20.px, //交叉轴item之间的距离
                  mainAxisSpacing: 20.px //主轴方向item之间的距离
                  ),
              itemBuilder: (context, index) 
                return GYHomeCategoryItem(items[index]);
              );
        );
  

两者实现的效果是一样的,但是在这种情况下,使用FutureBuilder实现起来,代码可能看起来简介

2.3 实现路由跳转,并传值

  • 配置路由

  • 路由跳转

  • 取出路由传递的值

    注意:拿到的都是栈顶widget的元素,所以不管是在栈的那个页面拿数据都是一样的

2.4 使用Provider实现数据共享

  • 创建共享数据
class GYMealViewModel extends ChangeNotifier 
  /*需要共享的数据*/
  List<GYMealModel> _meals = [];

  /*提供get方法*/
  List<GYMealModel> get meals 
    return _meals;
  

  //在初始化方法中获取数据
  GYMealViewModel() 
    //获取菜谱详细数据,本来是网络请求获取数据,目前是获取本地json文件的数据
    GYJsonParse.getMealData().then((value) 
      _meals = value;
      //发送通知告诉 使用共享数据的地方,共享数据更新了
      notifyListeners();
    );
  

  • 配置共享数据
void main() 
   Provider -> ViewModel/Provider/Consumer(Selector)
  runApp(
    //使用ChangeNotifierProvider作为顶层,不管在那里都可以访问这个共享数据
    ChangeNotifierProvider(
      //这里不会再启动App的时候就加载数据的, 是在第一次使用到共享数据的时候才会加载,是一个懒加载
      create: (ctx) => GYMealViewModel(),
      child: MyApp(),
    )
  );

  • 使用共享数据,Consumer和Selector两种方式
// 使用Consumer的方式来实现  共享数据的展示和获取
   @override
   Widget build(BuildContext context) 
     return Consumer<GYMealViewModel>(
       /**
        * 第一个参数: context 上下文
        * 第二个参数: ChangeNotifier对应的实例,共享的数据,也是我们在build函数中使用的主要对象
        * 第三个参数: child:目的是进行优化把不希望重新build的子widget层放到child属性之后,这样更新数据之后就不会重新build整个widget树
        */
         builder: (context, mealViewModel, child) 
             //根据id筛选出数据
           final meals = mealViewModel.meals.where((element) => element.categories!.contains(_categoryModel.id)).toList();
           return ListView.builder(itemBuilder: (context, index,) 
             return Text(meals[index].title ?? "名称为空");
           , itemCount: meals.length,);
         
     );
   


// 上面使用Consumer的方式来实现共享数据的获取, 这里其实使用Selector的方式来实现更加的简介方便
  @override
  Widget build(BuildContext context) 
    // TODO: implement build
    return Selector<GYMealViewModel, List<GYMealModel>>(
      builder: (context, meals, child) 
        return ListView.builder(
          itemBuilder: (
            context,
            index,
          ) 
            return Text(meals[index].title ?? "名称为空");
          ,
          itemCount: meals.length,
        );
      ,
      //这里是把 A 转换成 S
      selector: (context, mealVM) 
        return mealVM.meals
            .where((element) => element.categories!.contains(_categoryModel.id))
            .toList();
      ,
      shouldRebuild: (pre, next) 
        //比较两个数组是否完全一样 ,一样则不重新build, 如果不一样则重新build
        return !ListEquality().equals(pre, next);
      ,
    );
  

2.5 item详情页面实现

2.5.1 double.infinity 属性

  • double.infinity : 表示widge在该方向的尽可能占据最大的宽度

2.5.2 Colum中嵌套ListView的问题,报错问题

运行代码会报一个flutter中经典的错误, 其实这种场景我们会比较常见

  • 问题原因: Colum是希望内部所有子Widget都有一个明确的高度, 而ListView在垂直方向,是希望尽可能占据最大的空间,具体占据多大空间,不知道,所以产生了冲突,然后就上述错误

  • 解决原因:

    • 我们可以直接ListView的外层的Container的高度,这样设置可以正常运行,但是可能会有一个内部滚动的效果
    • 直接设置ListView的高度,让ListView直接包裹内容高度

2.5.3 Colum滑动

我们都知道Colum本身不是不可滑动的,当内容的显示超过Colum的显示范围时,那么就会报错
,那我们如何使Colum边的可以滑动了

在Colum的外层添加一个SingleChildScrollViewwidget即可,该Widget就可以使Colum进行滑动

2.6 flutter中实现抽屉效果

2.6.1 实现抽屉效果

在Flutter中如何实现抽屉效果, 其实很简单, flutter中给我们提供了一个属性drawer来设置抽屉效果

2.6.2 如何控制抽屉效果的宽度

我们查看Drawerwidget的属性,并没有发现有属性直接设置宽度:

  • 解决办法: 如何控制抽屉的宽度, 本身是没有一个属性来控制宽度, 我们可以在外面包裹一层Container,通过设置Conatiner的宽度来达到控制抽屉效果的看度

2.6.3 如何修改drawer的icon图标

我知道,在AppBar中有一个属性leading,这个可以修改左侧导航栏的图标

但是当我们修改导航栏左侧icon图标之后,我们发现点击按钮没有反应了,这个时候需要我们自己去打开 抽屉效果页面, 我们可以如下实现:

但是当我们点击按钮时,发现还是没有反应, 这是因为这句代码取的Scaffold根本不是你自己创建的那个Scaffold,因为你拿的是build方法的context,会沿着该widget树往上寻找,但是你创建的Scaffold是在这个widget的build方法中, 所以是肯定找不到的

  • 解决办法

做如上修改之后, 抽屉效果页面就可以打开了

2.7 实现过滤功能

具体实现功能参考demo代码

以上是关于Flutter学习-项目实战的主要内容,如果未能解决你的问题,请参考以下文章

最强Android 项目集成 Flutter 实战分享!不看看?

读者福利 补偿学习Flutter 项目实战总结

Flutter从入门到进阶实战携程网App 完整版

Flutter微信项目实战01基本框架搭建

Flutter微信项目实战01基本框架搭建

为什么要学习跨平台? Flutter 跨平台框架应用实战