Skip to content

flutter学习笔记

安装 Flutter(Dart)

安装 Flutter 默认安装Dart,如果只有Dart需求,可以查看官方文档。

使用 fvm 版本管理安装 Flutter

使用fvm进行安装,无论你是否有多版本需求安装教程

*项目使用 puro 版本管理安装 Flutter

可以查看本站点的 puro管理flutter版本 文档

安装 IDE 拓展

注意兼容

如果你使用 Android Studio/Intellij,安装拓展时请注意兼容的版本

安装 GetX Cli

打开你的命令行,执行

shell
pub global activate get_cli

如果你的是 Flutter 环境,那么执行

shell
flutter pub global activate get_cli

环境变量添加

C:\Users\[User]\AppData\Local\Pub\Cache\bin

11.创建 Flutter 项目

shell
flutter create my_project

12.初始化 GetX 结构

shell
get init
如果执行后卡住

GetX Cli 命令卡住不动?

这是因为 GetX Cli 在检测更新,和 Flutter 命令卡住同理。源码里并没有提供更换更新地址的选项。所以我 fork 了源码,进行了更改

可以去仓库github的 release 界面,下载编译好的 get

赋予运行权限并重命名成 cnget 避免冲突,再设置一下环境变量。就可以直接使用 cnget 了 。使用它和使用 get 一样,它们本就同源。

初始化后的 lib 目录结构如下

shell
.
├── app
│   ├── data
│   ├── modules
│   │   └── home
│   │       ├── bindings
│   │       │   └── home_binding.dart
│   │       ├── controllers
│   │       │   └── home_controller.dart
│   │       └── views
│   │           └── home_view.dart
│   └── routes
│       ├── app_pages.dart
│       └── app_routes.dart
└── main.dart

是的,GetX Cli 已经帮你初始化好了一个路由结构以及 home 子页面,这个实例是可以直接运行的。

GetX Cli 将项目全部装进了 app 目录中,modules 存放页面,每个页面又分别存在 bindings,controllers,views

如果你知道 MVVM,那么你应该很熟悉,它们分别负责的是:

  • bindings:负责 controller 与 view 的耦合,即 VM
  • controller:负责响应式变量和非响应式变量的管理,即 M
  • view:负责页面,即 V

即 view 与 controller 是解耦的,通过 binding 进行耦合。Flutter 官方示例大多数是一并写在一起,这将发生混乱,为协作和维护带来困难。

新增页面>

shell
get create page:demo

执行上面命令,你的 module 目录将会新增一个 demo 目录,结构如下

shell
.
├── demo
│   ├── bindings
│   │   └── demo_binding.dart
│   ├── controllers
│   │   └── demo_controller.dart
│   └── views
│       └── demo_view.dart
└── home
    ├── bindings
    │   └── home_binding.dart
    ├── controllers
    │   └── home_controller.dart
    └── views
        └── home_view.dart

这将为你生成和 home 一致结构的目录,并且自动将 demo 加进路由表中

新增 view

shell
get create view:demo on home

执行上面命令,将会在 module/home 目录下的 views 文件夹里新增一个 demo_view,结构如图

shell
.
├── bindings
│   └── home_binding.dart
├── controllers
│   └── home_controller.dart
└── views
    ├── demo_view.dart
    └── home_view.dart

新增 controller

shell
get create controller:demo on home

执行上面命令,将会在 module/home 目录下的 controllers 文件夹下新增一个 demo_controller,结构如图

shell
.
├── bindings
│   └── home_binding.dart
├── controllers
│   ├── demo_controller.dart
│   └── home_controller.dart
└── views
    ├── demo_view.dart
    └── home_view.dart

并且会自动在 home_binding 中耦合 demo_controller,但是此时并没有 view 与 demo_controller 耦合,这就需要我们手动去绑定它。

修改 demo_view

dart
// 修改前
class DemoView extends GetView {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('DemoView'),
        centerTitle: true,
      ),
      body: Center(
        child: Text(
          'DemoView is working',
          style: TextStyle(fontSize: 20),
        ),
      ),
    );
  }
}

// 修改后
class DemoView extends GetView<DemoController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('DemoView'),
        centerTitle: true,
      ),
      body: Center(
        child: Text(
          'DemoView is working',
          style: TextStyle(fontSize: 20),
        ),
      ),
    );
  }
}

是的,你只需要在 GetView 后加上绑定的 controller 即可

自定义 Controller 模板

从本地文件获取模板

shell
get create controller:auth with examples/authcontroller.dart on your_folder

从远程获取模板

shell
get create controller:auth with 'https://raw.githubusercontent.com/jonataslaw/get_cli/master/samples_file/controller.dart.example' on your_folder

如果你的模板长这样

dart
@import

class @controller extends GetxController {
  final  email = ''.obs;
  final  password = ''.obs;
  void login() {
  }

}

那么生成的 Controller 就是这样的

dart
import 'package:get/get.dart';

class AuthController extends GetxController {
  final email = ''.obs;
  final password = ''.obs;
  void login() {}
}

12.引入 GetX(手动)

选择1:在你的 pubspec.yaml 文件下增加

yaml
dependencies:
  get: ^4.1.1

最新版本进pub仓库查看:pub.dev


选择2:安装:flutter pub add get

使用:import 'package:get/get.dart';

MaterialApp()改为GetMaterialApp()

21. GetX创建项目

get create project

官方计时器例子

首先我们创建我们的变量控制器controller

dart
class Controller extends GetxController{
  var count = 0.obs;
  increment() => count++;
}

然后创建我们的页面,使用StatelessWidget节省一些内存,使用Getx你可能不再需要使用StatefulWidget。

dart
class Home extends StatelessWidget {

