目录
前言
作为一个开发了一年多的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 配置起来也比较的麻烦。至少不影响运行,还能接受。
标签:Widget,const,https,Text,重新学习,笔记,context,Flutter From: https://www.cnblogs.com/gclove2000/p/18201696