为了账号安全,请及时绑定邮箱和手机立即绑定

Flutter常用功能学习:新手入门教程

标签:
移动开发
概述

本文将详细介绍flutter常用功能学习,包括环境搭建、基础组件与布局管理、导航与路由管理、状态管理入门、数据存储与网络请求以及测试与调试技巧,帮助开发者快速掌握Flutter开发的核心技能。

引入与环境搭建
Flutter简介

Flutter 是谷歌推出的一款开源 UI 软件开发工具包。它允许开发者使用一套代码库来开发多种平台的应用,包括 Android、iOS、Web、Windows、macOS 和 Linux。Flutter 的设计目标是快速开发高性能的移动应用,并且可以直接运行在原生平台上,这使得 Flutter 能够提供接近原生应用的性能和用户体验。

Flutter 最大的优势之一是其高效的热重载(Hot Reload)功能,它允许开发者在几秒钟内看到代码更改的效果,极大地提高了开发效率。此外,Flutter 还提供了一个丰富的组件库,包括各种布局、动画、图表等,这些组件可以轻松地进行定制和扩展。

在 Flutter 中,开发者可以使用 Dart 语言编写代码,Dart 是一种面向对象、强类型的安全编程语言,由谷歌开发并在 2011 年推出,最初是作为 JavaScript 的替代品,现在被广泛用于 Web、移动应用和后端服务。

开发环境搭建

安装 Dart SDK