  @override
  Widget build(context) {

    // 使用Get.put()实例化你的类,使其对当下的所有子路由可用。
    final Controller c = Get.put(Controller());

    return Scaffold(
      // 使用Obx(()=>每当改变计数时,就更新Text()。
      appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),

      // 用一个简单的Get.to()即可代替Navigator.push那8行,无需上下文!
      body: Center(child: ElevatedButton(
              child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
      floatingActionButton:
          FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
  }
}

class Other extends StatelessWidget {
  // 你可以让Get找到一个正在被其他页面使用的Controller,并将它返回给你。
  final Controller c = Get.find();

  @override
  Widget build(context){
     // 访问更新后的计数变量
     return Scaffold(body: Center(child: Text("${c.count}")));
  }
}

如此便完成了官方的计时器

生成 Model Provider

准备你的数据 json 文件,放入 assets/models

例如:

user.json

json
{
  "name": "",
  "age": 0,
  "friends": ["", ""]
}

执行生成命令

shell
get generate model on home with assets/models/user.json
get generate model on home with [https://url]

如果报错

重新安装

dart pub global deactivate get_cli
dart pub global activate -s git https://github.com/knottx/get_cli.git --git-ref fix-generate-model

将会为你生成一份 Model 文件和一份 Provider 文件

网络请求

网络请求

GetX建议将网络请求拆分成 RepositoryProvider 两层

Repository 负责将请求的数据进行存储,预处理

Provider 负责发出请求,以及处理请求返回,请求错误等

dart
class User {
  String name;
  int age;
  List<String> friends;

  User({this.name, this.age, this.friends});

  User.fromJson(Map<String, dynamic> json) {
    name = json['name'];
    age = json['age'];
    friends = json['friends'].cast<String>();
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['name'] = this.name;
    data['age'] = this.age;
    data['friends'] = this.friends;
    return data;
  }
}

中间件

如果你想通过监听Get事件来触发动作,你可以使用routingCallback来实现。

dart
GetMaterialApp(
  routingCallback: (routing) {
    if(routing.current == '/second'){
      openAds();
    }
  }
)

如果你没有使用GetMaterialApp,你可以使用手动API来附加Middleware观察器。

dart
void main() {
  runApp(
    MaterialApp(
      onGenerateRoute: Router.generateRoute,
      initialRoute: "/",
      navigatorKey: Get.key,
      navigatorObservers: [
        GetObserver(MiddleWare.observer), // HERE !!!
      ],
    ),
  );
}

创建一个MiddleWare类

dart
class MiddleWare {
  static observer(Routing routing) {
    ///你除了可以监听路由外,还可以监听每个页面上的SnackBars、Dialogs和Bottomsheets。
    if (routing.current == '/second' && !routing.isSnackbar) {
      Get.snackbar("Hi", "You are on second route");
    } else if (routing.current =='/third'){
      print('last route called');
    }
  }
}

现在,在你的代码上使用Get:

dart
class First extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.add),
          onPressed: () {
            Get.snackbar("hi", "i am a modern snackbar");
          },
        ),
        title: Text('First Route'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Open route'),
          onPressed: () {
            Get.toNamed("/second");
          },
        ),
      ),
    );
  }
}

class Second extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.add),
          onPressed: () {
            Get.snackbar("hi", "i am a modern snackbar");
          },
        ),
        title: Text('second Route'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Open route'),
          onPressed: () {
            Get.toNamed("/third");
          },
        ),
      ),
    );
  }
}

class Third extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Third Route"),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Get.back();
          },
          child: Text('Go back!'),
        ),
      ),
    );
  }
}

免context导航

SnackBars

用Flutter创建一个简单的SnackBar,你必须获得Scaffold的context,或者你必须使用一个GlobalKey附加到你的Scaffold上。

dart
final snackBar = SnackBar(
  content: Text('Hi!'),
  action: SnackBarAction(
    label: 'I am a old and ugly snackbar :(',
    onPressed: (){}
  ),
);

// 在小组件树中找到脚手架并使用它显示一个SnackBars。
Scaffold.of(context).showSnackBar(snackBar);

用Get:

dart
Get.snackbar('Hi', 'i am a modern snackbar');

有了Get,你所要做的就是在你代码的任何地方调用你的Get.snackbar,或者按照你的意愿定制它。

dart
Get.snackbar(
  "Hey i'm a Get SnackBar!", // title
  "It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message
  icon: Icon(Icons.alarm),
  shouldIconPulse: true,
  onTap:(){},
  barBlur: 20,
  isDismissible: true,
  duration: Duration(seconds: 3),
);
////////// ALL FEATURES //////////
//     Color colorText,
//     Duration duration,
//     SnackPosition snackPosition,
//     Widget titleText,
//     Widget messageText,
//     bool instantInit,
//     Widget icon,
//     bool shouldIconPulse,
//     double maxWidth,
//     EdgeInsets margin,
//     EdgeInsets padding,
//     double borderRadius,
//     Color borderColor,
//     double borderWidth,
//     Color backgroundColor,
//     Color leftBarIndicatorColor,
//     List<BoxShadow> boxShadows,
//     Gradient backgroundGradient,
//     TextButton mainButton,
//     OnTap onTap,
//     bool isDismissible,
//     bool showProgressIndicator,
//     AnimationController progressIndicatorController,
//     Color progressIndicatorBackgroundColor,
//     Animation<Color> progressIndicatorValueColor,
//     SnackStyle snackStyle,
//     Curve forwardAnimationCurve,
//     Curve reverseAnimationCurve,
//     Duration animationDuration,
//     double barBlur,
//     double overlayBlur,
//     Color overlayColor,
//     Form userInputForm
///////////////////////////////////

如果您喜欢传统的SnackBars,或者想从头开始定制,包括只添加一行(Get.snackbar使用了一个强制性的标题和信息),您可以使用 Get.rawSnackbar();它提供了建立Get.snackbar的RAW API。

Dialogs

打开Dialogs:

dart
Get.dialog(YourDialogWidget());

打开默认Dialogs:

dart
Get.defaultDialog(
  onConfirm: () => print("Ok"),
  middleText: "Dialog made in 3 lines of code"
);

你也可以用Get.generalDialog代替showGeneralDialog。

对于所有其他的FlutterDialogs小部件,包括cupertinos,你可以使用Get.overlayContext来代替context,并在你的代码中任何地方打开它。 对于不使用Overlay的小组件,你可以使用Get.context。 这两个context在99%的情况下都可以代替你的UIcontext,除了在没有导航context的情况下使用 inheritedWidget的情况。

BottomSheets

