Flutter 学习笔记:再次重新学习Flutter

前言

作为一个开发了一年多的Uniapp的.NET 开发工程师,我打算去用FLutter完整的写一个小的程序的Demo。经过两年的编程开发学习,我作为一个普通的程序员,有我的技术选型的思考。

  • 必须主流,不一定是最受欢迎的,但是也是市场占比比较大的。比如前端的React,Vue2,Vue3。我认为你三个选择哪一个都可以,你喜欢就行。跨平台目前的主流技术是Uniapp,React Native和Flutter,我个人选择了Flutter。
  • 生态一定要很完善,Uniapp的生态就很差,虽然背靠Web生态,但是Uniapp的Web和普通的Web还是有区别的。而且Uniapp的社区很差,本身就是兼容国内的小程序生态的。
  • 选择简单易上手的方式,能用组件就用组件,能快速实现就快速实现。保持“不求甚解”的态度,先不用知道底层是怎么写的,代码能跑,需求能满足就行。等你很了解,很熟练了之久,再去研究也不迟。
  • 能真实的用到业务中。移动端开发的需求应该挺常见的,打算Flutter熟练使用之后,后面的开发都用Flutter了

相关链接

Flutter中文文档:https://docs.flutter.cn/get-started/install

构建您的第一个 Flutter 应用:https://codelabs.developers.google.com/codelabs/flutter-codelab-first?hl=zh-cn#8

Flutter 开发学习笔记(0):环境配置:https://blog.csdn.net/qq_44695769/article/details/137133411?spm=1001.2014.3001.5501

Flutter 开发学习笔记(1):第一个简单的Flutter项目(上):https://blog.csdn.net/qq_44695769/article/details/137176032?spm=1001.2014.3001.5501

Flutter 开发学习笔记(2):第一个简单的Flutter项目(下):https://blog.csdn.net/qq_44695769/article/details/137186682?spm=1001.2014.3001.5501

Flutter 开发学习笔记(3):第三方UI库的引入:https://blog.csdn.net/qq_44695769/article/details/137266619?spm=1001.2014.3001.5501

环境安装

因为好久没写Flutter了,打算从新开始再走一遍官方的新手教程

常见问题

Flutter新建项目运行报错Exception in thread “main” java.net.ConnectException: Connection timed out: connect:https://www.cnblogs.com/chorkiu/p/14767567.html

Flutter运行第一个项目时出现javax.net.ssl.SSLHandshakeException的一些解决思路:https://blog.csdn.net/fwhdzh/article/details/106632072

Flutter卡在Running ‘gradle assembleDebug‘最完整解决:https://blog.csdn.net/qq_43596067/article/details/107710915

Flutter编译卡在Running Gradle task ‘assembleDebug‘:https://blog.csdn.net/shiyangkai/article/details/124632441?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171169554516800215013010%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=171169554516800215013010&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-124632441-null-null.142%5Ev100%5Econtrol&utm_term=Flutter%20Running%20Gradle%20task%20assembleDebug...&spm=1018.2226.3001.4187

运行新建Flutter项目, 报错Exception in thread “main“ java.net.ConnectException: Connection timed out: connect:https://blog.csdn.net/a18339063397/article/details/125506390?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171169533516800225518971%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=171169533516800225518971&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-125506390-null-null.142%5Ev100%5Econtrol&utm_term=Flutter%20Exception%20in%20thread%20main%20java.net.ConnectException%3A%20Connection%20refused%3A%20connect&spm=1018.2226.3001.4187

Android studio配置Flutter开发环境报错问题解决:https://blog.csdn.net/lu202032/article/details/134421156?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171169787916800215067985%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=171169787916800215067985&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~times_rank-2-134421156-null-null.142%5Ev100%5Econtrol&utm_term=flutter%20jdk17&spm=1018.2226.3001.4187

环境编译成功

Flutter 开发学习笔记(0):环境配置:https://blog.csdn.net/qq_44695769/article/details/137133411?spm=1001.2014.3001.5501

跟着我这个文章走下去,把里面的地址替换成国内的镜像地址,SDK换成本地SDK,编译一遍通过就OK了。

项目启动页面

分析项目

程序入口

Flutter的程序入口是一个 main函数,在默认的示例里面,指向的是一个MyApp。Flutter的程序编写,更像是面向组件化的程序逻辑编写,Flutter为什么称之为嵌套地狱,因为他特别喜欢用组件化的思想