Dart SDK 包含了 Dart 编译器、工具和命令行接口,以及 Flutter 框架和工具。在安装 Flutter 之前,需要先安装 Dart SDK。安装步骤如下:

  1. 访问 Dart 官方网站(https://dart.dev/)并下载最新版本的 Dart SDK。
  2. 解压下载的 Dart SDK 安装包,并将解压后的 Dart SDK 添加到系统的 PATH 环境变量中。

安装 Flutter

安装 Flutter 的步骤如下:

  1. 访问 Flutter 官方网站(https://flutter.dev/)并下载最新版本的 Flutter SDK。
  2. 解压下载的 Flutter SDK 安装包,并将解压后的 Flutter SDK 添加到系统的 PATH 环境变量中。
  3. 验证安装是否成功,打开命令行工具并输入以下命令:

    flutter doctor

    如果安装正确,将显示一个检查列表,报告安装成功,并且可能列出需要安装的一些额外工具,例如 Android Studio、Xcode 等。

安装额外工具

安装 Flutter 后,还需要安装一些额外的工具来支持开发:

  • Android StudioIntelliJ IDEA:这是 Flutter 官方推荐的集成开发环境(IDE)。安装完成后,需要安装 Flutter 和 Dart 插件。
  • Android SDK:确保 Android SDK 已安装,并且 SDK Manager 中的以下组件已安装:
    • Android SDK Command-line Tools
    • SDK Platforms(例如 Android API 28)
    • Android SDK Build-Tools(例如版本 28.0.3)
    • Android Emulator
  • Xcode:如果你计划开发 iOS 应用,需要安装 Xcode,并确保安装了 Command Line Tools。
  • Windows 用户:确保已经安装了 Visual Studio 的 Windows 10 SDK 和适用于 Windows 的 Microsoft 安装包。

验证安装

完成上述步骤后,再次运行 flutter doctor 命令来确保所有组件都已正确安装。根据提示安装任何缺失的组件,然后重新运行 flutter doctor 以确认安装。

第一个Flutter项目

创建第一个 Flutter 项目并运行它。按照以下步骤操作:

  1. 打开命令行工具,使用 Flutter 命令行工具创建一个新的 Flutter 项目:

    flutter create my_first_flutter_app

    该命令会在当前目录下创建一个名为 my_first_flutter_app 的项目文件夹,目录结构如下:

    my_first_flutter_app/
    ├── android/
    ├── ios/
    ├── lib/
    │   └── main.dart
    ├── test/
    └── pubspec.yaml
  2. 进入项目文件夹:

    cd my_first_flutter_app
  3. 打开 lib/main.dart 文件,这个文件是项目的主入口点。默认代码如下:

    import 'package:flutter/material.dart';
    
    void main() {
     runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
     @override
     Widget build(BuildContext context) {
       return MaterialApp(
         title: 'Flutter Demo',
         theme: ThemeData(
           primarySwatch: Colors.blue,
         ),
         home: MyHomePage(title: 'Flutter Demo Home Page'),
       );
     }
    }
    
    class MyHomePage extends StatefulWidget {
     MyHomePage({Key? key, required this.title}) : super(key: key);
    
     final String title;
    
     @override
     _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
     int _counter = 0;
    
     void _incrementCounter() {
       setState(() {
         _counter++;
       });
     }
    
     @override
     Widget build(BuildContext context) {
       return Scaffold(
         appBar: AppBar(
           title: Text(widget.title),
         ),
         body: Center(
           child: Column(
             mainAxisAlignment: MainAxisAlignment.center,
             children: <Widget>[
               Text(
                 'You have pushed the button this many times:',
               ),
               Text(
                 '$_counter',
                 style: Theme.of(context).textTheme.headline4,
               ),
             ],
           ),
         ),
         floatingActionButton: FloatingActionButton(
           onPressed: _incrementCounter,
           tooltip: 'Increment',
           child: Icon(Icons.add),
         ),
       );
     }
    }
  4. 在命令行中运行以下命令来启动开发服务器并安装模拟器:

    flutter run

    运行命令后,你将看到一个默认的 Android 或 iOS 模拟器启动,并运行应用。你会看到一个简单的计数器应用,每次点击按钮,计数器就会增加。

通过以上步骤,你已经成功创建并运行了第一个 Flutter 项目。接下来,我们将会深入学习 Flutter 的基础组件和布局管理。

基础组件与布局
常用UI组件介绍

Flutter 提供了大量的内置 UI 组件,这些组件可以用来构建丰富的用户界面。下面是一些常用的 Flutter 组件:

Text

Text 组件用于显示文本。下面是一个简单的 Text 使用示例:

Text('Hello, Flutter!')

可以自定义文本属性,例如字体样式、大小、颜色等:

Text(
  'Hello, Flutter!',
  style: TextStyle(
    fontSize: 20.0,
    color: Colors.blue,
    fontWeight: FontWeight.bold,
  ),
)

Button

Flutter 提供了多种类型的按钮,包括 RaisedButtonFlatButtonFloatingActionButtonIconButton 等。

RaisedButton

RaisedButton 是一个带有阴影效果的按钮:

RaisedButton(
  onPressed: () {},
  child: Text('Raised Button'),
)

FlatButton

FlatButton 是一个扁平的按钮,没有阴影效果:

FlatButton(
  onPressed: () {},
  child: Text('Flat Button'),
)

FloatingActionButton

FloatingActionButton 是一种常用的悬浮按钮,通常放在应用的右下角:

FloatingActionButton(
  onPressed: () {},
  tooltip: 'Tap',
  child: Icon(Icons.add),
)

IconButton

IconButton 是一个包含图标的按钮:

IconButton(
  onPressed: () {},
  icon: Icon(Icons.search),
)

Container

Container 组件用于创建一个容器,可以包含其他组件并设置其外观属性,例如颜色、边框、背景图片等:

Container(
  color: Colors.red,
  padding: EdgeInsets.all(10.0),
  child: Text('Hello, Container!'),
)

Image

Image 组件用于显示图片。可以使用 Image.network 从网络加载图片,或者使用 Image.asset 从项目资源加载图片:

Image.network('https://example.com/image.jpg')
Image.asset('assets/images/ic_launcher.png')

ListView

ListView 组件用于创建一个垂直滚动的列表:

ListView(
  children: <Widget>[
    ListTile(
      title: Text('Item 1'),
      subtitle: Text('Subtitle 1'),
    ),
    ListTile(
      title: Text('Item 2'),
      subtitle: Text('Subtitle 2'),
    ),
  ],
)

Column 和 Row

ColumnRow 组件用于将多个子组件排列成列和行。例如:

Column(
  children: <Widget>[
    Text('Column 1'),
    Text('Column 2'),
  ],
)
Row(
  children: <Widget>[
    Text('Row 1'),
    Text('Row 2'),
  ],
)

Stack 和 Positioned

StackPositioned 组件用于创建重叠的布局。例如:

Stack(
  children: <Widget>[
    Image.network('https://example.com/image.jpg'),
    Positioned(
      top: 0.0,
      left: 0.0,
      child: Text('Top Left'),
    ),
    Positioned(
      bottom: 0.0,
      right: 0.0,
      child: Text('Bottom Right'),
    ),
  ],
)

了解这些基础组件和它们的用法是构建 Flutter 应用程序的重要一步。接下来,我们将进一步讨论布局管理器的使用。

布局管理器使用

Flutter 提供了多种布局管理器来帮助你创建灵活且响应式的设计。常用的布局管理器包括 ColumnRowStackWrapFlexExpanded 等。

Column 和 Row

ColumnRow 是最基本的布局管理器。Column 用于垂直排列其子组件,Row 用于水平排列其子组件。

Column(
  children: <Widget>[
    Text('Row 1'),
    Text('Row 2'),
  ],
)
Row(
  children: <Widget>[
    Text('Column 1'),
    Text('Column 2'),
  ],
)

Stack 和 Positioned

Stack 用于在二维空间中叠加其子组件,使用 Positioned 可以精确控制子组件的位置和大小。

Stack(
  children: <Widget>[
    Container(
      height: 200.0,
      width: 200.0,
      color: Colors.red,
    ),
    Positioned(
      top: 50.0,
      left: 50.0,
      child: Container(
        height: 100.0,
        width: 100.0,
        color: Colors.blue,
      ),
    ),
  ],
)

Wrap

Wrap 用于水平或垂直排列其子组件,并在空间不足时自动换行。

Wrap(
  spacing: 8.0, // 主轴(水平)方向空白
  runSpacing: 4.0, // 纵轴(垂直)方向空白
  children: <Widget>[
    Text('Wrap 1'),
    Text('Wrap 2'),
    Text('Wrap 3'),
    Text('Wrap 4'),
  ],
)

Flex 和 Expanded

Flex 用于创建一个弹性布局,其子组件通过 ExpandedFlexible 来控制它们的弹性空间。

Row(
  children: <Widget>[
    Expanded(
      flex: 1,
      child: Container(
        color: Colors.red,
        height: 100.0,
      ),
    ),
    Expanded(
      flex: 2,
      child: Container(
        color: Colors.blue,
        height: 100.0,
      ),
    ),
  ],
)

Align 和 Center

AlignCenter 用于将子组件相对于其父组件进行对齐。

Center(
  child: Text('Center'),
)
Align(
  alignment: Alignment.topRight,
  child: Text('Align Top Right'),
)

通过这些布局管理器,你可以灵活地设计和排列组件,从而构建出美观且响应式的用户界面。接下来,我们将讨论如何设置样式和主题。

样式和主题设置

Flutter 提供了强大的样式和主题设置功能,使得你可以轻松地定制应用的外观。下文将介绍如何使用 ThemeThemeData 来设置全局样式和主题。

Theme 和 ThemeData

Theme 是一个高阶组件,用于包裹整个应用,并为子组件提供全局主题。使用 ThemeData 类来定义主题。

Theme(
  data: ThemeData(
    primarySwatch: Colors.blue,
    primaryColor: Colors.blue,
    accentColor: Colors.red,
  ),
  child: MyApp(),
)

Material Design 主题

Flutter 默认遵循 Material Design 设计规范,提供了许多预定义的主题。你可以在 ThemeData 中设置字体样式、颜色、阴影等属性。

Theme(
  data: ThemeData(
    primarySwatch: Colors.blue,
    primaryColor: Colors.blue,
    accentColor: Colors.red,
    textTheme: TextTheme(
      bodyText1: TextStyle(fontSize: 16.0),
      bodyText2: TextStyle(fontSize: 14.0),
    ),
  ),
  child: MyApp(),
)

自定义主题

你可以通过覆盖默认主题来创建自定义主题。例如,你可以自定义按钮、文本、图标等的外观:

Theme(
  data: ThemeData(
    primarySwatch: Colors.blue,
    primaryColor: Colors.blue,
    accentColor: Colors.red,
    buttonTheme: ButtonThemeData(
      buttonColor: Colors.red,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(10.0),
      ),
    ),
  ),
  child: MyApp(),
)

自定义组件样式

你也可以直接为每个组件设置特定的样式。例如,为 Text 组件设置特定的样式:

Text(
  'Hello, Flutter!',
  style: TextStyle(
    fontSize: 20.0,
    color: Colors.blue,
    fontWeight: FontWeight.bold,
  ),
)

通过这些样式和主题设置,你可以轻松地定制应用的外观和感觉,使其符合你的设计要求。接下来,我们将进一步探讨导航与路由管理。

导航与路由管理
路由管理基础

在 Flutter 中,路由管理用于在不同的页面之间进行导航。Flutter 提供了 PageRouteBuilderPageRoute 两个主要类来创建自定义的页面路由。

MaterialPageRoute

MaterialPageRoute 是最常用的路由类,它实现了 PageRoute 接口,并提供了默认的过渡效果。以下是一个简单的路由示例:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: '/',
      routes: <String, WidgetBuilder>{
        '/': (BuildContext context) => HomePage(),
        '/second': (BuildContext context) => SecondPage(),
      },
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: RaisedButton(
          onPressed: () {
            Navigator.pushNamed(context, '/second');
          },
          child: Text('Go to Second Page'),
        ),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Page'),
      ),
      body: Center(
        child: RaisedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: Text('Go back'),
        ),
      ),
    );
  }
}

