Flutter学习笔记

中文开发者网站 中文开发者社区 官网 网页版预览Flutter

  • 完整的Demo流程 ✅
  • 常用的组件有哪些 ✅
  • 页面导航 ✅
  • 包依赖管理 ✅
  • 资源引用 ✅
  • 数据持久化 ✅
  • 网络请求 ✅
  • json解析 ✅
  • 原生调用的Flutter模块 ✅
  • 开发Flutter包和插件 ✅

完整的Demo流程

  • ChangeNotifier 设置应用状态。
  • ChangeNotifierProvider 应用状态提供给整个应用,任何组件都可以获取状态。
  • SafeArea确保显示不会被凹槽的状态栏挡住。
  • [WordPair? pair]加[]表示是可选的参数。

常用组件

  • MaterialApp 主题

  • Scaffold 脚手架

  • NavigationRail 导航组件

  • LayoutBuilder 布局伸缩通知变化

  • SizedBox、Spacer空白占用

  • Padding 间距

  • Text文本

  • Icon图标

  • TextButton.icon 文本+图标 按钮

  • Row和Column 行列列表

  • GridView 网格,ListTile网格项

页面导航

go路由库

//0.安装&依赖
flutter pub add go_router
dependencies: 
    go_router: ^14.3.0
//1.定义路由
final GoRouter _router = GoRouter(
  routes: <RouteBase>[
    GoRoute(
      path: '/',
      builder: (BuildContext context, GoRouterState state) {
        return const HomeScreen();
      },
      routes: <RouteBase>[
        GoRoute(
          path: 'details',
          builder: (BuildContext context, GoRouterState state) {
            return const DetailsScreen();
          },
        ),
      ],
    ),
  ],
);
class MyApp extends StatelessWidget {
  /// Constructs a [MyApp]
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: _router,
    );
  }
}
//2.页面定义
class HomeScreen extends StatelessWidget {...}
class DetailsScreen extends StatelessWidget {...}
//3.路由跳转
child: ElevatedButton(
          onPressed: () => context.go('/'),
          child: const Text('Go back to the Home screen'),
        )

资源引用

//pubspec.yaml 配置文件
assets:  
- assets/images/  
- assets/json/
  • 分辨率 1.0x、2.0x这种方式引用,v3.24.3会提示找不到异常,先忽略。
  • svg加载
flutter pub add flutter_svg
//加载
import 'package:flutter_svg/flutter_svg.dart';
SvgPicture.asset('assets/images/a4.svg',semanticsLabel: "icon",)
  • 字符串国际化
//1.搜索插件 Flutter Intl
//2.添加依赖
dependencies:
    flutter_localizations:
        sdk: flutter
    intl: any
flutter:
  generate: true
//3.根目录新建文件l10n.yaml,arb就是json文件,添加以下内容:
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
//4.运行 flutter run 命令,你将在 ${FLUTTER_PROJECT}/.dart_tool/flutter_gen/gen_l10n 中看到生成的文件。
//5.入口添加
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
MaterialApp(
 localizationsDelegates: [
    AppLocalizations.delegate,  
    GlobalMaterialLocalizations.delegate,  
    GlobalWidgetsLocalizations.delegate,  
    GlobalCupertinoLocalizations.delegate,
 ],
 supportedLocales: [
    const Locale('en', 'US'), // 美国英语
    const Locale('zh', 'CN'), // 中文简体
    //其他Locales
  ],
  locale: const Locale('en', 'US'), //手动指定locale
//6.获取当前语言
Locale myLocale = Localizations.localeOf(context);
//7.引用字符串
Text(AppLocalizations.of(context)!.welcome)

数据持久化

  • key-values
//执行
flutter pub add shared_preferences
//自动添加依赖
shared_preferences: ^2.3.2
//获取String
final prefs = await SharedPreferences.getInstance();  
spDataText = prefs.getString(Constants.SP_NAME) ?? ''; 
//设置String
final prefs = await SharedPreferences.getInstance();  
prefs.setString(Constants.SP_NAME, data);
//移除String
final prefs = await SharedPreferences.getInstance(); 
await prefs.remove(Constants.SP_NAME);
  • sqlite
//执行
flutter pub add sqflite path
//自动添加依赖
sqflite: ^2.4.0  
path: ^1.9.0

网络请求

flutter pub add http
import 'package:http/http.dart' as http;
  • 添加权限
//android
<uses-permission android:name="android.permission.INTERNET" />
//macos/Runner/DebugProfile.entitlements 和 macos/Runner/Release.entitlements 文件添加网络权限
<!-- Required to fetch data from the internet. --> <key>com.apple.security.network.client</key> <true/>
  • FutureBuilder 异步组件显示网络数据
//Album数据类
class Album {
  final int userId;
  final int id;
  final String title;

  Album({required this.userId, required this.id, required this.title});

  factory Album.fromJson(Map<String, dynamic> json) {
    if (json.containsKey('userId') &&
        json.containsKey('id') &&
        json.containsKey('title')) {
      return Album(
        userId: json['userId'] as int,
        id: json['id'] as int,
        title: json['title'] as String,
      );
    } else {
      throw const FormatException('Failed to load album.');
    }
  }
}
//http请求
Future<Album> fetchAlbum() async{
  final response = await http
      .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));

  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}