Wiget

在Flutter中,所有的组件都是继承Widget的,就像网页的Div一样,在Flutter中,一切皆widget

在Flutter中写代码,感觉就像写无限细分的Div组件一样

<A>
  </B>
</A>
...
<B>
  </C>
</B>
...
<C>
  </D>
</C>
...
<D>
  </F>
</D>

动态更新

在Widget中,分为无状态Widget和有状态Widget。简单来说,动态Widget中,会给你一个修改变量的钩子,有点像React,让你去修改Widget的中显示的参数,其实就是一个setState的钩子,这个和React或者Vue3的函数式编程的感觉很像

深度理解Flutter:有状态Widget与无状态Widget的详细对比:https://blog.csdn.net/HaiJun_Aion/article/details/135341129

如果你写过WPF,就知道WPF里面有一个依赖属性。在Flutter里面的状态和这个依赖属性差不多。状态就是Flutter的共享内存空间,无状态不代表Widget不可更改,而是Widget不维护内存空间,可以通过有状态的父组件更新广播,让无状态的子组件更新。

比如我们修改一下默认的启动项目

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

void main() {
  runApp(const MyApp());
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            // Text(
            //   '${_counter}',
            //   style: Theme.of(context).textTheme.headlineMedium,
            // ),
            TextWidget(text: '无状态Widget [${_counter}]')
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

class TextWidget extends StatelessWidget {
  const TextWidget({Key? key, required this.text}) : super(key: key);
  final String text;

  @override
  Widget build(BuildContext context) {
    return Text(
      text,
      textAlign: TextAlign.center,
      style: const TextStyle(
        fontSize: 40,
        color: Colors.red,
      ),
    );
  }
}

如果不清楚该怎么选择,那就能用无状态Widget,就用无状态Widget

按照教程初始化项目

构建您的第一个 Flutter 应用,创建项目:https://codelabs.developers.google.com/codelabs/flutter-codelab-first?hl=zh-cn#2

相关的配置我就不展开了,大家进去看看好了。

import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => MyAppState(),
      child: MaterialApp(
        title: 'Namer App',
        theme: ThemeData(
          useMaterial3: true,
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
        ),
        home: MyHomePage(),
      ),
    );
  }
}

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();

    return Scaffold(
      body: Column(
        children: [
          Text('A random idea:'),
          Text(appState.current.asLowerCase),
        ],
      ),
    );
  }
}

弱化Flutter编译检查

analysis_options.yaml中进行替换

include: package:flutter_lints/flutter.yaml

linter:
  rules:
    prefer_const_constructors: false
    prefer_final_fields: false
    use_key_in_widget_constructors: false
    prefer_const_literals_to_create_immutables: false
    prefer_const_constructors_in_immutables: false
    avoid_print: false

添加第一个按钮

注意,我这里的代码可能和教程的有点区别,有部分更新

    return Scaffold(
      body: Column(
        children: [
          const Text('A random idea:'),
          Text(appState.current.asLowerCase),
          ElevatedButton(
              onPressed: () {
                debugPrint('Hello Flutter!');
              },
              child: const Text("Click me"))
        ],
      ),
    );

快速嵌套组件化

由于我之前已经写过一次新手教程了,我后面就挑我觉得重要的讲了

有时候快捷键会卡住,得切换成英文输入法才行

数据更新:ChangeNotifier和StatefulWidget

用C# 的思想,ChangeNotifier其实就是全局变量。全局变量是成为屎山的重要前提,能不用全局变量就不用全局变量。数据流的流通应该是组件和组件之间互相引用的。反正就是能用StatefulWidget就不要用ChangeNotifier。

Dart中的委托

C# 最重要的三大设计,委托,Task和Linq。委托更是重中之重。委托其实就是指针的封装。

Dart中的委托,和C# 中的不太一样

final Function getNext;//Dart
Action getNext;//C#
final bool Function(string msg) isFavoriate;//Dart
Function<bool,string> isFavoriate;//C#

修改好的代码

import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => null,
      child: MaterialApp(
        title: 'Namer App',
        theme: ThemeData(
          useMaterial3: true,
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
        ),
        home: MyHomePage(),
      ),
    );
  }
}

void showMsg(String msg) {
  debugPrint("[main]: ${msg}");
}

class FavoriateModel {
  var current = WordPair.random();