在这个示例中,MaterialPageRoute 用于定义两个页面之间的导航。第一个页面 (HomePage) 有一个按钮,点击后导航到第二个页面 (SecondPage)。在第二个页面,有一个按钮用于返回上一个页面。

CupertinoPageRoute

CupertinoPageRoute 用于创建带圆润过渡效果的路由,适用于 iOS 风格的应用:

Navigator.push(
  context,
  CupertinoPageRoute(builder: (context) => SecondPage()),
);

接下来,我们将讨论如何使用导航组件来更加方便地实现页面跳转。

导航组件使用

Flutter 提供了一些常用的导航组件,例如 NavigatorNavigator.of,它们可以方便地管理和跳转页面。

Navigator.of

Navigator.of 是一个方便的方法,用于获取当前路由栈中的 NavigatorState,从而进行页面跳转操作:

Navigator.of(context).pushNamed('/second');

Navigator.pop

Navigator.pop 用于返回到上一个页面。可以在按钮的 onPressed 回调中使用:

RaisedButton(
  onPressed: () {
    Navigator.pop(context);
  },
  child: Text('Go back'),
)

Navigator.push

Navigator.push 用于跳转到一个新的页面,并将新页面添加到路由栈中。例如:

RaisedButton(
  onPressed: () {
    Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage()));
  },
  child: Text('Go to Second Page'),
)

