【Flutter从入门到实战】⑱Flutter的Extension、Flutter的项目实战、Flutter的Android-iOS项目配置、项目目录结构划分、主题、路由、屏幕适配、JSON解析

Flutter从入门到实战
一共分为23个系列
①(Flutter、Dart环境搭建篇) 共3个内容 已更新
②(Dart语法1 篇) 共4个内容 已更新
③(Dart语法2 篇) 共2个内容 已更新
④(Flutter案例开发篇) 共4个内容 已更新
⑤(Flutter的StatelessWidget 共3个内容 已更新
⑥(Flutter的基础Widget篇) 共2个内容 已更新
⑦(布局Widget篇) 共1个内容 已更新
⑧(Flex、Row、Column以及Flexible、Stack篇) 共1个内容 已更新
⑨(滚动的Widget篇) 共4个内容 已更新
⑩(Dart的Future和网络篇) 共3个内容 已更新
⑪(豆瓣案例-1篇) 共3个内容 已更新
⑫(豆瓣案例-2篇) 共3个内容 已更新
⑬(Flutter渲染流程篇) 共3个内容 已更新
⑭(状态管理篇) 共3个内容 已更新
⑮(Flutter事件监听-以及路由使用篇) 共2个内容 已更新
⑯(Flutter的动画篇) 共4个内容 已更新
⑰(Flutter的主题、屏幕适配、测试篇) 共4个内容 已更新
⑱(Flutter的项目实战-美食广场篇) 共8个内容 已更新

官方文档说明

官方视频教程
Flutter的YouTube视频教程-小部件

请添加图片描述


①、Flutter的Extension

1、Extension的使用

Extension的作用 : 可以为系统或者自定义的类扩展一些方法。和Swift的Extension类似。
Flutter的Extension 是基于Dart SDK 2.6版本以上才支持的
所以使用Extension 先需要修改项目的Dart SDK版本
请添加图片描述

1、Extension的使用 - 给上一篇文章的屏幕适配做一些扩展

之前屏幕适配的使用是
YHSizeFit.setPx(200.0)
我们能不能更加简洁一点
比如 200.0.px() 、或者 200.0.px
我们可以给系统的double、或者int 提供扩展方法或者get方法

double_extension.dart

屏幕适配的写法
200.0.px

import '../shared/size_fit.dart';

extension DoubleFit on double {
    
    
  // 写法 200.0.px()
  // double px(){
    
    
  //   return YHSizeFit.setPx(this);
  // }

  // 写法 200.0rpx()
  // double rpx(){
    
    
  //   return YHSizeFit.setRpx(this);
  // }

  // 写法 200.0.px
  double get px {
    
    
    return  YHSizeFit.setPx(this);
  }

  // 写法 200.0.rpx
  double get rpx {
    
    
    return  YHSizeFit.setRpx(this);
  }
}

int_extension.dart

屏幕适配的写法
200.px

import '../shared/size_fit.dart';

extension IntFit on int {
    
    
  // 写法 200.px()
  // double px(){
    
    
  //   return YHSizeFit.setPx(this);
  // }

  // 写法 200.rpx()
  // double rpx(){
    
    
  //   return YHSizeFit.setRpx(this);
  // }

  // 写法 200.px
  double get px {
    
    
  // 因为Dart里面没有转int类型。所以使用转double类型
    return  YHSizeFit.setPx(this.toDouble());
  }

  // 写法 200.rpx
  double get rpx {
    
    
    return  YHSizeFit.setRpx(this.toDouble());
  }
}

main.dart

import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:learn_flutter/day14_screenFit/shared/size_fit.dart'; 
import 'package:learn_flutter/day14_screenFit/extension/double_extension.dart';
import 'package:learn_flutter/day14_screenFit/extension/int_extension.dart';

main() => runApp(MyApp());

class MyApp extends StatelessWidget {
    
    
  const MyApp({
    
    Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    
    

    // // 1. 手机的物理分辨率
    // final physicalWidth = window.physicalSize.width;
    // final physicalHeight = window.physicalSize.height;
    // print('分辨率: $physicalWidth * $physicalHeight');
    //
    // // // 2.手机屏幕的大小(逻辑分辨率)
    // // final width = MediaQuery.of(context).size.width;
    // // final height = MediaQuery.of(context).size.height;
    // // print('手机屏幕的大小(逻辑分辨率): $width * $height');
    //
    // //  绕过MediaQuery获取屏幕的物理分辨率
    // // 2. 获取dpr - 屏幕分辨率的比例系数
    // final dpr = window.devicePixelRatio;
    //
    // // 3. 宽度和高度
    // final width = physicalWidth / dpr;
    // final height = physicalHeight / dpr;
    //
    // print('宽度和高度: $width * $height');
    //
    // // 4. 状态栏高度
    // final statusHeight = window.padding.top / dpr;
    // print('状态栏高度: $statusHeight');

    YHSizeFit.initialize(standardSize: 750);


    return MaterialApp(
      title: "学习模板 Demo",
      theme: ThemeData(
        primaryColor: Colors.red,
        splashColor: Colors.orange,
      ),
      home: YHHomePage(),
    );
  }
}

class YHHomePage extends StatelessWidget {
    
    
  @override
  Widget build(BuildContext context) {
    
    
    // // 1. 手机的物理分辨率
    // final physicalWidth = window.physicalSize.width;
    // final physicalHeight = window.physicalSize.height;
    // print('分辨率: $physicalWidth * $physicalHeight');
    //
    // 2.手机屏幕的大小(逻辑分辨率)
    // final width = MediaQuery.of(context).size.width;
    // final height = MediaQuery.of(context).size.height;
    // print('手机屏幕的大小(逻辑分辨率): $width * $height');


    //
    print("状态栏的高度 ${YHSizeFit.statusHeight}");
    print(200.0.px);
    print(400.0.px);

    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter Widget"),
      ),
      body: Center(
        child: Container(
          // 使用extension扩展更加简洁的写法
          // YHSizeFit.setPx(200)  ==> 200.0.px() - px()是方法
          // YHSizeFit.setPx(200)  ==> 200.0.px - 提供get方法
          width: 200.px,
            height: 200.0.px,
          color: Colors.red,
            alignment: Alignment.center,
            child: Text("Hello World",style: TextStyle(fontSize: 20.0.px),)),
      ),
    );
  }
}