Get.bottomSheet类似于showModalBottomSheet,但不需要context:

dart
Get.bottomSheet(
  Container(
    child: Wrap(
      children: <Widget>[
        ListTile(
          leading: Icon(Icons.music_note),
          title: Text('Music'),
          onTap: () {}
        ),
        ListTile(
          leading: Icon(Icons.videocam),
          title: Text('Video'),
          onTap: () {},
        ),
      ],
    ),
  )
);

嵌套导航

Get让Flutter的嵌套导航更加简单。 你不需要context,而是通过Id找到你的导航栈。

dart
Navigator(
  key: Get.nestedKey(1), // create a key by index
  initialRoute: '/',
  onGenerateRoute: (settings) {
    if (settings.name == '/') {
      return GetPageRoute(
        page: () => Scaffold(
          appBar: AppBar(
            title: Text("Main"),
          ),
          body: Center(
            child: TextButton(
              color: Colors.blue,
              onPressed: () {
                Get.toNamed('/second', id:1); // navigate by your nested route by index
              },
              child: Text("Go to second"),
            ),
          ),
        ),
      );
    } else if (settings.name == '/second') {
      return GetPageRoute(
        page: () => Center(
          child: Scaffold(
            appBar: AppBar(
              title: Text("Main"),
            ),
            body: Center(
              child:  Text("second")
            ),
          ),
        ),
      );
    }
  }
),

启动

flutter run

r 键 :Flutter热加载命令。

R键:Flutter热重启项目命令。

p 键:显示网格,这个可以很好的掌握布局情况,工作中很有用。

o 键:切换android和ios的预览模式。

q 键:退出调试预览模式。

get install [package]

get remove [package]

快速开始

基础

获取设备宽高

MediaQuery

dart
// 获取设备宽度和高度
// build方法中使用
    final size = MediaQuery.of(context).size;
// size.width size.height

查看设备

flutter devices  
$ flutter devices   

Flutter assets will be downloaded from https://storage.flutter-io.cn. Make sure you trust this source!
3 connected devices:
Windows (desktop) • windows • windows-x64    • Microsoft Windows [版本 10.0.19043.1766]
Chrome (web)      • chrome  • web-javascript • Google Chrome 103.0.5060.114
Edge (web)        • edge    • web-javascript • Microsoft Edge 103.0.1264.49

运行在所有的设备

flutter run -d all

指定设备运行

flutter run -d chrome

- 基础组件

按钮 Button

dart
ElevatedButton(onPressed: (){}, child: const Text("普通按钮")),
TextButton(onPressed: (){}, child: const Text("文本按钮")),
OutlinedButton(onPressed: (){}, child: const Text("边框按钮")),
IconButton(onPressed: (){}, icon: const Icon(Icons.thumb_down)),// 图标按钮

ElevatedButton.icon(onPressed: (){}, icon: const Icon(Icons.send),label: const Text("带图标的按钮"),),
TextButton.icon(onPressed: (){}, icon: const Icon(Icons.send),label: const Text("带图标的文字按钮"),),
OutlinedButton.icon(onPressed: (){}, icon: const Icon(Icons.send),label: const Text("带图标的边框按钮"),),


// shape  圆角 圆形
// side 边框
//
//
ElevatedButton(onPressed: (){}, style:const ButtonStyle(
              backgroundColor:  WidgetStatePropertyAll(Colors.red),
              foregroundColor:  WidgetStatePropertyAll(Colors.amberAccent),
              shape: WidgetStatePropertyAll(
                 RoundedRectangleBorder(borderRadius: BorderRadius.circular(42))// 圆角
                //CircleBorder(side: BorderSide(color: Colors.yellow)) //圆形
              )
            ),child:  const Text("修改样式"),),

图片 Image

dart
Image.asset("assets/imgs/120572574_p0.png",fit: BoxFit.fill)

圆形图片 ClipOval

dart
ClipOval(child: Image.asset(
                    "assets/imgs/120572574_p0.png",
                    fit: BoxFit.cover,
                  ),)

圆形图片 CircleAvatar

dart
const CircleAvatar(
                  radius: 100, // 半径
                  backgroundImage: AssetImage("assets/imgs/120572574_p0.png"),
                )

文本 Text

dart
const Text(
        "Text你好Flutter",
        textAlign: TextAlign.center, // 对齐
        style: TextStyle(
          color: Colors.blueAccent, // 颜色
          overflow: TextOverflow.ellipsis, // 裁剪
        ),
	),

单选 复选 Switch Checkbox

dart
Switch(
          value: _switchSelected,//当前状态
          onChanged:(value){
            //重新构建页面  
            setState(() {
              _switchSelected=value;
            });
          },
        ),
        Checkbox(
          value: _checkboxSelected,
          activeColor: Colors.red, //选中时的颜色
          onChanged:(value){
            setState(() {
              _checkboxSelected=value;
            });
          } ,
        )

输入框及表单 TextField Form

TextField 输入框

dart
Column(
  children: <Widget>[
    TextField(
      autofocus: true,
      decoration: InputDecoration(
        labelText: "用户名",
        hintText: "用户名或邮箱",
        prefixIcon: Icon(Icons.person)
      ),
    ),
    TextField(
      decoration: InputDecoration(
        labelText: "密码",
        hintText: "您的登录密码",
        prefixIcon: Icon(Icons.lock)
      ),
      obscureText: true,
    ),
  ],
);

Form 表单