Navigator.pushReplacement

Navigator.pushReplacement 用于跳转到一个新的页面,并替换当前页面。这在需要重置用户界面状态时非常有用:

RaisedButton(
  onPressed: () {
    Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => SecondPage()));
  },
  child: Text('Go to Second Page'),
)

通过这些导航组件,你可以轻松地实现页面之间的跳转,为用户提供流畅的导航体验。接下来,我们将介绍如何在页面间传递数据。

页面间传值

在 Flutter 中,页面间传递数据可以通过构造函数或额外参数来实现。下面是一个通过构造函数传递数据的示例:

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: RaisedButton(
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => SecondPage(title: 'Passed from Home Page'),
              ),
            );
          },
          child: Text('Go to Second Page'),
        ),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  final String title;

  SecondPage({Key key, @required this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: RaisedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: Text('Go back'),
        ),
      ),
    );
  }
}

在这个示例中,HomePage 页面通过构造函数将 title 传递给 SecondPage 页面。SecondPage 页面使用这个 title 来设置其标题。

使用额外参数传递数据

除了构造函数,还可以使用额外参数(例如 arguments)传递数据。例如:

RaisedButton(
  onPressed: () {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => SecondPage(),
      ),
    ).then((value) {
      print('Received data: $value');
    });
  },
  child: Text('Go to Second Page'),
)

SecondPage 中,接收并处理这些额外参数:

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Page'),
      ),
      body: Center(
        child: RaisedButton(
          onPressed: () {
            Navigator.pop(context, 'Data from Second Page');
          },
          child: Text('Go back'),
        ),
      ),
    );
  }
}

通过这些方法,你可以实现页面间的高效数据传递。接下来,我们将讨论状态管理的相关概念。

状态管理入门
状态管理概念

状态管理是 Flutter 开发中的一个重要概念,它涉及如何管理应用程序的状态,如数据、用户交互等,以便在不同的页面和组件之间共享和更新这些状态。一个良好的状态管理机制可以提高应用的可维护性、可测试性和性能。

在 Flutter 中,状态管理通常通过以下几个方面来实现:

  1. Provider:用于状态管理和全局状态共享。
  2. BlocRx:用于构建可复用逻辑组件。
  3. InheritedWidget:用于简单状态的共享。
  4. Riverpod:一种轻量级的状态管理解决方案。