②、Flutter的项目实战 - 美食

最终效果图

1、新建项目

  1. 使用终端创建项目
    指令为 flutter create 项目名称

请添加图片描述

2、对项目进行配置

对移动端进行配置

  1. appid
  2. 应用名称
  3. icon(应用图标)
  4. launcher启动图

2.1、对项目进行配置(安卓-Android)

  1. appid
    请添加图片描述
  2. 应用名称请添加图片描述
  3. icon(应用图标)
    应用图标大小
    请添加图片描述
    请添加图片描述
  4. launcher启动图
    请添加图片描述

2.1、效果图 - 对项目进行配置(安卓-Android)

请添加图片描述

2.2、对项目进行配置(苹果-iOS)

  1. appid
    请添加图片描述
  2. 应用名称
    请添加图片描述
  3. icon(应用图标)
    请添加图片描述
  4. 启动图片
    请添加图片描述
    请添加图片描述
    请添加图片描述

2.2、效果图 - 对项目进行配置(苹果-iOS)

请添加图片描述

③、Flutter的项目目录结构划分

  1. pages
  2. widgets
  3. servers/network
  4. router
  5. viewmodel
  6. model
  7. shared

抽取 Core、UI
请添加图片描述

④、Flutter的主题相关

第17篇已经讲过了主题相关的东西 所以直接copy过来即可

YHAppTheme.dart


import 'package:flutter/material.dart';

// 封装主题
class YHAppTheme {
    
    

  static const double smallFontSize = 16;
  static const double normalFontSize = 22;
  static const double largeFontSize = 24;

  static final Color norTextColors = Colors.red;
  static final Color darkTextColors = Colors.green;

// 由于需要传递context值 所以使用方法进行处理
  ThemeData norTheme(BuildContext context){
    
    
    return ThemeData(
      // canvasColor 每个Scaffold的背景颜色
      canvasColor: Color.fromRGBO(255, 254, 222, 1),
      colorScheme: Theme.of(context).colorScheme.copyWith(
          primary: Colors.red
      ),
      textTheme: TextTheme(
        bodyText2: TextStyle(fontSize: smallFontSize),
          ),
    );
  }

  ThemeData darkTheme(BuildContext context){
    
    
    return ThemeData(
      colorScheme: Theme.of(context).colorScheme.copyWith(
          primary: Colors.orange
      ),
      textTheme: TextTheme(
        bodyText2: TextStyle(fontSize: smallFontSize, color: darkTextColors),
      ),
    );
  }
}

⑤、Flutter的路由配置

路由类的封装
路由以及主题的使用

class MyApp extends StatelessWidget {
     
     
 const MyApp({
     
     Key? key}) : super(key: key);

