Flutter介绍
Flutter是谷歌公司开发的一款开源、免费的UI框架,可以让我们快速的在Android和iOS上构建高质量
App。它最大的特点就是跨平台、以及高性能。 目前 Flutter 已经支持 iOS、Android、Web、
Windows、macOS、Linux等。
Flutter基于谷歌的dart语言,如果没有任何Dart语言的基础,不建议直接学习Flutter。建议先学习Dart
语言的基本语法。然后再进入Flutter的学习。
Flutter 官网:https://flflutter.dev/
Flutter Packages官网:https://pub.dev/
需要有一定基础才能看懂此文章
项目准备
下载安装Flutter
官网
https://flutter.cn/docs/get-started/install/windows
下载完成后配置环境变量

加入 flutter\bin
目录的完整路径。
运行
where flutter dart

androad studio环境配置


安装插件
flutter和dart

创建flutter工程
Dart和Flutter对包名有一些规定,包名必须全部是小写,并且只能包含字母、数字和下划线。此外,包名不能以数字开头。
flutter create my_app (这里我叫only_you)
https://developer.android.google.cn/studio/run/managing-avds?hl=zh-cn#emulator

创建login组件
flutter create –template=package login_sdk

导入


导入测试

配置下载组组件
hi_download
flutter create --template=package hi_download
依赖
flutter pub add http
flutter pub add path_provider
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
typedef DownLoadListener = void Function(int total,int received,bool done);
class HiDownload{
int _total = 0, _received = 0;
Future<String> download({required String downLoadUrl,required String fileName,required DownLoadListener listener})
async {
debugPrint('start download');
var uri= Uri.parse(downLoadUrl);
var request=http.Request('GET',uri);
var response= await http.Client().send(request);
_total=response.contentLength??0;
var path=(await getApplicationDocumentsDirectory()).path;
final file=File('$path/$fileName');
final writeFile=file.openSync(mode: FileMode.write);
// 采用stream避免内存占用,提示性能
response.stream.listen((value) {
writeFile.writeFromSync(value);
_received+=value.length;
listener(_total,_received,false);
debugPrint('progress:${_received/_total}');
}).onDone(() async{
await writeFile.close();
listener(_total,_received,true);
debugPrint("-----done-----");
});
return file.absolute.path;
}
}
主项目
yaml
name: only_you
description: A new Flutter project.
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: '>=3.1.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
# 组件化配置
login_sdk:
path: "../login_sdk"
hi_download:
path: '../hi_download'
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true
新建文件目录

my_app/
├── android/
├── ios/
├── lib/
│ ├── main.dart
│ ├── app.dart
│ ├── config/
│ ├── models/
│ ├── views/
│ │ ├── pages/
│ │ ├── widgets/
│ ├── services/
│ ├── utils/
│ ├── routes/
│ └── theme/
├── test/
├── pubspec.yaml
└── README.md
- android/ 和 ios/ 目录包含了特定平台的代码。
- lib/ 目录是我们编写Dart代码的地方。
- main.dart 是应用程序的入口点。
- app.dart 通常包含了应用程序的根Widget,如 MaterialApp 或 CupertinoApp。
- config/ 目录用于存放应用的配置文件,如网络配置、环境配置等。
- models/ 目录包含了应用程序的数据模型。
- views/ 目录包含了应用程序的所有页面和自定义的Widget。
- services/ 目录包含了与后端API交互的服务类。
- utils/ 目录包含了一些实用工具,如网络请求、本地存储等。
- routes/ 目录包含了应用程序的所有路由(页面)。
- theme/ 目录用于存放应用的主题样式。
- test/ 目录用于存放测试代码。
- pubspec.yaml 文件用于管理Flutter应用的依赖项。
- README.md 文件用于描述项目的信息。
进入主项目only_you创建
mkdir lib/config lib/models lib/views lib/views/pages lib/views/widgets lib/services lib/utils lib/routes lib/theme
New-Item -ItemType Directory -Path .\lib\config
New-Item -ItemType Directory -Path .\lib\models
New-Item -ItemType Directory -Path .\lib\views
New-Item -ItemType Directory -Path .\lib\views\pages
New-Item -ItemType Directory -Path .\lib\views\widgets
New-Item -ItemType Directory -Path .\lib\services
New-Item -ItemType Directory -Path .\lib\utils
New-Item -ItemType Directory -Path .\lib\routes
New-Item -ItemType Directory -Path .\lib\theme
创建app.dart文件,这个文件通常用于定义应用的主题和路由。在lib/目录下创建app.dart文件:
touch lib/app.dart
新建routes.dart
import 'package:flutter/material.dart';
import 'package:your_project/views/pages/home_page.dart';
import 'package:your_project/views/pages/login_page.dart';
class Routes {
static const home = '/';
static const login = '/login';
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case home:
return MaterialPageRoute(builder: (_) => HomePage());
case login:
return MaterialPageRoute(builder: (_) => LoginPage());
default:
throw FormatException("Route not found");
}
}
}
在home_page.dart文件中创建一个HomePage类:
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: Text('Welcome to Home Page'),
),
);
}
}
在login_page.dart文件中创建一个LoginPage类:
import 'package:flutter/material.dart';
class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('login'),
),
body: Center(
child: Text('Welcome to'),
),
);
}
}
在app.dart文件中
import 'package:flutter/material.dart';
import 'views/pages/home_page.dart';
import 'views/pages/login_page.dart';
import 'routes/routes.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: Routes.home,
routes: {
Routes.home: (context) => HomePage(),
Routes.login: (context) => LoginPage(),
},
);
}
}
或者也可以直接在app配置路由
import 'package:flutter/material.dart';
import 'views/pages/home_page.dart';
import 'views/pages/login_page.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/',
routes: {
'/': (context) => HomePage(),
'/login': (context) => LoginPage(),
},
);
}
}
更新main.dart文件
import 'package:flutter/material.dart';
import 'app.dart';
void main() {
runApp(MyApp());
}
登錄頁面
在widgets中新建input_widget
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class InputWidget extends StatelessWidget {
final String hint;
final ValueChanged<String>? onChanged;
final bool obscureText;
final TextInputType? keyboardType;
final Widget? suffixIcon;
final List<TextInputFormatter>? inputFormatters;
const InputWidget(
{super.key,
required this.hint,
this.onChanged,
this.obscureText = false,
this.keyboardType,
this.suffixIcon,
this.inputFormatters});
get _input {
return TextField(
// 当输入框内容改变时,会调用这个函数
onChanged: onChanged,
// 是否隐藏输入的文本
obscureText: obscureText,
// 键盘类型
keyboardType: keyboardType,
// 是否启用自动纠错,当obscureText为true(隐藏输入的文本)时,自动纠错被禁用。
autocorrect: !obscureText,
// 光标颜色
cursorColor: Colors.white,
/*输入的文本的样式*/
style: const TextStyle(
fontSize: 17, color: Colors.white, fontWeight: FontWeight.w500),
/*输入框的装饰,包括边框、提示文本和提示文本的样式 */
decoration: InputDecoration(
border: InputBorder.none,
hintText: hint,
hintStyle: const TextStyle(fontSize: 17, color: Colors.grey),
//
suffixIcon: suffixIcon,
),
inputFormatters: inputFormatters,
);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
_input,
const Divider(
color: Colors.white,
height: 1,
thickness: 0.5,
)
],
);
}
}
樣式