下面我们将详细介绍如何使用这些状态管理库。

Flutter中的状态管理库

Provider

Provider 是一种简单而强大的状态管理库,广泛应用于 Flutter 开发中。它通过 Provider.of 方法从页面或组件中获取状态,而不需要通过构造函数传递状态。

以下是一个简单的 Provider 示例:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => Counter(),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: HomePage(),
      ),
    );
  }
}

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<Counter>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${counter.count}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counter.increment();
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

Bloc

Bloc 是一种行为驱动的组件库,用于构建可复用的逻辑组件。它通过事件和状态来管理应用的生命周期,使得代码更加模块化和可测试。

以下是一个简单的 Bloc 示例:

import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
import 'package:equatable/equatable.dart';

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  @override
  CounterState get initialState => CounterState(0);

  @override
  Stream<CounterState> mapEventToState(
    CounterEvent event,
  ) async* {
    if (event is IncrementEvent) {
      yield CounterState(state.value + 1);
    }
  }
}

class CounterEvent extends Equatable {
  @override
  List<Object> get props => [];
}

class IncrementEvent extends CounterEvent {}

class CounterState extends Equatable {
  final int value;

  CounterState(this.value);

  @override
  List<Object> get props => [value];
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => CounterBloc(),
      child: Scaffold(
        appBar: AppBar(
          title: Text('Home Page'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'You have pushed the button this many times:',
              ),
              BlocBuilder<CounterBloc, CounterState>(
                builder: (context, state) {
                  return Text(
                    '${state.value}',
                    style: Theme.of(context).textTheme.headline4,
                  );
                },
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            context.bloc<CounterBloc>().add(IncrementEvent());
          },
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

Riverpod

Riverpod 是一种轻量级的状态管理解决方案,它提供了类似 Provider 的功能,但更简单、更易于使用。以下是一个简单的 Riverpod 示例:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:riverpod/riverpod.dart';

void main() {
  runApp(MyApp());
}

final counterProvider = StateProvider<int>((ref) => 0);

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ProviderScope(
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: HomePage(),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = context.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${counter.value}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          context.read(counterProvider.notifier).state++;
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

InheritedWidget

InheritedWidget 是 Flutter 内置的一种状态管理机制,用于在树中传播数据。它允许特定的数据在树中传递,而不需要通过构造函数层层传递。以下是一个简单的 InheritedWidget 示例:

import 'package:flutter/material.dart';

class CounterWidget extends InheritedWidget {
  final int count;
  final void Function() increment;

  CounterWidget({
    Key key,
    @required Widget child,
    @required this.count,
    @required this.increment,
  }) : super(key: key, child: child);

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return count != (oldWidget as CounterWidget).count;
  }

  static CounterWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CounterWidget>();
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CounterWidget(
      count: 0,
      increment: () {},
      child: Scaffold(
        appBar: AppBar(
          title: Text('Home Page'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'You have pushed the button this many times:',
              ),
              Text(
                '${CounterWidget.of(context).count}',
                style: Theme.of(context).textTheme.headline4,
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            CounterWidget.of(context).increment();
          },
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

通过这些状态管理库,你可以轻松地管理和维护应用的状态,提高代码的可维护性和可测试性。接下来,我们将通过实践示例来巩固这些概念。

简单应用实践

示例:计数器应用

下面我们将通过一个简单的计数器应用来实践上述的状态管理库。

使用 Provider

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(MyApp());
}

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => Counter(),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: HomePage(),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<Counter>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${counter.count}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counter.increment();
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

使用 Bloc

import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
import 'package:equatable/equatable.dart';

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  @override
  CounterState get initialState => CounterState(0);

  @override
  Stream<CounterState> mapEventToState(
    CounterEvent event,
  ) async* {
    if (event is IncrementEvent) {
      yield CounterState(state.value + 1);
    }
  }
}

class CounterEvent extends Equatable {
  @override
  List<Object> get props => [];
}

class IncrementEvent extends CounterEvent {}

class CounterState extends Equatable {
  final int value;

  CounterState(this.value);

  @override
  List<Object> get props => [value];
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => CounterBloc(),
      child: Scaffold(
        appBar: AppBar(
          title: Text('Home Page'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'You have pushed the button this many times:',
              ),
              BlocBuilder<CounterBloc, CounterState>(
                builder: (context, state) {
                  return Text(
                    '${state.value}',
                    style: Theme.of(context).textTheme.headline4,
                  );
                },
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            context.bloc<CounterBloc>().add(IncrementEvent());
          },
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

使用 Riverpod

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:riverpod/riverpod.dart';

void main() {
  runApp(MyApp());
}

final counterProvider = StateProvider<int>((ref) => 0);

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ProviderScope(
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: HomePage(),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = context.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${counter.value}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          context.read(counterProvider.notifier).state++;
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

通过这些示例,你可以看到不同状态管理库的应用方式和优势。每种库都有其适用场景,选择合适的库可以简化应用的开发过程。接下来,我们将讨论数据存储与网络请求。

数据存储与网络请求
数据存储方式

在 Flutter 中,有多种数据存储方式可以用于保存应用数据。常用的有 SharedPreferences、SQLite、Hive 和本地文件等。

SharedPreferences

SharedPreferences 是一种简单且轻量级的存储方式,用于保存小量的键值对数据。以下是一个 SharedPreferences 的使用示例:

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String data;

  @override
  void initState() {
    super.initState();
    _loadData();
  }

  _loadData() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    setState(() {
      data = prefs.getString('data') ?? 'No data';
    });
  }

  _saveData(String value) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setString('data', value);
    _loadData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Data: $data',
            ),
            TextField(
              onChanged: (value) {
                _saveData(value);
              },
            ),
          ],
        ),
      ),
    );
  }
}

SQLite

SQLite 是一种轻量级的关系型数据库,适合用于存储复杂数据结构。以下是一个使用 SQLite 的示例:

import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<String> data;