 @override
Widget build(BuildContext context) {
     
     
  YHAppTheme appTheme = YHAppTheme();
 return MaterialApp(
  title: "美食广场",
 // 主题
theme: appTheme.norTheme(context),
// 路由
initialRoute: YHRoute.initialRoute,
routes: YHRoute.routes,
 onGenerateRoute: YHRoute.generateRoute,
 onUnknownRoute: YHRoute.unknownRoute,
);
}
}

YHRoute.dart

import 'package:flutter/material.dart';
import '../../ui/pages/main/main.dart';

class YHRoute {
    
    
  // 首页初始化路由
  // 默认启动页面
  static final String initialRoute = YHMainScreen.routeName;

  // 路由映射
  static final Map<String,WidgetBuilder> routes = {
    
    
    YHMainScreen.routeName: (ctx) => YHMainScreen()
  };


  // 自己扩展
  static final RouteFactory generateRoute = (settings) {
    
    

  };
  static final RouteFactory unknownRoute = (settings){
    
    

  };

}

Main的MyApp封装

Main的MyApp封装 作用是用于方便管理
这样main.dart代码就很简洁了

class MyApp extends StatelessWidget {
    
    
  const MyApp({
    
    Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    
    
    YHAppTheme appTheme = YHAppTheme();
    return MaterialApp(
      title: "美食广场",
      // 主题
      theme: appTheme.norTheme(context),
      // 路由
      initialRoute: YHRoute.initialRoute,
      routes: YHRoute.routes,
      onGenerateRoute: YHRoute.generateRoute,
      onUnknownRoute: YHRoute.unknownRoute,
    );
  }
}

⑥、Flutter的屏幕适配

size_fit.dart

第17篇已经讲过了 所以直接复制过来

import 'dart:ui';

class YHSizeFit {
    
    

  static double physicalWidth = 0.0;
  static double physicalHeight = 0.0;
  static double screentWidth = 0.0;
  static double screentHeight = 0.0;
  static double dpr = 0.0;
  static double statusHeight = 0.0;


  static double rpx = 0.0 ;
  static double px = 0.0; // 比如200的宽度高大小 在700 应该是乘以2

  // standardSize 因为有些公司的屏幕适配的规范基数不一样 所以单独抽取出来
  static void initialize({
     
     double standardSize = 750}){
    
    
    // 1. 手机的物理分辨率
     physicalWidth = window.physicalSize.width;
     physicalHeight = window.physicalSize.height;
    // print('分辨率: $physicalWidth * $physicalHeight');

    // // 2.手机屏幕的大小(逻辑分辨率)
    // final width = MediaQuery.of(context).size.width;
    // final height = MediaQuery.of(context).size.height;
    // print('手机屏幕的大小(逻辑分辨率): $width * $height');

    //  绕过MediaQuery获取屏幕的物理分辨率
    // 2. 获取dpr - 屏幕分辨率的比例系数
     dpr = window.devicePixelRatio;

    // 3. 宽度和高度
     screentWidth = physicalWidth / dpr;
     screentHeight = physicalHeight / dpr;

    // print('宽度和高度: $screentWidth * $screentHeight');

    // 4. 状态栏高度
     statusHeight = window.padding.top / dpr;
    // print('状态栏高度: $statusHeight');

    // 5. 计算rpx的大小 - 以小程序的比例计算
     // 以iPhone6为基准 不管什么屏幕 整体都按照750等分分割
    rpx = screentWidth / standardSize;
    px = screentWidth / standardSize * 2;

  }

  // 抽取方法 设置
  static double setRpx(double size){
    
    
    return rpx * size;
  }

  static double setPx(double size){
    
    
    return px * size;
  }

}

扩展 - double_extension.dart

import '../../ui/shared/size_fit.dart';

extension DoubleFit on double {
    
    
  // 写法 200.0.px()
  // double px(){
    
    
  //   return YHSizeFit.setPx(this);
  // }

  // 写法 200.0rpx()
  // double rpx(){
    
    
  //   return YHSizeFit.setRpx(this);
  // }

  // 写法 200.0.px
  double get px {
    
    
    return  YHSizeFit.setPx(this);
  }

  // 写法 200.0.rpx
  double get rpx {
    
    
    return  YHSizeFit.setRpx(this);
  }
}

扩展 - int _extension.dart

import '../../ui/shared/size_fit.dart';

extension IntFit on int {
    
    
  // 写法 200.px()
  // double px(){
    
    
  //   return YHSizeFit.setPx(this);
  // }

  // 写法 200.rpx()
  // double rpx(){
    
    
  //   return YHSizeFit.setRpx(this);
  // }