dart
return Form(
      key: _formKey, //设置globalKey,用于后面获取FormState
      autovalidateMode: AutovalidateMode.onUserInteraction,
      child: Column(
        children: <Widget>[
          TextFormField(
            autofocus: true,
            controller: _unameController,
            decoration: InputDecoration(
              labelText: "用户名",
              hintText: "用户名或邮箱",
              icon: Icon(Icons.person),
            ),
            // 校验用户名
            validator: (v) {
              return v!.trim().isNotEmpty ? null : "用户名不能为空";
            },
          ),
          TextFormField(
            controller: _pwdController,
            decoration: InputDecoration(
              labelText: "密码",
              hintText: "您的登录密码",
              icon: Icon(Icons.lock),
            ),
            obscureText: true,
            //校验密码
            validator: (v) {
              return v!.trim().length > 5 ? null : "密码不能少于6位";
            },
          ),
          // 登录按钮
          Padding(
            padding: const EdgeInsets.only(top: 28.0),
            child: Row(
              children: <Widget>[
                Expanded(
                  child: ElevatedButton(
                    child: Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Text("登录"),
                    ),
                    onPressed: () {
                      // 通过_formKey.currentState 获取FormState后,
                      // 调用validate()方法校验用户名密码是否合法,校验
                      // 通过后再提交数据。
                      if ((_formKey.currentState as FormState).validate()) {
                        //验证通过提交数据
                      }
                    },
                  ),
                ),
              ],
            ),
          )
        ],
      ),
    );

进度指示器 条 圆

LinearProgressIndicator 条

dart
// 模糊进度条(会执行一个动画)
LinearProgressIndicator(
  backgroundColor: Colors.grey[200],
  valueColor: AlwaysStoppedAnimation(Colors.blue),
),
//进度条显示50% 静止
LinearProgressIndicator(
  backgroundColor: Colors.grey[200],
  valueColor: AlwaysStoppedAnimation(Colors.blue),
  value: .5, 
)

CircularProgressIndicator 圆

dart
// 模糊进度条(会执行一个旋转动画)
CircularProgressIndicator(
  backgroundColor: Colors.grey[200],
  valueColor: AlwaysStoppedAnimation(Colors.blue),
),
//进度条显示50%,会显示一个半圆 静止
CircularProgressIndicator(
  backgroundColor: Colors.grey[200],
  valueColor: AlwaysStoppedAnimation(Colors.blue),
  value: .5,
),

- 布局类组件

对其与相对定位 Align

alignment: Alignment.center同Center

Alignment(1,1) 右下角

Alignment(0,1) 左下角

dart
Align(alignment: Alignment.center,child: MyGardView())

Center

dart
Center(child: MyGardView())

线性布局 Row Column

dart
Row( //外部没有Container时,row自适应占满一行
    mainAxisSize: MainAxisSize.min, // 紧凑排列,宽度自适应内容
    mainAxisAlignment: MainAxisAlignment.spaceBetween, // 主轴对齐
    crossAxisAlignment: CrossAxisAlignment.center, // 次轴对齐 相对于外部容器对齐
    children: [
       IconContainer(Icons.home,color: Colors.green),
       IconContainer(Icons.home,color: Colors.green),
       IconContainer(Icons.home,color: Colors.green)
    ],
)
dart
Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween, // 主轴对齐
          crossAxisAlignment: CrossAxisAlignment.center, // 次轴对齐 相对于外部容器对齐
          children: [
            IconContainer(Icons.home, color: Colors.green),
            IconContainer(Icons.home, color: Colors.green),
            IconContainer(Icons.home, color: Colors.green)
          ],
        )

Wrap 流式布局

一般在 Row Column 组件内使用

dart
Padding(padding: EdgeInsets.all(10),
        child: Wrap(
            direction: Axis.horizontal, // 主轴方向
            alignment: WrapAlignment.spaceBetween, // 主轴对齐
            spacing: 10, // 水平间距
            runSpacing: 10, // 垂直间距
            children: [
              Text("123"),
              Text("123"),
              Text("123"),
              Text("123"),
              Text("123"),
            ],
      ),),

Flex 弹性布局

Expanded.child推荐套一层SizedBox()给个宽度或者高度

一般Expanded直接在Row和Column中结合使用

dart
	Flex(
        direction: Axis.vertical, // 对齐方式 水平/垂直
        children: [
          Expanded(flex:2, child: IconContainer(Icons.home, color: Colors.green),),
          Expanded(flex:1, child: IconContainer(Icons.home, color: Colors.green),),
        ],
      )
    // or
   Row(
        children: [
          Expanded(flex:2, child: IconContainer(Icons.home, color: Colors.green),),
          Expanded(flex:1, child: IconContainer(Icons.home, color: Colors.green),),
        ],
      )

Stack - Positioned 定位

相对于外部容器定位,如果没有,就是屏幕

Stack一般配合Container内使用

Positioned内的child如果是row和column,需要指定宽度和高度(Positioned中设置的宽度高度是子组件的)

dart
Container(
        width: 300,
        height: 400,
        color: Colors.pink,
        child: Stack(
          alignment: Alignment.center, // 对齐
          children: [
            Positioned( // 定位
                left: 0,
                bottom: 0,
                child: Container(
                  width: 100,
                  height: 100,
                  color: Colors.yellow,
                )),
            const Text("123123")
          ],
        ),
      )

- 容器类组件

交互 InkWell 添加交互

  • onTap: 当用户点击 InkWell 时调用的回调函数。
  • onDoubleTap: 当用户双击 InkWell 时调用的回调函数。
  • onLongPress: 当用户长按 InkWell 时调用的回调函数。
  • child: 需要被 InkWell 包裹的 widget。
  • splashColor: 墨水涟漪动画的颜色。
  • highlightColor: InkWell 高亮时的背景颜色。

容器 Container

Container只设置宽高的话建议使用SizedBox

dart
    return Container(
        width: 200,
        height: 200,
        alignment: Alignment.center, // 元素居中对齐
        margin: const EdgeInsets.fromLTRB(0, 20, 0, 0),
        decoration: const BoxDecoration(color: Colors.amber),
        child: Text("123")
    );

宽高 SizedBox

Container只设置宽高的话建议使用SizedBox

dart
const SizedBox(
        height: 5,
    	width: 5
	),

填充 Padding

dart
Padding(padding: EdgeInsets.all(10), child: MyGardView())

装饰容器 BoxDecoration

要在容器中

dart
BoxDecoration(
                    color: Color.fromARGB(183, 202, 190, 190), // 颜色
                    borderRadius: BorderRadius.circular(30), //圆角
                  )
    
    