  @override
  void initState() {
    super.initState();
    _loadData();
  }

  _loadData() async {
    final db = await openDatabase('mydatabase.db');
    final List<Map<String, dynamic>> maps = await db.query('mytable');
    setState(() {
      data = List.generate(maps.length, (i) => maps[i]['data']);
    });
  }

  _saveData(String value) async {
    final db = await openDatabase('mydatabase.db');
    await db.insert('mytable', {'data': value});
    _loadData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Data: $data',
            ),
            TextField(
              onChanged: (value) {
                _saveData(value);
              },
            ),
          ],
        ),
      ),
    );
  }
}

Hive

Hive 是一种轻量级的 NoSQL 数据库,支持 JSON 数据存储,适合用于保存复杂的数据结构。以下是一个使用 Hive 的示例:

import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';

void main() async {
  await Hive.initFlutter();
  await Hive.openBox('mybox');
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<String> data;

  @override
  void initState() {
    super.initState();
    _loadData();
  }

  _loadData() async {
    final box = Hive.box('mybox');
    setState(() {
      data = box.keys.map((key) => box.get(key)).toList();
    });
  }

  _saveData(String value) async {
    final box = Hive.box('mybox');
    box.put('data', value);
    _loadData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Data: $data',
            ),
            TextField(
              onChanged: (value) {
                _saveData(value);
              },
            ),
          ],
        ),
      ),
    );
  }
}

本地文件

本地文件存储可以用来保存图片、视频或其他文件。以下是一个使用本地文件存储的示例:

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String data;

  @override
  void initState() {
    super.initState();
    _loadData();
  }

  _loadData() async {
    final directory = await getApplicationDocumentsDirectory();
    final path = directory.path + '/data.txt';
    final file = File(path);
    setState(() {
      data = await file.readAsString();
    });
  }

  _saveData(String value) async {
    final directory = await getApplicationDocumentsDirectory();
    final path = directory.path + '/data.txt';
    final file = File(path);
    await file.writeAsString(value);
    _loadData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Data: $data',
            ),
            TextField(
              onChanged: (value) {
                _saveData(value);
              },
            ),
          ],
        ),
      ),
    );
  }
}

通过这些数据存储方式,你可以根据应用需求选择最适合的存储方案。

网络请求库使用

在 Flutter 中,有许多网络请求库可以使用,例如 httpdioflutter_http。这些库提供了方便的方法来发送 HTTP 请求,处理响应并处理错误。

http

http 是一个简单的 HTTP 请求库,它提供了 GETPOSTPUTDELETE 等方法。

import 'package:http/http.dart' as http;
import 'dart:convert';

Future<Map<String, dynamic>> fetchData() async {
  final response = await http.get(Uri.parse('https://example.com/api/data'));
  if (response.statusCode == 200) {
    return json.decode(response.body);
  } else {
    throw Exception('Failed to load data');
  }
}

dio

