1、搭建flutter的开发环境:http://www.haijin.xyz/article/730
2、创建一个项目:
flutter create myapp
3、项目结构:
路由管理几个比较重要的点就在:
routes.dart的定义
home_page中:
onTap: () {
print("跳转到壁纸的详情页面");
Navigator.pushNamed(context, RoutePath.detailPage,
arguments:item
);
// Navigator.push(context, MaterialPageRoute(builder: (context){
// return DetailPage(
// records: item,
// );
// }));
},
main.dart中:
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '底部导航栏示例',
theme: ThemeData(
primarySwatch: Colors.blue,
),
onGenerateRoute: Routes.generateRoute,
initialRoute: RoutePath.myHome,
// home: MyHomePage(),
);
}
4、项目代码:(code_api.dart和code_model.dart不重要就不写了)
wallpaper_detail_api.dart:
import '../network/api_service.dart';
class WallpaperDetailApi {
final ApiService _apiService = ApiService();
void fetchTodoData({
int? currentPage,
int? pageSize,
int? typeId,
String? name,
required Function(dynamic data) callBack,
}) {
var body = {
"currentPage":currentPage,
"pageSize":pageSize,
"typeId":typeId,
"name":name,
};
_apiService.get(
url: 'http://192.168.97.13:5800/api',
body: body,
callbackFunc:callBack,
);
}
}
bottom_navigation_bar.dart:
// lib/components/bottom_navigation_bar.dart
import 'package:flutter/material.dart';
class BottomNavigationBarComponent extends StatefulWidget {
final List<Widget> pages;
final int initialIndex;
const BottomNavigationBarComponent({
super.key,
required this.pages,
this.initialIndex = 0,
});
@override
State createState() =>
_BottomNavigationBarComponentState();
}
class _BottomNavigationBarComponentState
extends State<BottomNavigationBarComponent> {
int _currentIndex = 0;
@override
void initState() {
super.initState();
_currentIndex = widget.initialIndex;
}
void _onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: IndexedStack(
index: _currentIndex,
children: widget.pages,
),
),
BottomNavigationBar(
currentIndex: _currentIndex,
onTap: _onTabTapped,
showSelectedLabels: true,
showUnselectedLabels: true,
type: BottomNavigationBarType.fixed,
selectedFontSize:12,
selectedItemColor: Colors.lightBlue,
unselectedItemColor: Theme.of(context).unselectedWidgetColor,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: '首页',
),
BottomNavigationBarItem(
icon: Icon(Icons.info),
label: '关于',
),
],
),
],
);
}
}
wallpaper_detail.dart:
/// total : 55052
/// pageNo : 1
/// pageSize : 5506
class WallpaperDetail {
WallpaperDetail({
num? total,
num? pageNo,
num? pageSize,
List<Records>? records,
}) {
_total = total;
_pageNo = pageNo;
_pageSize = pageSize;
_records = records;
}
WallpaperDetail.fromJson(dynamic json) {
_total = json['total'];
_pageNo = json['pageNo'];
_pageSize = json['pageSize'];
if (json['records'] != null) {
_records = [];
json['records'].forEach((v) {
_records?.add(Records.fromJson(v));
});
}
}
num? _total;
num? _pageNo;
num? _pageSize;
List<Records>? _records;
WallpaperDetail copyWith({
num? total,
num? pageNo,
num? pageSize,
List<Records>? records,
}) =>
WallpaperDetail(
total: total ?? _total,
pageNo: pageNo ?? _pageNo,
pageSize: pageSize ?? _pageSize,
records: records ?? _records,
);
num? get total => _total;
num? get pageNo => _pageNo;
num? get pageSize => _pageSize;
List<Records>? get records => _records;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['total'] = _total;
map['pageNo'] = _pageNo;
map['pageSize'] = _pageSize;
final records = _records;
if (records != null) {
map['records'] = records.map((v) => v.toJson()).toList();
}
return map;
}
}
/// id : 1933
/// wallpaperName : "甜美Lolita少女壁纸"
/// wallpaperListUrl : "https://up.enterdesk.com/edpic_360_360/cb/57/b9/cb57b98151247cd5a6e5211f05e460e3.jpg"
/// wallpaperType : 1
/// remark : ""
/// wallpaperDetailUrl : "https://up.enterdesk.com/edpic_source/cb/57/b9/cb57b98151247cd5a6e5211f05e460e3.jpg"
class Records {
Records({
num? id,
String? wallpaperName,
String? wallpaperListUrl,
num? wallpaperType,
String? remark,
String? wallpaperDetailUrl,
}) {
_id = id;
_wallpaperName = wallpaperName;
_wallpaperListUrl = wallpaperListUrl;
_wallpaperType = wallpaperType;
_remark = remark;
_wallpaperDetailUrl = wallpaperDetailUrl;
}
Records.fromJson(dynamic json) {
_id = json['id'];
_wallpaperName = json['wallpaperName'];
_wallpaperListUrl = json['wallpaperListUrl'];
_wallpaperType = json['wallpaperType'];
_remark = json['remark'];
_wallpaperDetailUrl = json['wallpaperDetailUrl'];
}
num? _id;
String? _wallpaperName;
String? _wallpaperListUrl;
num? _wallpaperType;
String? _remark;
String? _wallpaperDetailUrl;
Records copyWith({
num? id,
String? wallpaperName,
String? wallpaperListUrl,
num? wallpaperType,
String? remark,
String? wallpaperDetailUrl,
}) =>
Records(
id: id ?? _id,
wallpaperName: wallpaperName ?? _wallpaperName,
wallpaperListUrl: wallpaperListUrl ?? _wallpaperListUrl,
wallpaperType: wallpaperType ?? _wallpaperType,
remark: remark ?? _remark,
wallpaperDetailUrl: wallpaperDetailUrl ?? _wallpaperDetailUrl,
);
num? get id => _id;
String? get wallpaperName => _wallpaperName;
String? get wallpaperListUrl => _wallpaperListUrl;
num? get wallpaperType => _wallpaperType;
String? get remark => _remark;
String? get wallpaperDetailUrl => _wallpaperDetailUrl;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = _id;
map['wallpaperName'] = _wallpaperName;
map['wallpaperListUrl'] = _wallpaperListUrl;
map['wallpaperType'] = _wallpaperType;
map['remark'] = _remark;
map['wallpaperDetailUrl'] = _wallpaperDetailUrl;
return map;
}
}
api_service.dart:
// lib/network/api_service.dart
import 'dart:convert';
import 'package:dio/dio.dart';
class ApiService {
final Dio _dio = Dio();
Future<dynamic> request({
required String url,
required String method,
Map<String, dynamic>? headers,
dynamic body,
}) async {
try {
final options = Options(method: method.toUpperCase());
if (headers != null) {
options.headers = headers;
}
final response = await _dio.request(
url,
options: options,
data: body,
);
if (response.statusCode == 200 || response.statusCode == 201) {
return response.data;
} else {
throw Exception(
'Failed to load data with status code: ${response.statusCode}');
}
} catch (e) {
throw Exception('Error: $e');
}
}
Future<void> get({
required String url,
dynamic body,
required Function(dynamic) callbackFunc,
}) async {
final queryParameters = body;
final finalUrl = '$url?${_convertQueryParameters(queryParameters)}';
print("get请求的地址:$finalUrl");
final data = await request(
url: finalUrl,
method: 'GET',
);
// 回调响应的数据
callbackFunc(data);
}
Future<void> post({
required String url,
dynamic body,
required Function(dynamic) callbackFunc,
}) async {
final finalBody = body ?? {};
print("post请求的地址:$url");
final data = await request(
url: url,
method: 'GET',
body: finalBody,
);
// 回调响应的数据
callbackFunc(data);
}
String _convertQueryParameters(Map<String, dynamic>? parameters) {
if (parameters == null || parameters.isEmpty) {
return "";
}
final List<String> queryParts = [];
parameters.forEach((key, value) {
// 如果值为 null,则跳过这个键值对
if (value == null) return;
queryParts.add('$key=${Uri.encodeComponent(value.toString())}');
});
return queryParts.join('&');
}
}
about_page.dart:
// lib/pages/about_page.dart
import 'package:flutter/material.dart';
class AboutPage extends StatelessWidget {
const AboutPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('关于'),
),
body: const Center(
child: Text('这是关于页面'),
),
);
}
}
detail_page.dart:
import 'package:flutter/material.dart';
import '../model/wallpaper_detail.dart';
class DetailPage extends StatelessWidget {
final Records? records;
const DetailPage({
super.key,
required this.records,
});
@override
Widget build(BuildContext context) {
if (records == null) {
return const Text('数据缺失');
}
final String idStr = records!.id.toString();
final String wallpaperName = records!.wallpaperName ?? '未知名称';
return Scaffold(
appBar: AppBar(
title: Text(wallpaperName),
),
body: SingleChildScrollView( // 使用 SingleChildScrollView 来允许滚动
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Image.network(
records!.wallpaperDetailUrl ?? '',
fit: BoxFit.contain, // 使用 BoxFit.contain 以确保图片完整显示
loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
: null,
),
);
},
),
),
const SizedBox(height: 16),
Text(
"ID: $idStr",
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
'这是详情页面',
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
);
}
}
home_page.dart:
import 'package:flutter/material.dart';
import 'package:myapp/api/wallpaper_detail_api.dart';
import 'package:myapp/model/wallpaper_detail.dart';
import 'package:myapp/pages/detail_page.dart';
import 'package:myapp/route/routes.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin {
final WallpaperDetailApi _wallpaperDetailApi = WallpaperDetailApi();
WallpaperDetail? _wallpaper;
List<Records> _records = [];
// 页码
int _currentPage = 1;
// 是否正在刷新
bool _isRefreshing = false;
// 是否正在加载更多
bool _isLoadingMore = false;
double _listHeight = 0.0; // 列表的高度
final ScrollController _scrollController = ScrollController();
// 索引
int _itemIndex = 0;
@override
void initState() {
// TODO: implement initState
super.initState();
_scrollController.addListener((){
// 滑动到底部,去做加载更多的请求
print("_itemIndex:$_itemIndex");
print(_records.length);
if(_itemIndex + 5 > _records.length){
if (!_isLoadingMore) {
print("加载更多");
_currentPage++;
_fetchData();
}
}
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
Future<void> _fetchData() async {
try{
print("页码数:");
print(_currentPage);
_wallpaperDetailApi.fetchTodoData(currentPage: _currentPage,callBack: _analyzeWallpaperData);
} catch(e) {
print(e);
}
}
// 拉下刷新函数
Future<void> _refreshFunc() async {
print("刷新");
_currentPage = 1;
_fetchData();
}
// 解析获取的数据
void _analyzeWallpaperData(dynamic data) {
_wallpaper = WallpaperDetail.fromJson(data);
print("获取的数据");
if (_currentPage == 1) {
// 如果是第一页,则替换列表
setState(() {
_records = _wallpaper!.records!;
});
} else {
// 如果不是第一页,则追加到列表末尾
setState(() {
_wallpaper!.records?.forEach((item){
_records.add(item);
});
});
}
setState(() {
_isRefreshing = false;
_isLoadingMore = false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('壁纸列表'),
),
body: RefreshIndicator(
onRefresh: _refreshFunc, // 下拉刷新
child: _wallpaper == null
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
setState(() {
_currentPage = 1; // 重置页码
// _records.clear(); // 清空列表
});
_fetchData(); // 获取第一页数据
},
child: Text('获取壁纸'),
),
],
),
) : ListView.builder(
itemCount: (_wallpaper?.pageSize??0) as int,
itemBuilder: (context, index) {
_itemIndex = index;
if(index > _currentPage * 10 - 5){
if (!_isLoadingMore) {
print("加载更多");
_currentPage++;
_fetchData();
}
}
if (index < _records!.length) {
final item = _records?[index];
return ListTile(
leading: Image.network(item!.wallpaperListUrl??""),
title: Text(item!.wallpaperName??""),
subtitle: Text('ID: ${item?.id}'),
onTap: () {
print("跳转到壁纸的详情页面");
Navigator.pushNamed(context, RoutePath.detailPage,
arguments:item
);
// Navigator.push(context, MaterialPageRoute(builder: (context){
// return DetailPage(
// records: item,
// );
// }));
},
);
} else {
return Container(
height: 50,
alignment: Alignment.center,
child: CircularProgressIndicator(),
);
}
},
// controller: _scrollController,
),
),
);
}
@override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
}
routes.dart:
import 'package:flutter/material.dart';
import 'package:myapp/model/wallpaper_detail.dart';
import 'package:myapp/pages/detail_page.dart';
import 'package:myapp/pages/home_page.dart';
import '../main.dart';
class Routes {
static Route<dynamic> generateRoute(RouteSettings settings) {
print(settings.name);
switch (settings.name) {
case RoutePath.myHome:
return pageRoute(MyHomePage());
case RoutePath.detailPage:
Records? recordsObject = (settings.arguments is Records ? settings.arguments : null) as Records?;
return pageRoute(DetailPage(records: recordsObject));
}
return pageRoute(Scaffold(
body: SafeArea(
child: Center(child: Text("路由:│$settings.name}不存在 ")),
)));
}
// 获取页面
static MaterialPageRoute pageRoute(
Widget widget, {
RouteSettings? settings,
bool? fullscreenDialog,
bool? maintainState,
bool? allowSnapshotting,
}) {
return MaterialPageRoute(
builder: (context) {
return widget;
},
settings: settings,
fullscreenDialog: fullscreenDialog ?? false,
maintainState: maintainState ?? true,
allowSnapshotting: allowSnapshotting ?? true,
);
}
}
// 路由地址
class RoutePath {
// 首页
static const String myHome = "/";
// 详情页
static const String detailPage = "detail_page";
}
model_util.dart:
// lib/utils/model_util.dart
import 'dart:convert';
class ModelUtil<T> {
// 将请求返回后的json数组数据转化为某一个实体的集合
static List<T> convertJsonToList<T>(dynamic data, T Function(dynamic json) fromJson) {
List<dynamic> jsonData = data;
List<T> result = jsonData.map((v)=>fromJson(v)).toList();
return result;
}
// 将请求返回后的单个json对象转化为指定类型的实体对象
static T convertJsonToObject<T>(dynamic data, T Function(dynamic json) fromJson) {
return fromJson(data);
}
}
main.dart:
import 'package:flutter/material.dart';
import 'package:myapp/route/routes.dart';
import 'components/bottom_navigation_bar.dart';
import 'pages/home_page.dart';
import 'pages/about_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '底部导航栏示例',
theme: ThemeData(
primarySwatch: Colors.blue,
),
onGenerateRoute: Routes.generateRoute,
initialRoute: RoutePath.myHome,
// home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
final List<Widget> pages = [
HomePage(),
const AboutPage(),
];
return BottomNavigationBarComponent(
pages: pages,
);
}
}
pubspec.yaml:
name: myapp
description: "A new Flutter project."
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: '>=3.4.4 <4.0.0'
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.6
dio: ^4.0.6
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
flutter:
uses-material-design: true
运行截图: