Flutter跨平台开发详解
Flutter是Google开发的开源UI工具包,用于从单一代码库构建漂亮的、原生编译的多平台应用程序。Flutter使用Dart语言,提供了丰富的Widget系统和强大的性能,是现代跨平台开发的优秀选择。
核心价值
Flutter = 单一代码库 + 原生性能 + 丰富UI + 热重载
- 🚀 跨平台统一:一套代码运行在iOS、Android、Web、桌面
- ⚡ 原生性能:直接编译为原生代码,性能接近原生应用
- 🎨 丰富UI组件:Material Design和Cupertino风格组件
- 🔥 热重载:毫秒级代码更新,极大提升开发效率
- 📦 完整生态:丰富的第三方包和工具链
- 🎯 声明式UI:直观的UI构建方式,易于理解和维护
1. Dart语言基础
1.1 Dart语言特性
Dart是Flutter的编程语言,具有现代编程语言的特性,易于学习且功能强大。
- 基础语法
- 异步编程
- 集合操作
Dart基础语法
Dart基础语法示例
dart
1// 1. 变量声明2void main() {3 // 类型推断4 var name = 'Flutter';5 var year = 2024;6 var isAwesome = true;7 8 // 显式类型声明9 String language = 'Dart';10 int version = 3;11 bool isNullSafe = true;12 13 // 常量14 const pi = 3.14159;15 final currentTime = DateTime.now();16 17 // 空安全18 String? nullableName; // 可为空19 String nonNullableName = 'Flutter'; // 不可为空20 21 // 空值检查22 print(nullableName?.length ?? 0); // 安全调用23 24 // 集合类型25 List<String> fruits = ['apple', 'banana', 'orange'];26 Set<int> uniqueNumbers = {1, 2, 3, 4, 5};27 Map<String, int> scores = {28 'Alice': 95,29 'Bob': 87,30 'Charlie': 92,31 };32 33 // 展开操作符34 List<String> moreFruits = [...fruits, 'grape', 'kiwi'];35 36 // 条件表达式37 String message = isAwesome ? 'Flutter is awesome!' : 'Try Flutter';38 39 print('$name $year: $message');40}4142// 2. 函数定义43// 普通函数44String greet(String name, {String greeting = 'Hello'}) {45 return '$greeting, $name!';46}4748// 箭头函数49String greetShort(String name) => 'Hello, $name!';5051// 可选参数52void printInfo(String name, [int? age, String? city]) {53 print('Name: $name');54 if (age != null) print('Age: $age');55 if (city != null) print('City: $city');56}5758// 命名参数59void createUser({60 required String name,61 required String email,62 int age = 18,63 bool isActive = true,64}) {65 print('Creating user: $name ($email)');66}6768// 3. 类和对象69class Person {70 // 属性71 String name;72 int age;73 String? email;74 75 // 构造函数76 Person(this.name, this.age, {this.email});77 78 // 命名构造函数79 Person.guest() : name = 'Guest', age = 0;80 81 // Getter和Setter82 String get displayName => name.toUpperCase();83 84 set displayName(String value) {85 name = value.toLowerCase();86 }87 88 // 方法89 void introduce() {90 print('Hi, I\'m $name, $age years old.');91 }92 93 // 静态方法94 static Person fromJson(Map<String, dynamic> json) {95 return Person(96 json['name'] as String,97 json['age'] as int,98 email: json['email'] as String?,99 );100 }101 102 // 重写toString103 @override104 String toString() => 'Person(name: $name, age: $age)';105}106107// 4. 继承108class Student extends Person {109 String school;110 List<String> subjects;111 112 Student(String name, int age, this.school, this.subjects) 113 : super(name, age);114 115 @override116 void introduce() {117 super.introduce();118 print('I study at $school.');119 }120 121 void addSubject(String subject) {122 subjects.add(subject);123 }124}125126// 5. 抽象类和接口127abstract class Animal {128 String name;129 130 Animal(this.name);131 132 // 抽象方法133 void makeSound();134 135 // 具体方法136 void sleep() {137 print('$name is sleeping...');138 }139}140141class Dog extends Animal {142 Dog(String name) : super(name);143 144 @override145 void makeSound() {146 print('$name says: Woof!');147 }148}149150// 6. Mixin混入151mixin Flyable {152 void fly() {153 print('Flying high!');154 }155}156157mixin Swimmable {158 void swim() {159 print('Swimming gracefully!');160 }161}162163class Duck extends Animal with Flyable, Swimmable {164 Duck(String name) : super(name);165 166 @override167 void makeSound() {168 print('$name says: Quack!');169 }170}Dart异步编程
Dart异步编程详解
dart
1import 'dart:async';2import 'dart:convert';3import 'dart:io';45// 1. Future基础6Future<String> fetchUserData(int userId) async {7 // 模拟网络请求8 await Future.delayed(Duration(seconds: 2));9 10 if (userId <= 0) {11 throw Exception('Invalid user ID');12 }13 14 return 'User data for ID: $userId';15}1617// 2. 错误处理18Future<void> handleAsyncOperations() async {19 try {20 String userData = await fetchUserData(123);21 print('Success: $userData');22 } catch (e) {23 print('Error: $e');24 } finally {25 print('Cleanup completed');26 }27}2829// 3. 并发执行30Future<void> concurrentOperations() async {31 // 并行执行多个异步操作32 List<Future<String>> futures = [33 fetchUserData(1),34 fetchUserData(2),35 fetchUserData(3),36 ];37 38 try {39 List<String> results = await Future.wait(futures);40 print('All results: $results');41 } catch (e) {42 print('One or more operations failed: $e');43 }44 45 // 超时控制46 try {47 String result = await fetchUserData(1)48 .timeout(Duration(seconds: 1));49 print('Result: $result');50 } on TimeoutException {51 print('Operation timed out');52 }53}5455// 4. Stream流处理56class DataStream {57 // 创建Stream58 Stream<int> countStream(int max) async* {59 for (int i = 1; i <= max; i++) {60 await Future.delayed(Duration(milliseconds: 500));61 yield i;62 }63 }64 65 // 处理Stream66 Future<void> processStream() async {67 Stream<int> stream = countStream(5);68 69 // 监听Stream70 await for (int value in stream) {71 print('Received: $value');72 }73 74 // 使用Stream方法75 Stream<int> evenNumbers = countStream(10)76 .where((number) => number % 2 == 0)77 .map((number) => number * 2);78 79 List<int> results = await evenNumbers.toList();80 print('Even numbers doubled: $results');81 }82 83 // StreamController84 StreamController<String> _messageController = StreamController<String>();85 86 Stream<String> get messageStream => _messageController.stream;87 88 void addMessage(String message) {89 _messageController.add(message);90 }91 92 void dispose() {93 _messageController.close();94 }95}9697// 5. HTTP请求示例98class ApiService {99 static const String baseUrl = 'https://jsonplaceholder.typicode.com';100 101 Future<Map<String, dynamic>> getUser(int id) async {102 try {103 final response = await HttpClient()104 .getUrl(Uri.parse('$baseUrl/users/$id'))105 .then((request) => request.close());106 107 if (response.statusCode == 200) {108 String body = await response.transform(utf8.decoder).join();109 return json.decode(body) as Map<String, dynamic>;110 } else {111 throw Exception('Failed to load user: ${response.statusCode}');112 }113 } catch (e) {114 throw Exception('Network error: $e');115 }116 }117 118 Future<List<Map<String, dynamic>>> getUsers() async {119 try {120 final response = await HttpClient()121 .getUrl(Uri.parse('$baseUrl/users'))122 .then((request) => request.close());123 124 if (response.statusCode == 200) {125 String body = await response.transform(utf8.decoder).join();126 List<dynamic> jsonList = json.decode(body) as List<dynamic>;127 return jsonList.cast<Map<String, dynamic>>();128 } else {129 throw Exception('Failed to load users: ${response.statusCode}');130 }131 } catch (e) {132 throw Exception('Network error: $e');133 }134 }135}136137// 6. Isolate并发138import 'dart:isolate';139140class IsolateExample {141 // 在Isolate中执行计算密集型任务142 static void heavyComputation(SendPort sendPort) {143 int result = 0;144 for (int i = 0; i < 1000000000; i++) {145 result += i;146 }147 sendPort.send(result);148 }149 150 Future<int> runHeavyTask() async {151 ReceivePort receivePort = ReceivePort();152 153 await Isolate.spawn(heavyComputation, receivePort.sendPort);154 155 int result = await receivePort.first as int;156 return result;157 }158}159160// 使用示例161void main() async {162 print('=== Dart异步编程示例 ===');163 164 // 基础异步操作165 await handleAsyncOperations();166 167 // 并发操作168 await concurrentOperations();169 170 // Stream处理171 DataStream dataStream = DataStream();172 await dataStream.processStream();173 174 // API调用175 ApiService apiService = ApiService();176 try {177 Map<String, dynamic> user = await apiService.getUser(1);178 print('User: ${user['name']}');179 } catch (e) {180 print('API Error: $e');181 }182 183 // Isolate示例184 IsolateExample isolateExample = IsolateExample();185 print('Starting heavy computation...');186 int result = await isolateExample.runHeavyTask();187 print('Computation result: $result');188 189 dataStream.dispose();190}Dart集合操作
Dart集合操作详解
dart
1void main() {2 demonstrateListOperations();3 demonstrateSetOperations();4 demonstrateMapOperations();5 demonstrateAdvancedOperations();6}78// 1. List操作9void demonstrateListOperations() {10 print('=== List操作 ===');11 12 // 创建List13 List<String> fruits = ['apple', 'banana', 'orange'];14 List<int> numbers = List.generate(5, (index) => index * 2);15 16 print('Fruits: $fruits');17 print('Numbers: $numbers');18 19 // 添加元素20 fruits.add('grape');21 fruits.addAll(['kiwi', 'mango']);22 fruits.insert(1, 'strawberry');23 24 // 删除元素25 fruits.remove('banana');26 fruits.removeAt(0);27 fruits.removeWhere((fruit) => fruit.startsWith('k'));28 29 // 查找元素30 bool hasApple = fruits.contains('apple');31 int appleIndex = fruits.indexOf('apple');32 String? firstLongName = fruits.firstWhere(33 (fruit) => fruit.length > 6,34 orElse: () => 'Not found',35 );36 37 print('Has apple: $hasApple');38 print('Apple index: $appleIndex');39 print('First long name: $firstLongName');40 41 // 转换操作42 List<String> upperFruits = fruits.map((fruit) => fruit.toUpperCase()).toList();43 List<String> longFruits = fruits.where((fruit) => fruit.length > 5).toList();44 45 print('Upper fruits: $upperFruits');46 print('Long fruits: $longFruits');47 48 // 排序49 List<String> sortedFruits = [...fruits]..sort();50 List<String> sortedByLength = [...fruits]51 ..sort((a, b) => a.length.compareTo(b.length));52 53 print('Sorted fruits: $sortedFruits');54 print('Sorted by length: $sortedByLength');55 56 // 聚合操作57 int totalLength = fruits.fold(0, (sum, fruit) => sum + fruit.length);58 String joined = fruits.join(', ');59 60 print('Total length: $totalLength');61 print('Joined: $joined');62}6364// 2. Set操作65void demonstrateSetOperations() {66 print('\n=== Set操作 ===');67 68 Set<int> set1 = {1, 2, 3, 4, 5};69 Set<int> set2 = {4, 5, 6, 7, 8};70 71 // 集合运算72 Set<int> union = set1.union(set2); // 并集73 Set<int> intersection = set1.intersection(set2); // 交集74 Set<int> difference = set1.difference(set2); // 差集75 76 print('Set1: $set1');77 print('Set2: $set2');78 print('Union: $union');79 print('Intersection: $intersection');80 print('Difference: $difference');81 82 // 去重83 List<int> duplicates = [1, 2, 2, 3, 3, 3, 4, 4, 5];84 Set<int> unique = duplicates.toSet();85 List<int> uniqueList = unique.toList();86 87 print('Duplicates: $duplicates');88 print('Unique: $uniqueList');89}9091// 3. Map操作92void demonstrateMapOperations() {93 print('\n=== Map操作 ===');94 95 Map<String, int> scores = {96 'Alice': 95,97 'Bob': 87,98 'Charlie': 92,99 };100 101 // 添加和更新102 scores['David'] = 88;103 scores.putIfAbsent('Eve', () => 90);104 scores.update('Alice', (value) => value + 5);105 scores.updateAll((key, value) => value + 2);106 107 print('Scores: $scores');108 109 // 查询操作110 bool hasAlice = scores.containsKey('Alice');111 bool hasScore100 = scores.containsValue(100);112 int? aliceScore = scores['Alice'];113 114 print('Has Alice: $hasAlice');115 print('Has score 100: $hasScore100');116 print('Alice score: $aliceScore');117 118 // 遍历119 print('All entries:');120 scores.forEach((name, score) {121 print(' $name: $score');122 });123 124 // 转换125 List<String> names = scores.keys.toList();126 List<int> allScores = scores.values.toList();127 Map<String, String> grades = scores.map(128 (name, score) => MapEntry(name, score >= 90 ? 'A' : 'B'),129 );130 131 print('Names: $names');132 print('All scores: $allScores');133 print('Grades: $grades');134 135 // 过滤136 Map<String, int> highScores = Map.fromEntries(137 scores.entries.where((entry) => entry.value >= 90),138 );139 140 print('High scores: $highScores');141}142143// 4. 高级集合操作144void demonstrateAdvancedOperations() {145 print('\n=== 高级集合操作 ===');146 147 List<Map<String, dynamic>> students = [148 {'name': 'Alice', 'age': 20, 'grade': 'A', 'subjects': ['Math', 'Physics']},149 {'name': 'Bob', 'age': 19, 'grade': 'B', 'subjects': ['Chemistry', 'Biology']},150 {'name': 'Charlie', 'age': 21, 'grade': 'A', 'subjects': ['Math', 'Chemistry']},151 {'name': 'David', 'age': 20, 'grade': 'C', 'subjects': ['Physics', 'Biology']},152 ];153 154 // 复杂过滤155 List<Map<String, dynamic>> youngAStudents = students156 .where((student) => student['age'] < 21 && student['grade'] == 'A')157 .toList();158 159 print('Young A students: $youngAStudents');160 161 // 分组162 Map<String, List<Map<String, dynamic>>> studentsByGrade = {};163 for (var student in students) {164 String grade = student['grade'];165 studentsByGrade.putIfAbsent(grade, () => []).add(student);166 }167 168 print('Students by grade: $studentsByGrade');169 170 // 扁平化171 List<String> allSubjects = students172 .expand((student) => student['subjects'] as List<String>)173 .toSet()174 .toList();175 176 print('All subjects: $allSubjects');177 178 // 聚合统计179 double averageAge = students180 .map((student) => student['age'] as int)181 .reduce((a, b) => a + b) / students.length;182 183 Map<String, int> gradeCount = {};184 for (var student in students) {185 String grade = student['grade'];186 gradeCount[grade] = (gradeCount[grade] ?? 0) + 1;187 }188 189 print('Average age: ${averageAge.toStringAsFixed(1)}');190 print('Grade count: $gradeCount');191 192 // 链式操作193 List<String> result = students194 .where((student) => student['age'] >= 20)195 .map((student) => student['name'] as String)196 .where((name) => name.startsWith('A') || name.startsWith('C'))197 .map((name) => name.toUpperCase())198 .toList()199 ..sort();200 201 print('Processed names: $result');202}2. Widget与布局系统
2.1 Widget核心概念
Flutter中一切皆Widget,Widget是Flutter应用的基本构建块。
- StatelessWidget
- StatefulWidget
- 布局组件
StatelessWidget详解
StatelessWidget示例
dart
1import 'package:flutter/material.dart';23// 1. 基础StatelessWidget4class WelcomeScreen extends StatelessWidget {5 final String title;6 final String subtitle;7 final VoidCallback? onPressed;8 9 const WelcomeScreen({10 Key? key,11 required this.title,12 this.subtitle = '',13 this.onPressed,14 }) : super(key: key);15 16 @override17 Widget build(BuildContext context) {18 return Scaffold(19 appBar: AppBar(20 title: Text(title),21 backgroundColor: Theme.of(context).primaryColor,22 ),23 body: Center(24 child: Column(25 mainAxisAlignment: MainAxisAlignment.center,26 children: [27 Icon(28 Icons.flutter_dash,29 size: 100,30 color: Theme.of(context).primaryColor,31 ),32 SizedBox(height: 20),33 Text(34 title,35 style: Theme.of(context).textTheme.headlineMedium,36 textAlign: TextAlign.center,37 ),38 if (subtitle.isNotEmpty) ...[39 SizedBox(height: 10),40 Text(41 subtitle,42 style: Theme.of(context).textTheme.bodyLarge,43 textAlign: TextAlign.center,44 ),45 ],46 SizedBox(height: 30),47 if (onPressed != null)48 ElevatedButton(49 onPressed: onPressed,50 child: Text('开始使用'),51 ),52 ],53 ),54 ),55 );56 }57}5859// 2. 自定义卡片组件60class CustomCard extends StatelessWidget {61 final String title;62 final String description;63 final String? imageUrl;64 final List<String> tags;65 final VoidCallback? onTap;66 67 const CustomCard({68 Key? key,69 required this.title,70 required this.description,71 this.imageUrl,72 this.tags = const [],73 this.onTap,74 }) : super(key: key);75 76 @override77 Widget build(BuildContext context) {78 return Card(79 elevation: 4,80 margin: EdgeInsets.all(8),81 child: InkWell(82 onTap: onTap,83 borderRadius: BorderRadius.circular(8),84 child: Column(85 crossAxisAlignment: CrossAxisAlignment.start,86 children: [87 // 图片部分88 if (imageUrl != null)89 ClipRRect(90 borderRadius: BorderRadius.vertical(top: Radius.circular(8)),91 child: Image.network(92 imageUrl!,93 height: 200,94 width: double.infinity,95 fit: BoxFit.cover,96 errorBuilder: (context, error, stackTrace) {97 return Container(98 height: 200,99 color: Colors.grey[300],100 child: Icon(Icons.error, size: 50),101 );102 },103 ),104 ),105 106 // 内容部分107 Padding(108 padding: EdgeInsets.all(16),109 child: Column(110 crossAxisAlignment: CrossAxisAlignment.start,111 children: [112 Text(113 title,114 style: Theme.of(context).textTheme.titleLarge,115 maxLines: 2,116 overflow: TextOverflow.ellipsis,117 ),118 SizedBox(height: 8),119 Text(120 description,121 style: Theme.of(context).textTheme.bodyMedium,122 maxLines: 3,123 overflow: TextOverflow.ellipsis,124 ),125 126 // 标签部分127 if (tags.isNotEmpty) ...[128 SizedBox(height: 12),129 Wrap(130 spacing: 8,131 runSpacing: 4,132 children: tags.map((tag) => Chip(133 label: Text(134 tag,135 style: TextStyle(fontSize: 12),136 ),137 backgroundColor: Theme.of(context).primaryColor.withOpacity(0.1),138 )).toList(),139 ),140 ],141 ],142 ),143 ),144 ],145 ),146 ),147 );148 }149}150151// 3. 响应式布局组件152class ResponsiveLayout extends StatelessWidget {153 final Widget mobile;154 final Widget? tablet;155 final Widget? desktop;156 157 const ResponsiveLayout({158 Key? key,159 required this.mobile,160 this.tablet,161 this.desktop,162 }) : super(key: key);163 164 @override165 Widget build(BuildContext context) {166 return LayoutBuilder(167 builder: (context, constraints) {168 if (constraints.maxWidth >= 1200) {169 return desktop ?? tablet ?? mobile;170 } else if (constraints.maxWidth >= 800) {171 return tablet ?? mobile;172 } else {173 return mobile;174 }175 },176 );177 }178}179180// 4. 加载状态组件181class LoadingWidget extends StatelessWidget {182 final String? message;183 final double size;184 final Color? color;185 186 const LoadingWidget({187 Key? key,188 this.message,189 this.size = 50.0,190 this.color,191 }) : super(key: key);192 193 @override194 Widget build(BuildContext context) {195 return Center(196 child: Column(197 mainAxisSize: MainAxisSize.min,198 children: [199 SizedBox(200 width: size,201 height: size,202 child: CircularProgressIndicator(203 valueColor: AlwaysStoppedAnimation<Color>(204 color ?? Theme.of(context).primaryColor,205 ),206 ),207 ),208 if (message != null) ...[209 SizedBox(height: 16),210 Text(211 message!,212 style: Theme.of(context).textTheme.bodyMedium,213 textAlign: TextAlign.center,214 ),215 ],216 ],217 ),218 );219 }220}221222// 5. 错误状态组件223class ErrorWidget extends StatelessWidget {224 final String message;225 final VoidCallback? onRetry;226 final IconData icon;227 228 const ErrorWidget({229 Key? key,230 required this.message,231 this.onRetry,232 this.icon = Icons.error_outline,233 }) : super(key: key);234 235 @override236 Widget build(BuildContext context) {237 return Center(238 child: Padding(239 padding: EdgeInsets.all(32),240 child: Column(241 mainAxisSize: MainAxisSize.min,242 children: [243 Icon(244 icon,245 size: 80,246 color: Colors.red[300],247 ),248 SizedBox(height: 16),249 Text(250 '出错了',251 style: Theme.of(context).textTheme.headlineSmall,252 ),253 SizedBox(height: 8),254 Text(255 message,256 style: Theme.of(context).textTheme.bodyMedium,257 textAlign: TextAlign.center,258 ),259 if (onRetry != null) ...[260 SizedBox(height: 24),261 ElevatedButton.icon(262 onPressed: onRetry,263 icon: Icon(Icons.refresh),264 label: Text('重试'),265 ),266 ],267 ],268 ),269 ),270 );271 }272}273274// 使用示例275class ExampleApp extends StatelessWidget {276 @override277 Widget build(BuildContext context) {278 return MaterialApp(279 title: 'StatelessWidget示例',280 theme: ThemeData(281 primarySwatch: Colors.blue,282 visualDensity: VisualDensity.adaptivePlatformDensity,283 ),284 home: WelcomeScreen(285 title: 'Flutter开发',286 subtitle: '构建美丽的跨平台应用',287 onPressed: () {288 // 导航到下一个页面289 },290 ),291 );292 }293}StatefulWidget详解
StatefulWidget示例
dart
1import 'package:flutter/material.dart';2import 'dart:async';34// 1. 基础StatefulWidget - 计数器5class CounterWidget extends StatefulWidget {6 final int initialValue;7 final int step;8 final int? maxValue;9 final ValueChanged<int>? onChanged;10 11 const CounterWidget({12 Key? key,13 this.initialValue = 0,14 this.step = 1,15 this.maxValue,16 this.onChanged,17 }) : super(key: key);18 19 @override20 State<CounterWidget> createState() => _CounterWidgetState();21}2223class _CounterWidgetState extends State<CounterWidget> {24 late int _counter;25 26 @override27 void initState() {28 super.initState();29 _counter = widget.initialValue;30 }31 32 @override33 void didUpdateWidget(CounterWidget oldWidget) {34 super.didUpdateWidget(oldWidget);35 // 当widget属性改变时的处理36 if (oldWidget.initialValue != widget.initialValue) {37 _counter = widget.initialValue;38 }39 }40 41 void _increment() {42 setState(() {43 if (widget.maxValue == null || _counter < widget.maxValue!) {44 _counter += widget.step;45 widget.onChanged?.call(_counter);46 }47 });48 }49 50 void _decrement() {51 setState(() {52 _counter -= widget.step;53 widget.onChanged?.call(_counter);54 });55 }56 57 void _reset() {58 setState(() {59 _counter = widget.initialValue;60 widget.onChanged?.call(_counter);61 });62 }63 64 @override65 Widget build(BuildContext context) {66 return Card(67 child: Padding(68 padding: EdgeInsets.all(16),69 child: Column(70 mainAxisSize: MainAxisSize.min,71 children: [72 Text(73 '计数器',74 style: Theme.of(context).textTheme.titleLarge,75 ),76 SizedBox(height: 16),77 Text(78 '$_counter',79 style: Theme.of(context).textTheme.displayMedium,80 ),81 SizedBox(height: 16),82 Row(83 mainAxisAlignment: MainAxisAlignment.spaceEvenly,84 children: [85 ElevatedButton(86 onPressed: _decrement,87 child: Icon(Icons.remove),88 ),89 ElevatedButton(90 onPressed: _reset,91 child: Icon(Icons.refresh),92 ),93 ElevatedButton(94 onPressed: (widget.maxValue == null || _counter < widget.maxValue!)95 ? _increment96 : null,97 child: Icon(Icons.add),98 ),99 ],100 ),101 ],102 ),103 ),104 );105 }106}107108// 2. 表单组件109class UserForm extends StatefulWidget {110 final Function(Map<String, dynamic>) onSubmit;111 112 const UserForm({Key? key, required this.onSubmit}) : super(key: key);113 114 @override115 State<UserForm> createState() => _UserFormState();116}117118class _UserFormState extends State<UserForm> {119 final _formKey = GlobalKey<FormState>();120 final _nameController = TextEditingController();121 final _emailController = TextEditingController();122 final _phoneController = TextEditingController();123 124 String _selectedGender = '男';125 bool _agreeToTerms = false;126 List<String> _interests = [];127 128 final List<String> _availableInterests = [129 '编程', '设计', '音乐', '运动', '阅读', '旅行'130 ];131 132 @override133 void dispose() {134 _nameController.dispose();135 _emailController.dispose();136 _phoneController.dispose();137 super.dispose();138 }139 140 String? _validateEmail(String? value) {141 if (value == null || value.isEmpty) {142 return '请输入邮箱';143 }144 if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {145 return '请输入有效的邮箱地址';146 }147 return null;148 }149 150 String? _validatePhone(String? value) {151 if (value == null || value.isEmpty) {152 return '请输入手机号';153 }154 if (!RegExp(r'^1[3-9]\d{9}$').hasMatch(value)) {155 return '请输入有效的手机号';156 }157 return null;158 }159 160 void _submitForm() {161 if (_formKey.currentState!.validate() && _agreeToTerms) {162 final formData = {163 'name': _nameController.text,164 'email': _emailController.text,165 'phone': _phoneController.text,166 'gender': _selectedGender,167 'interests': _interests,168 };169 widget.onSubmit(formData);170 } else if (!_agreeToTerms) {171 ScaffoldMessenger.of(context).showSnackBar(172 SnackBar(content: Text('请同意用户协议')),173 );174 }175 }176 177 @override178 Widget build(BuildContext context) {179 return Form(180 key: _formKey,181 child: Column(182 crossAxisAlignment: CrossAxisAlignment.start,183 children: [184 // 姓名输入185 TextFormField(186 controller: _nameController,187 decoration: InputDecoration(188 labelText: '姓名',189 prefixIcon: Icon(Icons.person),190 border: OutlineInputBorder(),191 ),192 validator: (value) {193 if (value == null || value.isEmpty) {194 return '请输入姓名';195 }196 return null;197 },198 ),199 SizedBox(height: 16),200 201 // 邮箱输入202 TextFormField(203 controller: _emailController,204 decoration: InputDecoration(205 labelText: '邮箱',206 prefixIcon: Icon(Icons.email),207 border: OutlineInputBorder(),208 ),209 keyboardType: TextInputType.emailAddress,210 validator: _validateEmail,211 ),212 SizedBox(height: 16),213 214 // 手机号输入215 TextFormField(216 controller: _phoneController,217 decoration: InputDecoration(218 labelText: '手机号',219 prefixIcon: Icon(Icons.phone),220 border: OutlineInputBorder(),221 ),222 keyboardType: TextInputType.phone,223 validator: _validatePhone,224 ),225 SizedBox(height: 16),226 227 // 性别选择228 Text('性别', style: Theme.of(context).textTheme.titleMedium),229 Row(230 children: [231 Radio<String>(232 value: '男',233 groupValue: _selectedGender,234 onChanged: (value) {235 setState(() {236 _selectedGender = value!;237 });238 },239 ),240 Text('男'),241 Radio<String>(242 value: '女',243 groupValue: _selectedGender,244 onChanged: (value) {245 setState(() {246 _selectedGender = value!;247 });248 },249 ),250 Text('女'),251 ],252 ),253 SizedBox(height: 16),254 255 // 兴趣选择256 Text('兴趣爱好', style: Theme.of(context).textTheme.titleMedium),257 Wrap(258 spacing: 8,259 children: _availableInterests.map((interest) {260 return FilterChip(261 label: Text(interest),262 selected: _interests.contains(interest),263 onSelected: (selected) {264 setState(() {265 if (selected) {266 _interests.add(interest);267 } else {268 _interests.remove(interest);269 }270 });271 },272 );273 }).toList(),274 ),275 SizedBox(height: 16),276 277 // 协议同意278 CheckboxListTile(279 title: Text('我同意用户协议和隐私政策'),280 value: _agreeToTerms,281 onChanged: (value) {282 setState(() {283 _agreeToTerms = value!;284 });285 },286 controlAffinity: ListTileControlAffinity.leading,287 ),288 SizedBox(height: 24),289 290 // 提交按钮291 SizedBox(292 width: double.infinity,293 child: ElevatedButton(294 onPressed: _submitForm,295 child: Text('提交'),296 ),297 ),298 ],299 ),300 );301 }302}303304// 3. 定时器组件305class TimerWidget extends StatefulWidget {306 final int initialSeconds;307 final VoidCallback? onFinished;308 309 const TimerWidget({310 Key? key,311 this.initialSeconds = 60,312 this.onFinished,313 }) : super(key: key);314 315 @override316 State<TimerWidget> createState() => _TimerWidgetState();317}318319class _TimerWidgetState extends State<TimerWidget> {320 late int _remainingSeconds;321 Timer? _timer;322 bool _isRunning = false;323 324 @override325 void initState() {326 super.initState();327 _remainingSeconds = widget.initialSeconds;328 }329 330 @override331 void dispose() {332 _timer?.cancel();333 super.dispose();334 }335 336 void _startTimer() {337 setState(() {338 _isRunning = true;339 });340 341 _timer = Timer.periodic(Duration(seconds: 1), (timer) {342 setState(() {343 if (_remainingSeconds > 0) {344 _remainingSeconds--;345 } else {346 _isRunning = false;347 timer.cancel();348 widget.onFinished?.call();349 }350 });351 });352 }353 354 void _pauseTimer() {355 setState(() {356 _isRunning = false;357 });358 _timer?.cancel();359 }360 361 void _resetTimer() {362 setState(() {363 _isRunning = false;364 _remainingSeconds = widget.initialSeconds;365 });366 _timer?.cancel();367 }368 369 String _formatTime(int seconds) {370 int minutes = seconds ~/ 60;371 int remainingSeconds = seconds % 60;372 return '${minutes.toString().padLeft(2, '0')}:${remainingSeconds.toString().padLeft(2, '0')}';373 }374 375 @override376 Widget build(BuildContext context) {377 return Card(378 child: Padding(379 padding: EdgeInsets.all(16),380 child: Column(381 mainAxisSize: MainAxisSize.min,382 children: [383 Text(384 '倒计时',385 style: Theme.of(context).textTheme.titleLarge,386 ),387 SizedBox(height: 16),388 Text(389 _formatTime(_remainingSeconds),390 style: Theme.of(context).textTheme.displayLarge?.copyWith(391 color: _remainingSeconds <= 10 ? Colors.red : null,392 ),393 ),394 SizedBox(height: 16),395 Row(396 mainAxisAlignment: MainAxisAlignment.spaceEvenly,397 children: [398 ElevatedButton(399 onPressed: _isRunning ? _pauseTimer : _startTimer,400 child: Text(_isRunning ? '暂停' : '开始'),401 ),402 ElevatedButton(403 onPressed: _resetTimer,404 child: Text('重置'),405 ),406 ],407 ),408 ],409 ),410 ),411 );412 }413}414415// 4. 生命周期演示组件416class LifecycleDemo extends StatefulWidget {417 @override418 State<LifecycleDemo> createState() => _LifecycleDemoState();419}420421class _LifecycleDemoState extends State<LifecycleDemo>422 with WidgetsBindingObserver {423 List<String> _lifecycleEvents = [];424 425 void _addEvent(String event) {426 setState(() {427 _lifecycleEvents.add('${DateTime.now().toString().substring(11, 19)}: $event');428 });429 print('Lifecycle: $event');430 }431 432 @override433 void initState() {434 super.initState();435 _addEvent('initState');436 WidgetsBinding.instance.addObserver(this);437 }438 439 @override440 void didChangeDependencies() {441 super.didChangeDependencies();442 _addEvent('didChangeDependencies');443 }444 445 @override446 void didUpdateWidget(LifecycleDemo oldWidget) {447 super.didUpdateWidget(oldWidget);448 _addEvent('didUpdateWidget');449 }450 451 @override452 void deactivate() {453 _addEvent('deactivate');454 super.deactivate();455 }456 457 @override458 void dispose() {459 _addEvent('dispose');460 WidgetsBinding.instance.removeObserver(this);461 super.dispose();462 }463 464 @override465 void didChangeAppLifecycleState(AppLifecycleState state) {466 _addEvent('App lifecycle: ${state.toString()}');467 }468 469 @override470 Widget build(BuildContext context) {471 _addEvent('build');472 473 return Scaffold(474 appBar: AppBar(title: Text('生命周期演示')),475 body: Column(476 children: [477 Padding(478 padding: EdgeInsets.all(16),479 child: Text(480 '生命周期事件:',481 style: Theme.of(context).textTheme.titleMedium,482 ),483 ),484 Expanded(485 child: ListView.builder(486 itemCount: _lifecycleEvents.length,487 itemBuilder: (context, index) {488 return ListTile(489 title: Text(_lifecycleEvents[index]),490 dense: true,491 );492 },493 ),494 ),495 ],496 ),497 floatingActionButton: FloatingActionButton(498 onPressed: () {499 setState(() {500 // 触发rebuild501 });502 },503 child: Icon(Icons.refresh),504 ),505 );506 }507}Flutter布局组件
Flutter布局组件详解
dart
1import 'package:flutter/material.dart';23class LayoutExamples extends StatelessWidget {4 @override5 Widget build(BuildContext context) {6 return Scaffold(7 appBar: AppBar(title: Text('布局组件示例')),8 body: SingleChildScrollView(9 padding: EdgeInsets.all(16),10 child: Column(11 crossAxisAlignment: CrossAxisAlignment.start,12 children: [13 _buildSectionTitle('Container布局'),14 _buildContainerExample(),15 SizedBox(height: 24),16 17 _buildSectionTitle('Row和Column布局'),18 _buildRowColumnExample(),19 SizedBox(height: 24),20 21 _buildSectionTitle('Stack布局'),22 _buildStackExample(),23 SizedBox(height: 24),24 25 _buildSectionTitle('Flex布局'),26 _buildFlexExample(),27 SizedBox(height: 24),28 29 _buildSectionTitle('Wrap布局'),30 _buildWrapExample(),31 SizedBox(height: 24),32 33 _buildSectionTitle('GridView布局'),34 _buildGridExample(),35 ],36 ),37 ),38 );39 }40 41 Widget _buildSectionTitle(String title) {42 return Padding(43 padding: EdgeInsets.only(bottom: 8),44 child: Text(45 title,46 style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),47 ),48 );49 }50 51 // 1. Container布局示例52 Widget _buildContainerExample() {53 return Row(54 children: [55 // 基础Container56 Container(57 width: 80,58 height: 80,59 color: Colors.blue,60 child: Center(61 child: Text('基础', style: TextStyle(color: Colors.white)),62 ),63 ),64 SizedBox(width: 8),65 66 // 带装饰的Container67 Container(68 width: 80,69 height: 80,70 decoration: BoxDecoration(71 gradient: LinearGradient(72 colors: [Colors.purple, Colors.pink],73 ),74 borderRadius: BorderRadius.circular(12),75 boxShadow: [76 BoxShadow(77 color: Colors.grey.withOpacity(0.5),78 spreadRadius: 2,79 blurRadius: 5,80 offset: Offset(0, 3),81 ),82 ],83 ),84 child: Center(85 child: Text('装饰', style: TextStyle(color: Colors.white)),86 ),87 ),88 SizedBox(width: 8),89 90 // 带边框的Container91 Container(92 width: 80,93 height: 80,94 decoration: BoxDecoration(95 color: Colors.white,96 border: Border.all(color: Colors.orange, width: 3),97 borderRadius: BorderRadius.circular(8),98 ),99 child: Center(100 child: Text('边框', style: TextStyle(color: Colors.orange)),101 ),102 ),103 ],104 );105 }106 107 // 2. Row和Column布局示例108 Widget _buildRowColumnExample() {109 return Column(110 children: [111 // Row示例112 Container(113 height: 60,114 decoration: BoxDecoration(115 color: Colors.grey[100],116 borderRadius: BorderRadius.circular(8),117 ),118 child: Row(119 mainAxisAlignment: MainAxisAlignment.spaceEvenly,120 children: [121 Icon(Icons.home, color: Colors.blue),122 Icon(Icons.search, color: Colors.green),123 Icon(Icons.favorite, color: Colors.red),124 Icon(Icons.person, color: Colors.purple),125 ],126 ),127 ),128 SizedBox(height: 16),129 130 // Column示例131 Row(132 children: [133 Expanded(134 child: Container(135 height: 120,136 decoration: BoxDecoration(137 color: Colors.blue[50],138 borderRadius: BorderRadius.circular(8),139 ),140 child: Column(141 mainAxisAlignment: MainAxisAlignment.spaceEvenly,142 children: [143 Icon(Icons.cloud, color: Colors.blue),144 Text('云存储'),145 Text('100GB', style: TextStyle(fontSize: 12)),146 ],147 ),148 ),149 ),150 SizedBox(width: 8),151 Expanded(152 child: Container(153 height: 120,154 decoration: BoxDecoration(155 color: Colors.green[50],156 borderRadius: BorderRadius.circular(8),157 ),158 child: Column(159 mainAxisAlignment: MainAxisAlignment.spaceEvenly,160 children: [161 Icon(Icons.security, color: Colors.green),162 Text('安全'),163 Text('已保护', style: TextStyle(fontSize: 12)),164 ],165 ),166 ),167 ),168 ],169 ),170 ],171 );172 }173 174 // 3. Stack布局示例175 Widget _buildStackExample() {176 return Container(177 height: 150,178 child: Stack(179 children: [180 // 背景181 Container(182 width: double.infinity,183 height: 150,184 decoration: BoxDecoration(185 gradient: LinearGradient(186 colors: [Colors.blue, Colors.purple],187 ),188 borderRadius: BorderRadius.circular(12),189 ),190 ),191 192 // 左上角标签193 Positioned(194 top: 8,195 left: 8,196 child: Container(197 padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),198 decoration: BoxDecoration(199 color: Colors.red,200 borderRadius: BorderRadius.circular(12),201 ),202 child: Text(203 'NEW',204 style: TextStyle(color: Colors.white, fontSize: 12),205 ),206 ),207 ),208 209 // 中心内容210 Center(211 child: Column(212 mainAxisSize: MainAxisSize.min,213 children: [214 Icon(Icons.star, color: Colors.white, size: 40),215 Text(216 'Stack布局',217 style: TextStyle(color: Colors.white, fontSize: 18),218 ),219 ],220 ),221 ),222 223 // 右下角按钮224 Positioned(225 bottom: 8,226 right: 8,227 child: FloatingActionButton(228 mini: true,229 onPressed: () {},230 child: Icon(Icons.add),231 ),232 ),233 ],234 ),235 );236 }237 238 // 4. Flex布局示例239 Widget _buildFlexExample() {240 return Column(241 children: [242 // Flexible示例243 Container(244 height: 60,245 child: Row(246 children: [247 Container(248 width: 60,249 color: Colors.red,250 child: Center(child: Text('固定')),251 ),252 Flexible(253 flex: 1,254 child: Container(255 color: Colors.green,256 child: Center(child: Text('Flex 1')),257 ),258 ),259 Flexible(260 flex: 2,261 child: Container(262 color: Colors.blue,263 child: Center(child: Text('Flex 2')),264 ),265 ),266 ],267 ),268 ),269 SizedBox(height: 8),270 271 // Expanded示例272 Container(273 height: 60,274 child: Row(275 children: [276 Expanded(277 flex: 1,278 child: Container(279 color: Colors.orange,280 child: Center(child: Text('Expanded 1')),281 ),282 ),283 Expanded(284 flex: 3,285 child: Container(286 color: Colors.purple,287 child: Center(child: Text('Expanded 3')),288 ),289 ),290 ],291 ),292 ),293 ],294 );295 }296 297 // 5. Wrap布局示例298 Widget _buildWrapExample() {299 final List<String> tags = [300 'Flutter', 'Dart', '移动开发', '跨平台', 'UI', '组件',301 '状态管理', '导航', '动画', '网络请求', '数据库', '测试'302 ];303 304 return Container(305 padding: EdgeInsets.all(16),306 decoration: BoxDecoration(307 color: Colors.grey[50],308 borderRadius: BorderRadius.circular(8),309 ),310 child: Wrap(311 spacing: 8,312 runSpacing: 8,313 children: tags.map((tag) => Chip(314 label: Text(tag),315 backgroundColor: Colors.blue[100],316 )).toList(),317 ),318 );319 }320 321 // 6. GridView布局示例322 Widget _buildGridExample() {323 return Container(324 height: 200,325 child: GridView.builder(326 gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(327 crossAxisCount: 3,328 crossAxisSpacing: 8,329 mainAxisSpacing: 8,330 childAspectRatio: 1,331 ),332 itemCount: 9,333 itemBuilder: (context, index) {334 final colors = [335 Colors.red, Colors.green, Colors.blue,336 Colors.orange, Colors.purple, Colors.teal,337 Colors.pink, Colors.indigo, Colors.amber,338 ];339 340 return Container(341 decoration: BoxDecoration(342 color: colors[index],343 borderRadius: BorderRadius.circular(8),344 ),345 child: Center(346 child: Text(347 '${index + 1}',348 style: TextStyle(349 color: Colors.white,350 fontSize: 18,351 fontWeight: FontWeight.bold,352 ),353 ),354 ),355 );356 },357 ),358 );359 }360}361362// 复杂布局示例 - 仿微信聊天界面363class ChatLayoutExample extends StatelessWidget {364 final List<ChatMessage> messages = [365 ChatMessage(text: '你好!', isMe: false, time: '10:30'),366 ChatMessage(text: '嗨,最近怎么样?', isMe: true, time: '10:31'),367 ChatMessage(text: '还不错,在学习Flutter呢', isMe: false, time: '10:32'),368 ChatMessage(text: '哇,Flutter很棒的!我也在学', isMe: true, time: '10:33'),369 ChatMessage(text: '一起加油!💪', isMe: false, time: '10:34'),370 ];371 372 @override373 Widget build(BuildContext context) {374 return Scaffold(375 appBar: AppBar(376 title: Text('聊天界面'),377 backgroundColor: Colors.green,378 ),379 body: Column(380 children: [381 Expanded(382 child: ListView.builder(383 padding: EdgeInsets.all(16),384 itemCount: messages.length,385 itemBuilder: (context, index) {386 return _buildMessageBubble(messages[index]);387 },388 ),389 ),390 _buildInputArea(),391 ],392 ),393 );394 }395 396 Widget _buildMessageBubble(ChatMessage message) {397 return Padding(398 padding: EdgeInsets.only(bottom: 16),399 child: Row(400 mainAxisAlignment: message.isMe 401 ? MainAxisAlignment.end 402 : MainAxisAlignment.start,403 crossAxisAlignment: CrossAxisAlignment.start,404 children: [405 if (!message.isMe) ...[406 CircleAvatar(407 radius: 20,408 backgroundColor: Colors.grey[300],409 child: Icon(Icons.person, color: Colors.grey[600]),410 ),411 SizedBox(width: 8),412 ],413 414 Flexible(415 child: Column(416 crossAxisAlignment: message.isMe 417 ? CrossAxisAlignment.end 418 : CrossAxisAlignment.start,419 children: [420 Container(421 padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10),422 decoration: BoxDecoration(423 color: message.isMe ? Colors.green : Colors.grey[200],424 borderRadius: BorderRadius.circular(18),425 ),426 child: Text(427 message.text,428 style: TextStyle(429 color: message.isMe ? Colors.white : Colors.black87,430 ),431 ),432 ),433 SizedBox(height: 4),434 Text(435 message.time,436 style: TextStyle(437 fontSize: 12,438 color: Colors.grey[600],439 ),440 ),441 ],442 ),443 ),444 445 if (message.isMe) ...[446 SizedBox(width: 8),447 CircleAvatar(448 radius: 20,449 backgroundColor: Colors.green[300],450 child: Icon(Icons.person, color: Colors.white),451 ),452 ],453 ],454 ),455 );456 }457 458 Widget _buildInputArea() {459 return Container(460 padding: EdgeInsets.all(16),461 decoration: BoxDecoration(462 color: Colors.white,463 border: Border(top: BorderSide(color: Colors.grey[300]!)),464 ),465 child: Row(466 children: [467 Expanded(468 child: TextField(469 decoration: InputDecoration(470 hintText: '输入消息...',471 border: OutlineInputBorder(472 borderRadius: BorderRadius.circular(25),473 ),474 contentPadding: EdgeInsets.symmetric(475 horizontal: 16,476 vertical: 8,477 ),478 ),479 ),480 ),481 SizedBox(width: 8),482 FloatingActionButton(483 mini: true,484 onPressed: () {},485 child: Icon(Icons.send),486 ),487 ],488 ),489 );490 }491}492493class ChatMessage {494 final String text;495 final bool isMe;496 final String time;497 498 ChatMessage({499 required this.text,500 required this.isMe,501 required this.time,502 });503}
评论