dio 是一个功能丰富的 HTTP 请求库,支持多种配置和拦截器。

import 'package:dio/dio.dart';

Future<Map<String, dynamic>> fetchData() async {
  final dio = Dio();
  final response = await dio.get('https://example.com/api/data');
  if (response.statusCode == 200) {
    return response.data;
  } else {
    throw Exception('Failed to load data');
  }
}

flutter_http

flutter_http 是另一个简单的 HTTP 请求库,适用于简单的场景。

import 'package:flutter_http/flutter_http.dart';

Future<Map<String, dynamic>> fetchData() async {
  final response = await FlutterHttp.get(Uri.parse('https://example.com/api/data'));
  if (response.statusCode == 200) {
    return json.decode(response.body);
  } else {
    throw Exception('Failed to load data');
  }
}

通过这些网络请求库,你可以方便地与后端服务进行交互,获取和处理数据。

异步操作基础

在 Flutter 中,异步操作是构建高效应用的关键。Flutter 提供了多种异步机制,例如 Futureasyncawait 等。以下是一个简单的异步操作示例:

import 'dart:async';

Future<void> fetchData() async {
  print('Fetching data...');
  await Future.delayed(Duration(seconds: 2)); // 模拟网络请求延迟
  final data = {'key': 'value'};
  print('Data fetched: $data');
}

void main() async {
  print('Starting...');
  await fetchData();
  print('Finished');
}

在这个示例中,fetchData 方法是一个异步函数,它使用 await 关键字来等待异步操作完成。当 fetchData 函数完成时,main 函数会继续执行。

使用 FutureBuilder

在 Flutter 中,FutureBuilder 是一个常用的组件,用于异步加载数据并在 UI 中显示。以下是一个使用 FutureBuilder 的示例:

import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  Future<Map<String, dynamic>> fetchData;

  @override
  void initState() {
    super.initState();
    fetchData = fetchRemoteData();
  }

  Future<Map<String, dynamic>> fetchRemoteData() async {
    final response = await http.get(Uri.parse('https://example.com/api/data'));
    if (response.statusCode == 200) {
      return json.decode(response.body);
    } else {
      throw Exception('Failed to load data');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: FutureBuilder<Map<String, dynamic>>(
          future: fetchData,
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              return Text(
                'Data: ${snapshot.data['key']}',
              );
            } else if (snapshot.hasError) {
              return Text(
                'Error: ${snapshot.error}',
              );
            }
            return CircularProgressIndicator();
          },
        ),
      ),
    );
  }
}

在这个示例中,FutureBuilder 用于异步加载数据并在 UI 中显示。当数据加载完成时,显示数据;如果发生错误,显示错误信息;如果数据还在加载中,显示一个加载指示器。

通过这些示例,你可以了解如何在 Flutter 中进行异步操作和网络请求。接下来,我们将探讨测试与调试技巧。

测试与调试技巧
单元测试入门

在 Flutter 中,单元测试是一种重要的开发实践,它可以帮助确保代码的质量和稳定性。Flutter 提供了 flutter_test 包来支持单元测试。要使用单元测试,你需要在 pubspec.yaml 文件中添加 flutter_test 依赖:

dependencies:
  flutter:
    sdk: flutter

dev_dependencies:
  flutter_test:
    sdk: flutter

然后创建一个测试文件,例如 test_counter.dart

import 'package:flutter_test/flutter_test.dart';
import 'package:myapp/main.dart';

void main() {
  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(MyApp());

    // Verify that our counter starts at 0.
    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsNothing);

    // Tap the '+' icon and trigger a frame.
    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();

    // Verify that our counter has incremented.
    expect(find.text('0'), findsNothing);
    expect(find.text('1'), findsOneWidget);
  });
}

在这个示例中,testWidgets 函数用于测试 Flutter 组件的行为。pumpWidget 方法用于构建和渲染测试组件,find 方法用于查找组件中的特定元素,tap 方法用于模拟用户点击事件。

你可以使用 flutter test 命令来运行测试:

flutter test

测试异步操作

除了同步操作,你还可以测试异步操作,例如 FutureStream。以下是一个测试异步操作的示例:

import 'package:flutter_test/flutter_test.dart';
import 'package:myapp/main.dart';

Future<int> fetchData() async {
  return 42;
}

void main() {
  test('Fetch data test', () async {
    final data = await fetchData();
    expect(data, 42);
  });
}