//显示组件
late Future<Album> futureAlbum = fetchAlbum();
FutureBuilder<Album>(
  future: futureAlbum,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text("title=${snapshot.data!.title},userId=${snapshot.data!.userId}");
    } else if (snapshot.hasError) {
      return Text('${snapshot.error}');
    }

    // By default, show a loading spinner.
    return const CircularProgressIndicator();
  },
)

json解析

  • dart:convert:内置的库,手动序列化/反序列化
class User{
   String name = "";
   int age = 0;

   User();

   User.fromJson(Map<String, dynamic> json)
       : name = json['name'] as String,
          age = json['age'] as int;

   Map<String, dynamic> toJson() => {
      'name': name,
      'age': age,
   };
}
//反序列化
var user = jsonDecode(json) as Map<String, dynamic>;
print("test 名字:${user['name']},年龄:${user['age']}");
//序列化
var userObj = User();  
userObj.name = user['name'];  
userObj.age = user['age'];
String json = jsonEncode(userObj);//是调用toJson方法。
  • dart:json_serializable:自动序列化/反序列化
//依赖
flutter pub add json_annotation dev:build_runner dev:json_serializable
//使用注解
import 'package:json_annotation/json_annotation.dart';  
part 'user.g.dart';  
  
@JsonSerializable()  
class User{  
@JsonKey(name: 'name') //别名  
String name = "";  
int age = 0;  
  
User();  

factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);  
  
Map<String, dynamic> toJson() => _$UserToJson(this);  
}
//如果有dart安装不是flutter自带,使用brew删除
brew uninstall dart
//生成代码User.g.dart
flutter pub run build_runner build --delete-conflicting-outputs
//User类修改
import 'package:json_annotation/json_annotation.dart';  
part 'User.g.dart';  //还是报错,但是可以运行起来✅

@JsonSerializable(nullable: false)  
class User{  
@JsonKey(name: 'name') //别名  
String name = "";  
int age = 0;  
  
User();  
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);  
Map<String, dynamic> toJson() => _$UserToJson(this);  
  
}

原生调用的Flutter模块

Flutter模块

  • 导出aar包:flutter build aar

    • 如果直接导包,需要查看输出的文件夹下:pom文件类似这些依赖也一并导入
      <groupId>io.flutter</groupId>
      <artifactId>flutter_embedding_debug</artifactId>
      <version>1.0.0-36335019a8eab588c3c2ea783c618d90505be233</version>
      <scope>compile</scope>
    
    • 使用本地仓库依赖
    allprojects {
        String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?:
                "https://storage.googleapis.com"
        repositories {
            maven {
                url'../../flutter_module/build/host/outputs/repo'
            }
            maven {
                url "$storageUrl/download.flutter.io"
            }
            google()
            mavenCentral()
        }
    }
    //添加依赖项
    debugImplementation 'dev.flutter.example.flutter_module:flutter_debug:1.0'  
    releaseImplementation 'dev.flutter.example.flutter_module:flutter_release:1.0'
    
  • 配置Flutter模块

  • flutter create -t module flutter_module创建Flutter模块

//在pubspec.yaml下配置为Flutter模块
flutter:
  module:
    androidX: true
    androidPackage: dev.flutter.example.flutter_module //定义android模块下唯一标识
    iosBundleIdentifier: dev.flutter.example.flutterModule //定义ios模块下唯一标识
  • 集成到android或ios