DecoratedBox(
   decoration: BoxDecoration(
     gradient: LinearGradient(colors:[Colors.red,Colors.orange.shade700]), //背景渐变
     borderRadius: BorderRadius.circular(3.0), //3像素圆角
     boxShadow: [ //阴影
       BoxShadow(
         color:Colors.black54,
         offset: Offset(2.0,2.0),
         blurRadius: 4.0
       )
     ]
   ),
  child: Padding(
    padding: EdgeInsets.symmetric(horizontal: 80.0, vertical: 18.0),
    child: Text("Login", style: TextStyle(color: Colors.white),),
  )
)

屏幕宽度 AspectRatio

1 / 1, // 宽高等于屏幕宽度

2 / 1,// 宽等于屏幕宽度,高度为屏幕宽度一半

3 / 1,// 宽等于屏幕宽度,高度为屏幕宽度三分之一

16/9,// 16:9的宽高比 图片

dart
AspectRatio(
        aspectRatio: 1 / 1, // 容器 相对于屏幕宽度 子元素宽高比
        child: Container(
          color: Colors.tealAccent,
        ),
      )

卡片 Card

dart
          Card(
            elevation: 20, // 阴影深度
            shadowColor: Colors.amber, // 阴影颜色
            color: Colors.black12, //  背景颜色
            margin: const EdgeInsets.all(10),// 边距
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(10) // 圆角
            ), 
            child: const Column(
              children: [
                ListTile(
                  title: Text("张三"),
                  subtitle: Text("资深美食迁移家"),
                ),
                Divider(),
                ListTile(
                  title: Text("电话:123123123"),
                ),
                ListTile(
                  title: Text("电话:123123123"),
                ),
              ],
            ),
          ),


// 实例
// 图文列表
Card(
            elevation: 20, // 阴影深度
            margin: const EdgeInsets.all(10), // 边距
            shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(10) // 圆角
                ),
            child: Column(
              children: [
                AspectRatio(
                  aspectRatio: 16 / 9,
                  child: Image.asset(
                    "assets/imgs/120572574_p0.png",
                    fit: BoxFit.cover,
                  ),
                ),
                ListTile(
                  leading: ClipOval(child: Image.asset(
                    "assets/imgs/120572574_p0.png",
                    fit: BoxFit.cover,
                  ),),
                  title: const Text("张三"),
                  subtitle: const Text("资深美食迁移家"),
                ),
              ],
            ),
          ),

- Scaffold 页面骨架

drawer 抽屉组件

dart
return Obx(
      () => Scaffold(
        appBar: AppBar(
          title: const Text('HomeView'),
          centerTitle: true,
          backgroundColor: Colors.transparent, // 背景透明
          elevation: 0, // 阴影取消
        ),
        // body: controller.pages[controller.currentIndex.value], // 无缓存
        body: PageView(
          scrollDirection: Axis.horizontal,
          controller: controller.pageController,
          children: controller.pages,
          onPageChanged: (index) {
            controller.setCuttentIndex(index);
          },
        ), // 带缓存 也可滑动 PageView
        // 浮动按钮
        floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
        floatingActionButton: FloatingActionButton(
          onPressed: () {},
          child: const Icon(Icons.add),
        ),
        bottomNavigationBar: BottomNavigationBar(
          iconSize: 25, // icon大小
          fixedColor: Colors.red, // 选中颜色
          currentIndex: controller.currentIndex.value, // 选中索引
          type: BottomNavigationBarType.fixed, // 超过3个菜单时需要配置
          onTap: (index) {
            controller.setCuttentIndex(index);
            controller.pageController.jumpToPage(index); // PageView跳转
          },
          items: [
            BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"),
            BottomNavigationBarItem(icon: Icon(Icons.category), label: "分类"),
            BottomNavigationBarItem(icon: Icon(Icons.people), label: "服务"),
            BottomNavigationBarItem(icon: Icon(Icons.people), label: "购物车"),
            BottomNavigationBarItem(icon: Icon(Icons.people), label: "用户"),
          ],
        ),
      ),
    );

AppBar