扩展
import 'package:flutter/cupertino.dart';
// 扩展int以方便使用
extension Intfix on int {
SizedBox get paddingHeight{
return SizedBox(height: toDouble(),);
}
SizedBox get paddingWidth{
return SizedBox(width: toDouble());
}
}
//扩展double以方便使用
extension Dobulefix on double{
SizedBox get paddingHeight{
return SizedBox(height: this,);
}
SizedBox get paddingWidth{
return SizedBox(width: this);
}
}
页面使用
//原本
const SizedBox(height: 16.0),
//扩展效果
16.paddingHeight
背景改成本地的方便加载
新建assets
yaml
flutter:
uses-material-design: true
assets:
- assets/background.jpg
注册
注册是一个h5页面
所以需要跳转到h5
flutter pub add url_launcher
页面
_jumpRegistration() async {
Uri uri = Uri.parse('http://together.muxuetianyin.cn/register');
if(!await launchUrl(uri,mode: LaunchMode.externalApplication)){
debugPrint('Could not launch $uri');
}
}
请求
import 'package:http/http.dart' as http;
import 'dart:convert';
class ApiService {
static const String _baseUrl = 'https://muxuetianyin.cn/api';
// 登录接口
static Future<Map<String, dynamic>> login(String userAccount, String userPassword) async {
var url = '$_baseUrl/api/user/login';
var body = jsonEncode({
'userAccount': userAccount,
'userPassword': userPassword,
});
var response = await http.post(
Uri.parse(url),
headers: {"Content-Type": "application/json"},
body: body,
);
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
throw Exception('Failed to login');
}
}
//
}
yaml
shared_preferences: ^2.0.8
或者用另一个库
flutter pub add flutter_hi_cache
页面
void _handleLogin() async {
try {
var result = await ApiService.login(
_usernameController.text,
_passwordController.text,
);
if (result['code'] == 0) {
print('登录成功');
// 这里可以处理登录成功后的逻辑,例如跳转到主页
// 存储请求头
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('Authorization', result['data']['token']);
} else {
print('登录失败:${result['description']}');
}
} catch (e) {
print('请求失败:$e');
}
}
LoginService
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class LoginService {
static const String _baseUrl = 'https://muxuetianyin.cn';
// 登录接口
static Future<Map<String, dynamic>> login(String userAccount, String userPassword) async {
var url = '$_baseUrl/api/user/login';
var body = jsonEncode({
'userAccount': userAccount,
'userPassword': userPassword,
});
var response = await http.post(
Uri.parse(url),
headers: {"Content-Type": "application/json"},
body: body,
);
if (response.statusCode == 200) {
var result = jsonDecode(response.body);
// 保存token
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString("token", result['data']['token']);
prefs.setString("refreshToken", result['data']['refreshToken']);
return result;
} else {
throw Exception('Failed to login');
}
}
//刷新Token
static Future<Map<String, dynamic>> refreshToken() async {
var url = '$_baseUrl/api/user/refresh';
SharedPreferences prefs = await SharedPreferences.getInstance();
var refreshToken = prefs.getString("refreshToken");
if(refreshToken == null){
throw Exception('Failed to refreshToken');
}
var body = jsonEncode({
'refreshToken': refreshToken
});
var response = await http.post(
Uri.parse(url),
headers: {"Content-Type": "application/json"},
body: body,
);
if (response.statusCode == 200) {
var result = jsonDecode(response.body);
// 保存新的 token
prefs.setString("token", result['data']);
return result;
} else {
throw Exception('Failed to refreshToken');
}
}
// 获取token
static Future<String?> getToken() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var token = prefs.getString("token");
return token;
}
// 登出
static Future<void> logout() async {
// 移除token
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.remove("token");
// 跳转到登录页
// ...
}
}