cd flutter_module/
flutter pub get
#android打开项目即可
open -a "Android Studio" ../android_fullscreen
#ios需要pod安装下再打开
cd ../ios_fullscreen
pod install
open IOSFullScreen.xcworkspace

Android

  • FlutterEngine预初始化
  • MethodChannel
  • methodChannel#setMethodCallHandler(..) 处理flutter回调原生
  • methodChannel#invokeMethod(..) 处理原生调用flutter
  • FlutterActivity flutter页面
const val ENGINE_ID = "1"
private lateinit var channel: MethodChannel
var count = 0
/**Application#onCreate()下调用**/
val flutterEngine = FlutterEngine(this)  
flutterEngine  
.dartExecutor  
.executeDartEntrypoint(  
DartExecutor.DartEntrypoint.createDefault()  
)  
  
FlutterEngineCache.getInstance().put(ENGINE_ID, flutterEngine)
channel = MethodChannel(flutterEngine.dartExecutor, "dev.flutter.example/counter")
channel.setMethodCallHandler { call, _ ->
//flutter调用原生时,接收
            when (call.method) {
                "incrementCounter" -> {
                    count++
                    reportCounter()
                }
                "requestCounter" -> {
                    reportCounter()
                }
            }
        }
//...

private fun reportCounter() {
//原生调用flutter
    channel.invokeMethod("reportCounter", count)
}
//跳转到Flutter 页面
val intent = FlutterActivity
        .withCachedEngine(ENGINE_ID)
        .build(this)
    startActivity(intent)
//添加AndroidMainfest.xml声明
   <activity
            android:name="io.flutter.embedding.android.FlutterActivity"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
            android:exported="true"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize" />
  • android源码方式引入
//build.gradle文件
dependencies {
    implementation project(':flutter')
}
//settings.gradle文件,其中evaluate执行指定文件的代码
setBinding(new Binding([gradle: this]))
evaluate(new File(
        settingsDir.parentFile,
        'flutter_module/.android/include_flutter.groovy'
))
include ':flutter_module'
project(':flutter_module').projectDir = new File('../flutter_module')   
  • android aar包方式引入
//执行
flutter build aar
//添加gradle本地依赖库地址
 String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?:
      "https://storage.googleapis.com"
      repositories {
        maven {
            url
            '/Users/xxx/flutter_module/build/hos
            t/outputs/repo'
        }
        maven {
            url "$storageUrl/download.flutter.io"
        }
      }
//添加依赖库
debugImplementation("dev.flutter.example.flutter_module:flutter_debug:1.0")     releaseImplementation("dev.flutter.example.flutter_module:flutter_release:1.0")//
  • ⚠️使用kts时,新建flutter_settings.gradle,再引入
apply { from("flutter_settings.gradle") }
/**遇到这个问题
Caused by: org.gradle.api.InvalidUserCodeException: Build was configured to prefer settings repositories over project repositories but repository ‘maven’ was added by plugin class ‘FlutterPlugin’ Caused by: org.gradle.api.internal.plugins.PluginApplicationException: Failed to apply plugin class ‘FlutterPlugin’.
**/
//修改repositoriesMode
repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
//异常
Caused by: org.gradle.api.internal.plugins.PluginApplicationException: Failed to apply plugin class 'FlutterPlugin'.
//修改repositoriesMode
repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)

Android-Error
  • 使用Compose新建项目导入flutter模块提示错误
Execution failed for task ':app:checkDebugAarMetadata'.
> Could not resolve all files for configuration ':app:debugRuntimeClasspath'.
   > Could not find org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0.
     Searched in the following locations:
       - https://storage.googleapis.com/download.flutter.io/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.0/kotlin-stdlib-jdk8-1.9.0.pom
     Required by:
         project :app
   > Could not find androidx.compose.ui:ui-tooling:.
     Required by:

Flutter