  ///喜欢列表
  var favorites = <WordPair>[];
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var _selectedIndex = 0;
  FavoriateModel favorate = new FavoriateModel();

  void getNext() {
    setState(() {
      favorate.current = WordPair.random();
    });
  }

  /// 添加喜欢
  void toggleFavorite() {
    setState(() {
      if (favorate.favorites.contains(favorate.current)) {
        favorate.favorites.remove(favorate.current);
        showMsg("remove => ${favorate.current}");
      } else {
        favorate.favorites.add(favorate.current);
        showMsg("add => ${favorate.current}");
      }
    });
  }

  bool isFavoriate (WordPair str) {
    return this.favorate.favorites.contains(str);
  }

  @override
  Widget build(BuildContext context) {
    Widget _page;
    switch (_selectedIndex) {
      case 0:
        _page = GeneratorPage(
          appState: this.favorate,
          toggleFavorite: this.toggleFavorite,
          getNext: this.getNext,
          isFavoriate: this.isFavoriate,
        );
        break;
      case 1:
        _page = FavoraitePage(
          appState: favorate,
        );
        break;
      default:
        throw UnimplementedError("no widget in ${_selectedIndex}");
    }

    return LayoutBuilder(builder: (context, constraints) {
      return Scaffold(
        body: Row(
          children: [
            SafeArea(
              child: NavigationRail(
                extended: constraints.maxWidth >= 600,
                destinations: const [
                  NavigationRailDestination(
                    icon: Icon(Icons.home),
                    label: Text('Home'),
                  ),
                  NavigationRailDestination(
                    icon: Icon(Icons.favorite),
                    label: Text('Favorites'),
                  ),
                ],
                selectedIndex: _selectedIndex,
                onDestinationSelected: (value) {
                  showMsg('selected: $value');
                  setState(() {
                    _selectedIndex = value;
                  });
                },
              ),
            ),
            Expanded(
              child: Container(
                color: Theme.of(context).colorScheme.primaryContainer,
                child: _page,
              ),
            ),
          ],
        ),
      );
    });
  }
}

class FavoraitePage extends StatelessWidget {
  const FavoraitePage({super.key, required this.appState});

  final FavoriateModel appState;

  @override
  Widget build(BuildContext context) {
    var _message = <String>[];
    appState.favorites.forEach((item) {
      _message.add(item.toString());
    });
    _message.insert(0, "message");
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: _message.map((e) => Text(e)).toList(),
      ),
    );
  }
}

class GeneratorPage extends StatelessWidget {
  const GeneratorPage(
      {super.key,
      required this.appState,
      required this.toggleFavorite,
      required this.getNext,
      required this.isFavoriate});

  final Function toggleFavorite;
  final Function getNext;
  final bool Function(WordPair msg) isFavoriate;
  final FavoriateModel appState;

  @override
  Widget build(BuildContext context) {
    var pair = appState.current;

    IconData icon;

    if (this.isFavoriate(pair)) {
      icon = Icons.favorite;
    } else {
      icon = Icons.favorite_border;
    }

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          BigCard(pair: pair),
          const SizedBox(height: 10),
          Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              ElevatedButton.icon(
                onPressed: () {
                  toggleFavorite();
                },
                icon: Icon(icon),
                label: const Text('Like'),
              ),
              const SizedBox(width: 10),
              ElevatedButton(
                onPressed: () {
                  getNext();
                },
                child: const Text('Next'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class BigCard extends StatelessWidget {
  const BigCard({
    super.key,
    required this.pair,
  });

  final WordPair pair;

  @override
  Widget build(BuildContext context) {
    final _theme = Theme.of(context); //获取容器的样式

    return Center(
      child: Card(
        color: _theme.colorScheme.primary,
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Text(pair.asLowerCase),
        ),
      ),
    );
  }
}

运行效果

总结

还是不习惯使用Dart,语法上来说还可以,就是编辑器比较恶心。在Anriod Studio 里面,缩进是不能改的,默认2格。而且代码提示很垃圾,不说像Visual studio里面一样的智能提示了,连'switch','required'这种关键词有时候都提示不出来,怪不得Visual studio 用的人多。但是我又懒得配置VS Code,毕竟VS code 配置起来也比较的麻烦。至少不影响运行,还能接受。

posted @ 2024-05-21 13:31  gclove2000  阅读(43)  评论(0编辑  收藏  举报