flutter之路由管理

我爱海鲸 2024-07-31 17:34:04 flutter学习

简介Dart、跨平台、dio、http

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

运行截图:

 

 

你好:我的2025