- `MethodChannel`
- `methodChannel#setMethodCallHandler(..)` 处理原生回调flutter
- `methodChannel#invokeMethod<void>(..)`处理flutter调用原生
```dart
void main() {
  //这个调用确保,在MethodChannel创建之前,Flutter绑定关系已经设置好了。
  WidgetsFlutterBinding.ensureInitialized();

  final model = CounterModel();

  runApp(
    ChangeNotifierProvider.value(
      value: model,
      child: const MyApp(),
    ),
  );
}

class CounterModel extends ChangeNotifier {
  CounterModel() {
    _channel.setMethodCallHandler(_handleMessage);
    _channel.invokeMethod<void>('requestCounter');
  }
  final _channel = const MethodChannel('dev.flutter.example/counter');
  int _count = 0;  
  int get count => _count;

  void increment() {
    //Flutter中点击触发,调用原生方法增加count。
    _channel.invokeMethod<void>('incrementCounter');
  }

  Future<dynamic> _handleMessage(MethodCall call) async {
    //处理原生调flutter方法,更新count。
    if (call.method == 'reportCounter') {
      _count = call.arguments as int;
      notifyListeners();
    }
  }
}

iOS

  • FlutterEngine
  • flutterEngine?.run(withEntrypoint: nil)初始化启动Flutter引擎
  • FlutterMethodChannel
  • flutterMethodChannel#setMethodCallHandler(..) 设置flutter回调原生ios
  • flutterMethodChannel?.invokeMethod(..) 设置ios调用flutter
  • FlutterViewControllerflutter页面
  • iOS新建项目出现上下区域黑色,改配置里面Launch Screen File 添加LaunchScreen
//应用入口初始化
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    var flutterEngine : FlutterEngine?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Instantiate Flutter engine
        self.flutterEngine = FlutterEngine(name: "io.flutter", project: nil)
        self.flutterEngine?.run(withEntrypoint: nil)

        return true
    }
}
//UIViewController下
var methodChannel : FlutterMethodChannel?
var count = 0

if let flutterEngine = (UIApplication.shared.delegate as? AppDelegate)?.flutterEngine {
                                                                                       methodChannel = FlutterMethodChannel(name: "dev.flutter.example/counter",binaryMessenger: flutterEngine.binaryMessenger)
                                                                                       methodChannel?.setMethodCallHandler({ [weak self](call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
                                                                                       if let strongSelf = self {
                    switch(call.method) {
                    case "incrementCounter":
                        strongSelf.count += 1
                        strongSelf.counterLabel.text = "Current counter: \(strongSelf.count)"
                        strongSelf.reportCounter()
                    case "requestCounter":
                        strongSelf.reportCounter()
                    default:
                        // Unrecognized method name
                        print("Unrecognized method name: \(call.method)")
                    }
                }
            })
}

func reportCounter() {
    methodChannel?.invokeMethod("reportCounter", arguments: count)
}
@IBAction func buttonWasTapped(_ sender: Any) {
        if let flutterEngine = (UIApplication.shared.delegate as? AppDelegate)?.flutterEngine {
            let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
            self.present(flutterViewController, animated: true, completion: nil)
        }
    }

//pod文件

flutter_application_path = '../flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'IOSFullScreen' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for IOSFullScreen
  install_all_flutter_pods(flutter_application_path)

  target 'IOSFullScreenTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'IOSFullScreenUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end

post_install do |installer|
  flutter_post_install(installer) if defined?(flutter_post_install)
end

iOS-Error
  • 使用SwiftUI 新建项目 导入flutter模块 pod install提示错误
    [!] The platform of the targetiOSAddFlutter(macOS 14.0) is not compatible withFlutterPluginRegistrant (0.0.1), which does not supportmacOS.

开发Flutter包和插件

纯Flutter的自定义包
  • 创建自定义包flutter create --template=package hello
  • 通过path路径形式加载包
dependencies: 
  hello:
    path: ../../packgage-plugin/hello/
  • 通过git形式加载包,(可通过path设置路径,默认是根目录下)
dependencies: 
    packageA:
      git:
        url: git@github.com:flutter/packageA.git
        path: packages/packageA

调用特定平台API的原生插件包

  • flutter create --template=plugin --platforms=android,ios -i objc hello,其中-i指定ios语言,-a指定android语言 ,平台有android,ios,linux,macos,windows
  • ErrorTips:新增--platforms的macos时,运行macos应用提示error: no such module 'native_hello' import native_hello。需要在macos重新运行 pod install重新刷新flutter插件。
  • Futurn.then(),调用异步代码
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容