  // 写法 200.px
  double get px {
    
    
    //  // 因为Dart里面没有转int类型。所以使用转double类型
    return  YHSizeFit.setPx(this.toDouble());
  }

  // 写法 200.rpx
  double get rpx {
    
    
    return  YHSizeFit.setRpx(this.toDouble());
  }
}

⑦、Flutter的Json解析

json解析 工具类封装
json to dart 转换网站
https://javiercbk.github.io/json_to_dart/
加载json文件 进行解析是一个异步操作 。需要使用asyncawaitFuture

json_parse.dart

import 'dart:convert';

import 'package:favorcate/core/model/category_model.dart';
import 'package:flutter/services.dart';
class JsonParse{
    
    
  // static void getCatgoryData() async{
    
    
  static Future<List<YHCategoryModel>> getCatgoryData() async{
    
    
    // 1. 加载json文件
    final jsonString = await rootBundle.loadString("assets/json/category.json");

    // 2. 将JsonString 转成 Map/List
    // json.decode(source) // 将字符串转成Map或者list
    // json.encode(value) // 将json对象也就是Map或者List 转成 json字符串

    // 因为转换是异步操作 所以要使用 async
    final result = json.decode(jsonString);

    // 3. 将Map中的内容转换成一个个对象
    // 转换工具 https://javiercbk.github.io/json_to_dart/
    final resultList = result["category"];
    List<YHCategoryModel> categorys = [];
    for (var json in resultList)
      {
    
    
        categorys.add(YHCategoryModel.fromJson(json));
      }
    return categorys;
  }
}

model - category_model.dart 将颜色进行16进制封装

import 'package:flutter/material.dart';
import 'dart:ui';


class YHCategoryModel {
    
    
  String? id;
  String? title;
  String? color;
  Color cColor = Colors.red;

  YHCategoryModel({
    
    this.id, this.title, this.color});

  YHCategoryModel.fromJson(Map<String, dynamic> json) {
    
    
    id = json['id'];
    title = json['title'];
    color = json['color'];
    
    // 1. 将color 转成16进制的数字
    final colorInt = int.parse(color!,radix: 16);
    // 2.将透明度加进去
    cColor = Color(colorInt | 0xFF000000);

  }

  Map<String, dynamic> toJson() {
    
    
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['id'] = this.id;
    data['title'] = this.title;
    data['color'] = this.color;
    return data;
  }
}
	

⑧、首页的抽取 - 渐变颜色设置

YHHomeScreen.dart

import 'package:flutter/material.dart';
import 'home_content.dart';

class YHHomeScreen extends StatelessWidget {
    
    
  // const YHHomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    
    
    return Scaffold(
      appBar: AppBar(
        title: Text("美食广场"),
      ),
      body: YHHomeContent(),
    );
  }
}

YHHomeContent.dart

加载首页内容。本地json数据 在初始化的时候 就进行解析

import 'package:favorcate/core/extension/int_extension.dart';
import 'package:flutter/material.dart';

import '../../../core/model/category_model.dart';
import '../../../core/services/json_parse.dart';

class YHHomeContent extends StatefulWidget {
    
    

  const YHHomeContent({
    
    Key? key}) : super(key: key);

  @override
  State<YHHomeContent> createState() => _YHHomeContentState();
}

class _YHHomeContentState extends State<YHHomeContent> {
    
    

  List<YHCategoryModel> _categorys = [];

  @override
  void initState() {
    
    
    // TODO: implement initState
    super.initState();
    // 加载数据
    JsonParse.getCatgoryData().then((res){
    
    
      setState(() {
    
    
        _categorys = res;
      });
      // print(res);
    });
  }
  @override
  Widget build(BuildContext context) {
    
    
    return GridView.builder(
      padding: EdgeInsets.all(20.px),
      itemCount: _categorys.length,
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          crossAxisSpacing: 20.px,
          mainAxisSpacing: 20.px,
          childAspectRatio: 1.5,// 宽高比
        ), itemBuilder: (ctx,index){
    
    
        final bgColor = _categorys[index].cColor;
        return Container(
          decoration: BoxDecoration(
            color: bgColor,
            borderRadius: BorderRadius.circular(12),
            // 渐变颜色
            gradient: LinearGradient(
              colors: [
                bgColor.withOpacity(.5),
                bgColor
              ]

            )
          ),
            alignment: Alignment.center,
            child: Text(_categorys[index].title!,style: TextStyle(fontWeight: FontWeight.bold),));
    });
  }
}

项目效果图 - 美食广场

请添加图片描述

猜你喜欢

转载自blog.csdn.net/qq_42816425/article/details/123890610