dart
AppBar(
          leading: controller.flag.value
              ? null
              : Icon(FontAwesomeIcons.adn, color: Colors.white),
          leadingWidth: controller.flag.value ? 0 : ScreenAdapter.width(140),
          title: AnimatedContainer(
            width: ScreenAdapter.width(controller.flag.value ? 920 : 620),
            height: ScreenAdapter.height(96),
            decoration: BoxDecoration(
              color: Color.fromARGB(59, 202, 190, 190),
              borderRadius: BorderRadius.circular(30),
            ),
            duration: Duration(milliseconds: 300),
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Padding(
                  padding: EdgeInsets.fromLTRB(
                    ScreenAdapter.width(20),
                    ScreenAdapter.width(0),
                    ScreenAdapter.width(4),
                    ScreenAdapter.width(0),
                  ),
                  child: Icon(
                    Icons.search,
                    size: ScreenAdapter.width(42),
                    color: Colors.black54,
                  ),
                ),
                Text(
                  "手机${controller.flag.value}",
                  style: TextStyle(
                    fontSize: ScreenAdapter.fontSize(36),
                    color: Colors.black54,
                  ),
                ),
                Expanded(
                  child: Align(
                    alignment: Alignment.centerRight, // 将 Icon 对齐到右侧
                    child: Padding(
                      padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
                      child: Icon(
                        Icons.qr_code_scanner_outlined,
                        size: ScreenAdapter.width(42),
                        color: Colors.black54,
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
          centerTitle: true,
          backgroundColor: controller.flag.value
              ? Colors.white
              : Colors.transparent, // 背景透明
          elevation: 0, // 阴影取消
          actions: [
            IconButton(onPressed: () {}, icon: Icon(Icons.qr_code)),
            IconButton(onPressed: () {}, icon: Icon(Icons.message)),
          ],
        )

- 可滚动组件

列表 ListView-ListTile

dart
    return ListView(
      children: [
        Container(
          height: 150,
          decoration: const BoxDecoration(
            color: Colors.blue,
          ),
          child: ListTile(
            leading: Image.asset("assets/imgs/120572574_p0.png"),
            title: const Text("琳芙斯"),
            subtitle: const Text("ID:1234567890"),
            trailing: const Icon(Icons.chevron_right_sharp),
          ),
        ),
        const Divider(),
        ListTile(
          leading: Image.asset("assets/imgs/120572574_p0.png"),// 左边图片
          title: const Text("这里是一篇新闻内容标题"),// 标题
          subtitle: const Text("这是而二级标题"),// 子标题
          trailing: const Icon(Icons.chevron_right_sharp),// 右边图片
        ),
      ],
    );

AnimatedList

文档

类似ListView,多了动画

dart

网格 GridView

dart
GridView.count(
      padding:const EdgeInsets.all(10),// 边距
      crossAxisSpacing: 10,// 水平间距
      mainAxisSpacing: 10, // 垂直间距
      crossAxisCount: 2,// 一行显示多少个
      childAspectRatio: 0.8,// 宽高比
      children: _initGridViewData(),
    );

PageView 与页面缓存 (轮播)

链接

dart
/// controller
final PageController pageController = PageController(initialPage: 2);
// controller.pageController.jumpToPage(index); // PageView跳转
/// view
PageView(
        scrollDirection: Axis.vertical, // 滑动方向,默认水平
        controller: controller.pageController,
        physics: NeverScrollableScrollPhysics(), //禁止左右滑动
        children: controller.pages,
        onPageChanged: (index) {
        	controller.setCuttentIndex(index);
        },
)

禁止左右滑动

dart
physics: const NeverScrollableScrollPhysics(),   //禁止左右滑动

示例

dart
/// controller
class TabsController extends GetxController {
  final currentIndex = 2.obs;
  final PageController pageController = PageController(initialPage: 2);
  final List<Widget> pages = const [
    HomeView(),
    CategoryView(),
    GiveView(),
    CartView(),
    UserView(),
  ];
  /// 更新tabbar
  setCuttentIndex(index) {
    currentIndex.value = index;
  }
}

/// view
Widget build(BuildContext context) {
    return Obx(
      () => Scaffold(
        // body: controller.pages[controller.currentIndex.value], // 无缓存
        body: PageView(
          scrollDirection: Axis.horizontal,
          controller: controller.pageController,
          physics: NeverScrollableScrollPhysics(),
          children: controller.pages,
          onPageChanged: (index) {
            controller.setCuttentIndex(index);
          },
        ), // 带缓存 也可滑动 PageView
        // 浮动按钮
        // floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
        // floatingActionButton: FloatingActionButton(
        //   onPressed: () {},
        //   child: const Icon(Icons.add),
        // ),
        bottomNavigationBar: BottomNavigationBar(
          iconSize: 25, // icon大小
          fixedColor: Colors.red, // 选中颜色
          currentIndex: controller.currentIndex.value, // 选中索引
          type: BottomNavigationBarType.fixed, // 超过3个菜单时需要配置
          onTap: (index) {
            controller.setCuttentIndex(index);
            controller.pageController.jumpToPage(index); // PageView跳转
          },
          items: [
            BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"),
            BottomNavigationBarItem(icon: Icon(Icons.category), label: "分类"),
            BottomNavigationBarItem(icon: Icon(Icons.people), label: "服务"),
            BottomNavigationBarItem(icon: Icon(Icons.people), label: "购物车"),
            BottomNavigationBarItem(icon: Icon(Icons.people), label: "用户"),
          ],
        ),
      ),
    );
  }

- 功能型组件

导航返回拦截(WillPopScope)

* 配置自定义IconFont

yaml
flutter:
  fonts:
    - family: xmIcon
      fonts:
        - asset: assets/fonts/iconfont.ttf
dart
/// xmFonts.dart
import 'package:flutter/material.dart';

class XmFontsIcon {
  static const IconData xiaomi=IconData(
    0xe609, // "unicode": "e609",   0x + unicode
    fontFamily: "xmIcon", // 对应 yaml 的 family
    matchTextDirection: true
  );
  static const IconData weixiufuwu=IconData(
    0xe609,
    fontFamily: "xmIcon",
    matchTextDirection: true
  );
}

使用

dart
Icon(XmFontsIcon.xiaomi) // 对应上面的class

* 透明状态栏

dart
//// main.dart
import 'package:flutter/services.dart';

void main() => bootstrap();

bootstrap() async {
  // flutter 修改状态栏颜色  透明状态栏
  SystemUiOverlayStyle systemUiOverlayStyle = const SystemUiOverlayStyle(
    statusBarColor: Colors.transparent,
  );
  SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);

  // await ApplicationInit.init();
  runApp(const Application());
}

自定义AppBar

dart

* 屏幕适配方案

安装

flutter_screenutil: ^5.9.3	# 屏幕适配方案

配置

dart
/// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_xmshop/application.dart';


void main() => bootstrap();

bootstrap() async {
  // await ApplicationInit.init();
  runApp(const Application());
}
dart
/// application.dart
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_xmshop/app/routes/app_pages.dart';
import 'package:get/get.dart';


class Application extends StatelessWidget{
  const Application({super.key});

  @override
  Widget build(BuildContext context) {
    return ScreenUtilInit(
      designSize: const Size(1080, 2400), //设计稿 宽 高
      minTextAdapt: true,
      splitScreenMode: true,
      builder: (BuildContext context, Widget? child) => 
          MediaQuery(
            data: MediaQuery.of(context).copyWith(textScaler: const TextScaler.linear(1.0)),
            child: GetMaterialApp(
              debugShowCheckedModeBanner: false,
              title: '小米商城',
              // initialBinding: LoginBinding(),
              // theme: AppTheme.light,
              // darkTheme: AppTheme.dark,
              initialRoute: AppPages.INITIAL, // 首页
              getPages: AppPages.routes, // 路由表
              defaultTransition: Transition.rightToLeft,
              // builder: EasyLoading.init(),
            )
          ),
    );
  }
}

直接使用

dart
200.w  //宽度
200.h  //高度
200.r  //较宽高 小的一个
200.sp //字体
1.sw //设备宽度
1.sh //设备高度
dart
Container(
        width: 1080.w, // 设计稿宽度.w
        height: 200.h, // 设计稿高度.h
        child: Text('CartView is working', style: TextStyle(fontSize: 20.sp)),
),

封装使用

组件层改过3次调用方式,保险起见封装一层

ScreenAdapter.width(10);

dart
import 'package:flutter_screenutil/flutter_screenutil.dart';

/// 封装flutter_screenutil
class ScreenAdapter {
  static width(num v) {
    return v.w;
  }

  static height(num v) {
    return v.h;
  }

  static fontSize(num v) {
    return v.sp;
  }

  static getScreenWidth() {
    return 1.sw;
  }

  static getScreenHeight() {
    return 1.sh;
  }
}

* Swiper轮播图插件

文档

yaml
flutter_swiper_view: ^1.1.8 # 轮播图插件
dart
Swiper(
              itemCount: controller.swiperList.length,
              itemBuilder: (context, index) {
                return Image.network(
                  controller.swiperList[index]["url"],
                  fit: BoxFit.fill,
                );
              },
              pagination: SwiperPagination(
                builder: SwiperPagination.rect,
                
              ),
              // control: SwiperControl(),
              autoplay: true, // 自动轮播
              loop: true,
              duration: 3000, // 切换时间
            )

* 瀑布流插件

yaml
flutter_staggered_grid_view: ^0.6.2 # 瀑布流插件
dart
MasonryGridView.count(
            crossAxisCount: 2, // 2列
            mainAxisSpacing: 15, // 水平间距
            crossAxisSpacing: 15, // 垂直间距
            itemCount: 20, // 数量
            shrinkWrap: true, // 收缩,让元素自适应宽度
            physics: NeverScrollableScrollPhysics(), //禁止滑动
            itemBuilder: (context, index) {
              var height = 50 + 150 * Random().nextDouble(); // 随机高度
              return Container(
                height: height,
                decoration: BoxDecoration(
                  border: Border.all(color: Colors.yellow, width: 1),
                  borderRadius: BorderRadius.circular(10),
                ),
                child: Text("data"),
              );
            },
          )

* Dio 网络请求

yaml
dio: ^5.8.0+1
dart
  getFocusData() async {
    var response = await Dio().get("https://miapp.itying.com/api/focus");
    swiperList.value = response.data["result"];
    update();
  }

封装

* GetX

组件

通知 snackbar

dart
Get.snackbar('title','messagee',
             colorText?: Colors.blue,// 文字颜色
             titleText?: Text("title"),// 覆盖第一个参数
             messageText?: Text("messagee"),// 覆盖第二个参数
             borderWidth?: 10, //边框宽
      		 borderRadius?: 50,//边框圆角
     		 borderColor?: Colors.blue,// 边框颜色
             duration?: const Duration(seconds: 5),// 显示时间
             icon: const Icon(Icons.add_comment_sharp), // 左侧icon
            
            );

默认弹窗 defaultDialog

如果配合其他组件,需要先关闭Get.back()再使用其他组件,因为同一时刻只能显示一个组件

dart
Get.defaultDialog(
          title: "提示",
          content: const Text("这里是通知"),
          confirm: TextButton(
              onPressed: () {
                Get.back(); // 关闭弹窗
                Get.snackbar("提示", "完成通知"); // 如果配合其他组件,需要先关闭再使用,因为同一时刻只能显示一个组件
              },
              child: const Text("确认")),
    	  cancel: TextButton(
              onPressed: () {
                Get.back();
              },
              child: const Text("取消")),
          barrierDismissible: false // 点击外部暗区是否关闭
);

底部弹窗 bottomSheet

dart
 Get.bottomSheet(
        Container(
          height: 200,
          color: Colors.amber,
          child: Center(
            child: ElevatedButton(
                onPressed: () {
                  Get.back();
                },
                child: const Text('退出')),
          ),
        ),
        isDismissible: false, //是否点击暗区关闭
        enableDrag: false, //是否向下拖拽关闭
      );

主题切换 changeTheme

dart
Get.changeTheme( Get.isDarkMode ? ThemeData.light() : ThemeData.dark() );// 变更主题,亮暗切换

路由管理

Getx支持 免上下文(context) 使用 路由/snackbars/dialogs/bottomsheets

普通导航

导航到新的页面。

dart
Get.to(
                    const MyView(),
                    fullscreenDialog: true,
                    transition: Transition.upToDown,//线性
                    duration: const Duration(milliseconds: 500),// 过渡时间
                    curve: Curves.bounceIn // 过渡
);

关闭SnackBars、Dialogs、BottomSheets或任何你通常会用Navigator.pop(context)关闭的东西。

dart
Get.back(result: Text("返回上一页带的参数"));

进入下一个页面,但没有返回上一个页面的选项(用于SplashScreens,登录页面等)。

dart
Get.off(NextScreen());

进入下一个界面并取消之前的所有路由(在购物车、投票和测试中很有用)。

dart
Get.offAll(NextScreen());

要导航到下一条路由,并在返回后立即接收或更新数据。

dart
var data = await Get.to(Payment());

在另一个页面上,发送前一个路由的数据。

dart
Get.back(result: 'success');
dart
// 默认的Flutter导航
Navigator.of(context).push(
  context,
  MaterialPageRoute(
    builder: (BuildContext context) {
      return HomePage();
    },
  ),
);

// 使用Flutter语法获得,而不需要context。
navigator.push(
  MaterialPageRoute(
    builder: (_) {
      return HomePage();
    },
  ),
);

// get语法 (这要好得多)
Get.to(HomePage());

别名导航

导航到下一个页面

dart
Get.toNamed("/NextScreen");

浏览并删除前一个页面。

dart
Get.offNamed("/NextScreen");

浏览并删除所有以前的页面。

dart
Get.offAllNamed("/NextScreen");

要定义路由,使用GetMaterialApp。

dart
void main() {
  runApp(
    GetMaterialApp(
      initialRoute: '/',
      getPages: [
        GetPage(name: '/', page: () => MyHomePage()),
        GetPage(name: '/second', page: () => Second()),
        GetPage(
          name: '/third',
          page: () => Third(),
          transition: Transition.zoom  
        ),
      ],
    )
  );
}

要处理到未定义路线的导航(404错误),可以在GetMaterialApp中定义unknownRoute页面。

dart
void main() {
  runApp(
    GetMaterialApp(
      unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
      initialRoute: '/',
      getPages: [
        GetPage(name: '/', page: () => MyHomePage()),
        GetPage(name: '/second', page: () => Second()),
      ],
    )
  );
}

路由传参

Get提供高级动态URL,就像在Web上一样。

dart
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");

获取参数:

dart
print(Get.parameters['id']);
// out: 354
print(Get.parameters['name']);
// out: Enzo

你也可以用Get轻松接收NamedParameters。

dart
void main() {
  runApp(
    GetMaterialApp(
      initialRoute: '/',
      getPages: [
      GetPage(
        name: '/',
        page: () => MyHomePage(),
      ),
      GetPage(
        name: '/profile/',
        page: () => MyProfile(),
      ),
       //你可以为有参数的路由定义一个不同的页面,也可以为没有参数的路由定义一个不同的页面,但是你必须在不接收参数的路由上使用斜杠"/",就像上面说的那样。
       GetPage(
        name: '/profile/:user',
        page: () => UserProfile(),
      ),
      GetPage(
        name: '/third',
        page: () => Third(),
        transition: Transition.cupertino  
      ),
     ],
    )
  );
}

发送数据

dart
Get.toNamed("/second/34954");

在第二个页面上,通过参数获取数据

dart
print(Get.parameters['user']);
// out: 34954

或像这样发送多个参数

dart
Get.toNamed("/profile/34954?flag=true");

在第二个屏幕上,通常按参数获取数据

dart
print(Get.parameters['user']);
print(Get.parameters['flag']);
// out: 34954 true

现在,你需要做的就是使用Get.toNamed()来导航你的别名路由,不需要任何context(你可以直接从你的BLoC或Controller类中调用你的路由),当你的应用程序被编译到web时,你的路由将出现在URL中。

获取路由参数 arguments
dart
Get.arguments['name']

重定向日志

你可以选择重定向所有来自GetX的日志信息。 如果你想使用你自己喜欢的日志包,并想查看那里的日志。

dart
GetMaterialApp(
  enableLog: true,
  logWriterCallback: localLogWriter,
);

void localLogWriter(String text, {bool isError = false}) {
  // 在这里把信息传递给你最喜欢的日志包。
  // 请注意,即使enableLog: false,日志信息也会在这个回调中被推送。
  // 如果你想的话,可以通过GetConfig.isLogEnable来检查这个标志。
}

其他高级Api

dart
// 给出当前页面的args。
Get.arguments

//给出以前的路由名称
Get.previousRoute

// 给出要访问的原始路由,例如,rawRoute.isFirst()
Get.rawRoute

// 允许从GetObserver访问Rounting API。
Get.routing

// 检查 snackbar 是否打开
Get.isSnackbarOpen

// 检查 dialog 是否打开
Get.isDialogOpen

// 检查 bottomsheet 是否打开
Get.isBottomSheetOpen

// 删除一个路由。
Get.removeRoute()

//反复返回,直到表达式返回真。
Get.until()

// 转到下一条路由,并删除所有之前的路由,直到表达式返回true。
Get.offUntil()

// 转到下一个命名的路由,并删除所有之前的路由,直到表达式返回true。
Get.offNamedUntil()

//检查应用程序在哪个平台上运行。
GetPlatform.isAndroid
GetPlatform.isIOS
GetPlatform.isMacOS
GetPlatform.isWindows
GetPlatform.isLinux
GetPlatform.isFuchsia

//检查设备类型
GetPlatform.isMobile
GetPlatform.isDesktop
//所有平台都是独立支持web的!
//你可以知道你是否在浏览器内运行。
//在Windows、iOS、OSX、Android等系统上。
GetPlatform.isWeb


// 相当于.MediaQuery.of(context).size.height,
//但不可改变。
Get.height
Get.width

// 提供当前上下文。
Get.context

// 在你的代码中的任何地方,在前台提供 snackbar/dialog/bottomsheet 的上下文。
Get.contextOverlay

// 注意:以下方法是对上下文的扩展。
// 因为在你的UI的任何地方都可以访问上下文,你可以在UI代码的任何地方使用它。

// 如果你需要一个可改变的高度/宽度(如桌面或浏览器窗口可以缩放),你将需要使用上下文。
context.width
context.height

// 让您可以定义一半的页面、三分之一的页面等。
// 对响应式应用很有用。
// 参数: dividedBy (double) 可选 - 默认值:1
// 参数: reducedBy (double) 可选 - 默认值:0。
context.heightTransformer()
context.widthTransformer()

/// 类似于 MediaQuery.of(context).size。
context.mediaQuerySize()

/// 类似于 MediaQuery.of(context).padding。
context.mediaQueryPadding()

/// 类似于 MediaQuery.of(context).viewPadding。
context.mediaQueryViewPadding()

/// 类似于 MediaQuery.of(context).viewInsets。
context.mediaQueryViewInsets()

/// 类似于 MediaQuery.of(context).orientation;
context.orientation()

///检查设备是否处于横向模式
context.isLandscape()

///检查设备是否处于纵向模式。
context.isPortrait()

///类似于MediaQuery.of(context).devicePixelRatio。
context.devicePixelRatio()

///类似于MediaQuery.of(context).textScaleFactor。
context.textScaleFactor()

///查询设备最短边。
context.mediaQueryShortestSide()

///如果宽度大于800,则为真。
context.showNavbar()

///如果最短边小于600p,则为真。
context.isPhone()

///如果最短边大于600p,则为真。
context.isSmallTablet()

///如果最短边大于720p,则为真。
context.isLargeTablet()

///如果当前设备是平板电脑,则为真
context.isTablet()

///根据页面大小返回一个值<T>。
///可以给值为:
///watch:如果最短边小于300
///mobile:如果最短边小于600
///tablet:如果最短边(shortestSide)小于1200
///desktop:如果宽度大于1200
context.responsiveValue<T>()

# 开发填充

圆角图片

Container中使用BoxDecoration

dart
Container(
        height: ScreenAdapter.height(420),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(ScreenAdapter.width(20)),
          image: DecorationImage(
            fit: BoxFit.cover,
            image: AssetImage("assets/imgs/xiaomiBanner2.png"),
          ),
        ),
      )