在这个示例中,使用 async 关键字来定义一个异步测试函数,并使用 await 关键字来等待异步操作完成。

使用 mockito 库

为了更好地测试依赖外部服务的代码,你可以使用 mockito 库来创建模拟对象。首先,在 pubspec.yaml 中添加 mockito 依赖:

dev_dependencies:
  flutter_test:
    sdk: flutter
  mockito: ^5.0.0

然后创建并使用模拟对象:

import 'package:flutter_test/flutter_test.dart';
import 'package:myapp/main.dart';
import 'package:mockito/mockito.dart';

class MockDataRepository extends Mock {
  Future<int> fetchData();
}

void main() {
  test('Fetch data test with mock', () async {
    final repository = MockDataRepository();
    when(repository.fetchData()).thenAnswer((_) async => 42);

    final data = await repository.fetchData();
    expect(data, 42);
  });
}

在这个示例中,MockDataRepository 是一个模拟的数据仓库,when 函数用于设置模拟对象的行为。

通过这些单元测试示例,你可以确保代码的正确性和健壮性。接下来,我们将讨论界面调试技巧。

界面调试技巧

在 Flutter 中,有多种工具和方法可以帮助你进行界面调试。以下是一些常用的界面调试技巧:

Flutter DevTools

Flutter DevTools 是一个集成开发工具,它提供了许多有用的调试功能,例如性能分析、调试器和日志查看器。你可以通过以下命令启动 DevTools:

flutter pub global activate flutter_devtools
flutter pub global run devtools

Debug Paint

Debug Paint 是一个用于调试布局和动画的工具。它可以帮助你可视化布局层级和组件的边界。启用 Debug Paint:

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: MyHomePage(),
      debugShowMaterialGrid: true, // 显示网格
      showPerformanceOverlay: true, // 显示性能层
    ),
  );
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: Text('Hello, Debug Paint!'),
      ),
    );
  }
}

Flutter Inspector

Flutter Inspector 是一个强大的调试工具,它允许你在 DevTools 中查看和修改代码的布局和状态。你可以在 DevTools 中选择组件,并查看其属性和子组件。

通过这些界面调试技巧,你可以更方便地理解和调试 Flutter 应用的界面。接下来,我们将探讨性能优化建议。

性能优化建议

在 Flutter 中,性能优化是一个重要的开发任务。以下是一些性能优化的建议:

尽量减少状态更新

状态更新会导致组件重新构建,这可能会影响性能。尽量减少不必要的状态更新,例如使用 StatefulWidgetState 类来管理状态,而不是在每个构建周期中重新创建状态。

使用 const 关键字

对于不会改变的组件,使用 const 关键字可以提高性能。const 关键字告诉编译器这些组件在运行时不会改变,因此可以缓存它们。

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const Text('Hello, World!');
  }
}

使用 Sliver 布局

对于具有大量数据的列表,使用 Sliver 布局可以提高性能。Sliver 布局可以提高列表的滚动性能,并减少不必要的布局计算。

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: CustomScrollView(
        slivers: <Widget>[
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                return ListTile(
                  title: Text('Item $index'),
                );
              },
              childCount: 100,
            ),
          ),
        ],
      ),
    );
  }
}

懒加载和分页加载

对于大数据集合,使用懒加载和分页加载可以提高性能。懒加载只在需要时加载数据,分页加载则按需加载数据。

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<String> data;
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    data = List.generate(10, (index) => 'Item $index');
    _fetchData();
  }

  _fetchData() async {
    if (_isLoading) return;
    _isLoading = true;
    final response = await http.get(Uri.parse('https://example.com/api/data'));
    if (response.statusCode == 200) {
      setState(() {
        data.addAll(json.decode(response.body));
      });
    }
    _isLoading = false;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: ListView.builder(
        itemCount: data.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(data[index]),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _fetchData,
        tooltip: 'Fetch more data',
        child: Icon(Icons.add),
      ),
    );
  }
}

使用 FutureBuilderStreamBuilder

对于异步操作,使用 FutureBuilderStreamBuilder 可以提高性能。这些组件可以在异步操作完成时自动更新 UI。

减少不必要的布局计算

尝试减少不必要的布局计算,例如避免在 build 方法中进行复杂的计算,使用 LayoutBuilderMediaQuery 来获取屏幕尺寸等。

通过这些性能优化建议,你可以提高 Flutter 应用的性能,提供更好的用户体验。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消