您现在的位置是:网站首页> Flutter

Flutter实践问题及经验汇总

  • Flutter
  • 2025-06-28
  • 874人已阅读
摘要

Flutter实践问题及经验汇总


重点问题汇总

创建一个Flutter项目后,打开MainActivity 出现Can't resolve symbol FlutterActivity 错误


其他问题

Flutter中使用第三方库

Flutter执行梳理和关键点整理

关于Flutter web字体下载出错

Flutter dart里根据不同的平台执行不同的代码

Flutter界面库

Flutter 的UI组件有哪些

Flutter所有布局组件的详细介绍及例子

Flutter发票图片寻边实现

Flutter列表的例子,每一项包含图片、图片说明以及删除、编辑和添加功能

Flutter的GridView.builder详细介绍下并给出例子




Flutter执行梳理和关键点整理

Dart基本语法

flutter如何判断平台执行不同的代码

执行过程

Flutter弹出新页面并获得新页面的返回值的例子

Flutter使用线程并使用线程同步的例子




Flutter中使用第三方库

1.在pubspec.yaml文件中添加依赖

在Flutter项目的pubspec.yaml文件中,在dependencies部分添加要使用的第三方库及其版本号。例如:

dependencies:

  flutter:

    sdk: flutter

  http: ^0.13.4

这里添加了http库的依赖,版本号为0.13.4。


2.运行flutter pub get命令

在终端或命令行中,进入Flutter项目的根目录,运行以下命令来获取添加的第三方库:

flutter pub get

这将根据pubspec.yaml文件中的依赖项下载并安装相应的第三方库。


3.在代码中导入和使用第三方库

在需要使用第三方库的Dart文件中,使用import语句导入库,然后就可以使用库提供的类、函数等功能了。例如:


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


void main() async {

  var response = await http.get(Uri.parse('https://api.example.com/data'));

  print(response.body);

}

这里导入了http库,并使用其提供的get函数发送HTTP GET请求,获取响应数据。


下面是一个使用第三方库的具体例子,演示了如何使用shared_preferences库来存储和读取简单的键值对数据:

1.在pubspec.yaml文件中添加shared_preferences依赖


dependencies:

  flutter:

    sdk: flutter

  shared_preferences: ^2.0.15


2.运行flutter pub get命令安装依赖。


3.在Dart文件中导入shared_preferences库


import 'package:shared_preferences/shared_preferences.dart';


4.使用shared_preferences库存储和读取数据:


void main() async {

  // 获取SharedPreferences实例

  SharedPreferences prefs = await SharedPreferences.getInstance();


  // 存储数据

  await prefs.setString('username', 'John');

  await prefs.setInt('age', 25);


  // 读取数据

  String username = prefs.getString('username') ?? '';

  int age = prefs.getInt('age') ?? 0;


  print('Username: $username');

  print('Age: $age');

}

在这个例子中,我们首先通过SharedPreferences.getInstance()获取SharedPreferences的实例。然后使用setString()和setInt()方法存储字符串和整数数据。最后,使用getString()和getInt()方法读取存储的数据,如果数据不存在,则使用空字符串和0作为默认值。

以上就是Flutter使用第三方库的方法和一个具体的例子。通过添加依赖、安装库和在代码中导入和使用库,我们可以方便地扩展Flutter应用的功能,提高开发效率。






flutter如何判断平台执行不同的代码

1. 使用 Platform 类

Flutter提供了一个 Platform 类,可以用来判断当前运行的平台。以下是一个简单的示例:

dart

import 'dart:io' show Platform;


void main() {

  if (Platform.isAndroid) {

    print('Running on Android');

  } else if (Platform.isIOS) {

    print('Running on iOS');

  } else if (Platform.isWindows) {

    print('Running on Windows');

  } else if (Platform.isLinux) {

    print('Running on Linux');

  } else if (Platform.isMacOS) {

    print('Running on macOS');

  } else {

    print('Running on an unknown platform');

  }

}


2. 使用 defaultTargetPlatform

defaultTargetPlatform 是 Flutter 提供的另一个方法,可以用来判断当前平台。以下是一个示例:

dart

import 'package:flutter/foundation.dart';


void main() {

  if (defaultTargetPlatform == TargetPlatform.android) {

    print('Running on Android');

  } else if (defaultTargetPlatform == TargetPlatform.iOS) {

    print('Running on iOS');

  } else if (defaultTargetPlatform == TargetPlatform.windows) {

    print('Running on Windows');

  } else if (defaultTargetPlatform == TargetPlatform.linux) {

    print('Running on Linux');

  } else if (defaultTargetPlatform == TargetPlatform.macOS) {

    print('Running on macOS');

  } else {

    print('Running on an unknown platform');

  }

}


3. 使用平台通道(Platform Channels)

对于需要调用平台特定的API,可以使用平台通道。以下是一个简单的示例,演示如何在Flutter中调用Android和iOS的电池电量API:

Dart代码:

dart

import 'package:flutter/services.dart';


class BatteryLevel {

  static const platform = MethodChannel('com.example.battery');


  Future<String> getBatteryLevel() async {

    try {

      final int result = await platform.invokeMethod('getBatteryLevel');

      return 'Battery level at $result % .';

    } on PlatformException catch (e) {

      return "Failed to get battery level: '${e.message}'.";

    }

  }

}


Android代码(Kotlin):

kotlin

import android.os.Bundle

import io.flutter.app.FlutterActivity

import io.flutter.plugin.common.MethodChannel


class MainActivity: FlutterActivity() {

  private val CHANNEL = "com.example.battery"


  override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)

    MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->

      if (call.method == "getBatteryLevel") {

        val batteryLevel = getBatteryLevel()


        if (batteryLevel != -1) {

          result.success(batteryLevel)

        } else {

          result.error("UNAVAILABLE", "Battery level not available.", null)

        }

      } else {

        result.notImplemented()

      }

    }

  }


  private fun getBatteryLevel(): Int {

    val batteryLevel: Int

    val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager

    batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)

    return batteryLevel

  }

}


iOS代码(Swift):

swift

import Flutter

import UIKit


@UIApplicationMain

@objc class AppDelegate: FlutterAppDelegate {

  override func application(

    _ application: UIApplication,

    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?

  ) -> Bool {

    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController

    let batteryChannel = FlutterMethodChannel(name: "com.example.battery",

                                              binaryMessenger: controller.binaryMessenger)

    batteryChannel.setMethodCallHandler({

      (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in

      if call.method == "getBatteryLevel" {

        self.receiveBatteryLevel(result: result)

      } else {

        result(FlutterMethodNotImplemented)

      }

    })


    return super.application(application, didFinishLaunchingWithOptions: launchOptions)

  }


  private func receiveBatteryLevel(result: FlutterResult) {

    let device = UIDevice.current

    device.isBatteryMonitoringEnabled = true

    if device.batteryState == UIDevice.BatteryState.unknown {

      result(FlutterError(code: "UNAVAILABLE",

                          message: "Battery level not available.",

                          details: nil))

    } else {

      result(Int(device.batteryLevel * 100))

    }

  }

}


通过这些方法,开发者可以在Flutter应用中根据不同的平台执行不同的代码,从而实现跨平台的功能。






执行过程

main函数入口,runApp启动

import 'package:flutter/material.dart';

import 'login_page.dart';


void main() {

  runApp(MyApp());

}


class MyApp extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      title: 'Login Demo',

      theme: ThemeData(

        primarySwatch: Colors.blue,

      ),

      home: LoginPage(),

    );

  }

}


LoginPage派生自StatefulWidget,里面实现产生状态类_LoginPageState,状态类传入LoginPage

import 'package:flutter/material.dart';


class LoginPage extends StatefulWidget {

  @override

  _LoginPageState createState() => _LoginPageState();

}


class _LoginPageState extends State<LoginPage> {

//这个类里有个变量widget就是LoginPage

  final _formKey = GlobalKey<FormState>();

  String _username = '';

  String _password = '';


  void _onSubmit() {

    if (_formKey.currentState!.validate()) {

      _formKey.currentState!.save();

      // 执行登录逻辑,比如发送网络请求等

      print('Username: $_username, Password: $_password');

    }

  }


  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: Text('Login'),

      ),

      body: Padding(

        padding: EdgeInsets.all(16.0),

        child: Form(

          key: _formKey,

          child: Column(

            children: [

              TextFormField(

                decoration: InputDecoration(labelText: 'Username'),

                validator: (value) {

                  if (value == null || value.isEmpty) {

                    return 'Please enter your username';

                  }

                  return null;

                },

                onSaved: (value) {

                  _username = value!;

                },

              ),

              TextFormField(

                decoration: InputDecoration(labelText: 'Password'),

                obscureText: true,

                validator: (value) {

                  if (value == null || value.isEmpty) {

                    return 'Please enter your password';

                  }

                  return null;

                },

                onSaved: (value) {

                  _password = value!;

                },

              ),

              SizedBox(height: 16.0),

              ElevatedButton(

                onPressed: _onSubmit,

                child: Text('Login'),

              ),

            ],

          ),

        ),

      ),

    );

  }

}



Flutter弹出新页面并获得新页面的返回值的例子

在Flutter中,你可以使用 Navigator.push() 方法弹出一个新页面,并通过 Navigator.pop() 方法返回数据给上一个页面。下面是一个示例:

1.首先,创建一个新页面 NewPage,并在其中添加一个文本字段和一个按钮:


class NewPage extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    final TextEditingController _textController = TextEditingController();


    return Scaffold(

      appBar: AppBar(

        title: Text('New Page'),

      ),

      body: Column(

        children: [

          TextField(

            controller: _textController,

            decoration: InputDecoration(

              labelText: 'Enter some text',

            ),

          ),

          ElevatedButton(

            onPressed: () {

              Navigator.pop(context, _textController.text);

            },

            child: Text('Return Data'),

          ),

        ],

      ),

    );

  }

}


2.在主页面中,添加一个按钮来打开新页面,并等待新页面返回数据:


class HomePage extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: Text('Home Page'),

      ),

      body: Center(

        child: ElevatedButton(

          onPressed: () async {

            final result = await Navigator.push(

              context,

              MaterialPageRoute(builder: (context) => NewPage()),

            );

            ScaffoldMessenger.of(context).showSnackBar(

              SnackBar(content: Text('Returned data: $result')),

            );

          },

          child: Text('Open New Page'),

        ),

      ),

    );

  }

}

在这个示例中:


在新页面 NewPage 中,我们创建了一个文本字段和一个按钮。当按钮被点击时,我们使用 Navigator.pop() 方法返回文本字段的内容给上一个页面。

在主页面 HomePage 中,我们添加了一个按钮来打开新页面。当按钮被点击时,我们使用 Navigator.push() 方法打开新页面,并等待新页面返回数据。

在新页面返回数据后,我们使用 ScaffoldMessenger 显示一个 SnackBar,以显示返回的数据。

通过这种方式,你可以在Flutter中弹出一个新页面,并获取新页面返回的数据。当新页面使用 Navigator.pop() 方法返回数据时,主页面可以通过 await 关键字等待并接收返回的数据



Flutter使用线程并使用线程同步的例子

Flutter 本身是单线程的,但它提供了 Isolate 机制来实现并发。以下是一个使用 Isolate 和线程同步的例子:

dart

import 'dart:isolate';

import 'package:flutter/foundation.dart';


void main() async {

  // 创建一个 ReceivePort 来接收消息

  final receivePort = ReceivePort();


  // 启动一个新的 Isolate

  await Isolate.spawn(heavyComputation, receivePort.sendPort);


  // 等待并打印结果

  final result = await receivePort.first;

  print('计算结果: $result');

}


// 在新的 Isolate 中执行的函数

void heavyComputation(SendPort sendPort) {

  int result = 0;

  

  // 模拟耗时计算

  for (int i = 0; i < 1000000000; i++) {

    result += i;

  }


  // 计算完成后,将结果发送回主 Isolate

  sendPort.send(result);

}


这个例子展示了如何:

使用 Isolate.spawn() 创建一个新的 Isolate 来执行耗时的计算任务.

使用 ReceivePort 和 SendPort 在主 Isolate 和新 Isolate 之间进行通信.

在主 Isolate 中等待计算结果并打印.


对于线程同步,Flutter 提供了一些机制:

Future 和 async/await: 用于处理异步操作.

Completer: 可以用来创建自定义的 Future.

Stream: 用于处理异步事件序列.

Mutex (在 synchronized 包中): 用于确保某段代码在同一时间只能被一个 Isolate 执行.

以下是一个使用 Mutex 的例子:

dart

import 'package:synchronized/synchronized.dart';


class SharedResource {

  final _lock = Lock();

  int _value = 0;


  Future<void> increment() async {

    await _lock.synchronized(() async {

      // 模拟一些耗时操作

      await Future.delayed(Duration(milliseconds: 100));

      _value++;

      print('当前值: $_value');

    });

  }

}


void main() async {

  final resource = SharedResource();

  

  // 模拟多个并发操作

  await Future.wait([

    resource.increment(),

    resource.increment(),

    resource.increment(),

  ]);

}


这个例子展示了如何使用 Lock 来确保 increment 方法在同一时间只能被一个调用者执行,从而避免竞态条件.

需要注意的是,虽然 Isolate 提供了并发能力,但它们之间不共享内存,通信只能通过消息传递。这种设计可以避免许多常见的并发问题,但也意味着在使用时需要特别注意数据的序列化和反序列化.



一个使用 Mutex 在 Flutter 中实现同步的例子:

import 'dart:async';

import 'package:flutter/material.dart';

import 'package:synchronized/synchronized.dart';


void main() {

  runApp(MyApp());

}


class MyApp extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      title: 'Mutex Example',

      home: HomePage(),

    );

  }

}


class HomePage extends StatefulWidget {

  @override

  _HomePageState createState() => _HomePageState();

}


class _HomePageState extends State<HomePage> {

  int _counter = 0;

  final _mutex = Lock();

  Future<void> _incrementCounter() async {

    await _mutex.synchronized(() async {

      // 模拟一些异步操作

      await Future.delayed(Duration(seconds: 1));

      setState(() {

        _counter++;

      });

    });

  }


  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: Text('Mutex Example'),

      ),

      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),

      ),

    );

  }

}

在这个例子中,我们使用了 synchronized 包中的 Lock 类来创建一个 Mutex 对象 _mutex。


在 _incrementCounter 方法中,我们使用 _mutex.synchronized 方法来包装需要同步的代码块。在这个代码块中,我们模拟了一些异步操作(使用 Future.delayed)并更新了计数器的值。

通过使用 Mutex,我们确保了在异步操作完成之前,其他并发调用 _incrementCounter 方法的代码不会进入同步代码块,从而避免了竞态条件和不一致的状态。

当你点击浮动操作按钮时,它会调用 _incrementCounter 方法,并在异步操作完成后更新计数器的值。由于使用了 Mutex,即使在快速连续点击按钮的情况下,计数器的值也会正确地递增,而不会出现竞态条件。

请注意,你需要将 synchronized 包添加到你的 pubspec.yaml 文件中,并运行 flutter pub get 来安装依赖项。

这就是一个在 Flutter 中使用 Mutex 实现同步的简单例子。Mutex 可以帮助你管理共享资源的访问,避免并发访问导致的问题。





关于Flutter web字体下载出错

修改项目目录下的pubspec.yaml文件添加字定义字体如

  assets:

    - assets/fonts/

  fonts:

   - family: CustomFont

     fonts:

       - asset: assets/fonts/site.ttf



1.png

代码修改

children: <Widget>[

            const Text(

              'You have pushed the button this many times:',

              style:  TextStyle(

                fontFamily: 'CustomFont',

              ),

            ),

            Text(

              '$_counter',

               style: const TextStyle(

                fontFamily: 'CustomFont',

              ),


            ),

          ],

点击下载例子

1.png

pubspec.yaml:

2.png

main.dart

3.png



Flutter dart里根据不同的平台执行不同的代码

使用 dart:io 库


import 'dart:io';


void executeBasedOnPlatform() {

  if (Platform.isAndroid) {

    // 执行 Android 特定的代码

    print("执行 Android 平台的代码");

  } else if (Platform.isIOS) {

    // 执行 iOS 特定的代码

    print("执行 iOS 平台的代码");

  } else if (Platform.isWindows) {

    // 执行 Windows 特定的代码

    print("执行 Windows 平台的代码");

  } else if (Platform.isMacOS) {

    // 执行 macOS 特定的代码

    print("执行 macOS 平台的代码");

  } else if (Platform.isLinux) {

    // 执行 Linux 特定的代码

    print("执行 Linux 平台的代码");

  }

}



使用 flutter/foundation.dart 库

对于 Flutter 特有的平台(如 Flutter Web),你可以使用 flutter/foundation.dart 库中的 kIsWeb 常量来判断是否是 Web 平台:


import 'package:flutter/foundation.dart' show kIsWeb;


void executeBasedOnPlatform() {

  if (kIsWeb) {

    // 执行 Web 特定的代码

    print("执行 Web 平台的代码");

  } else {

    // 执行非 Web 平台的代码

    print("执行非 Web 平台的代码");

  }

}



组合使用

在实际项目中,你可能需要根据多种情况判断并执行不同的代码。这时,你可以组合使用上述两种方法来实现更复杂的平台判断逻辑:


import 'dart:io';

import 'package:flutter/foundation.dart' show kIsWeb;


void executeBasedOnPlatform() {

  if (kIsWeb) {

    // 执行 Web 特定的代码

    print("执行 Web 平台的代码");

  } else if (Platform.isAndroid) {

    // 执行 Android 特定的代码

    print("执行 Android 平台的代码");

  } else if (Platform.isIOS) {

    // 执行 iOS 特定的代码

    print("执行 iOS 平台的代码");

  } else if (Platform.isWindows) {

    // 执行 Windows 特定的代码

    print("执行 Windows 平台的代码");

  } else if (Platform.isMacOS) {

    // 执行 macOS 特定的代码

    print("执行 macOS 平台的代码");

  } else if (Platform.isLinux) {

    // 执行 Linux 特定的代码

    print("执行 Linux 平台的代码");

  }

}



Flutter界面库

import  'package:flutter/material.dart'

void main(){

runApp(

 MaterialApp(

 title:'静态变量使用例子',

 home:MyApp()

)

)


class MyApp extends StatelessWidget{

@override

Widget build(BuildContent content){

return Scaffold(

appBar:AppBar(

title:Text(KString.mainTitle),

),

body:Center(

child:Text(KString.homeTitle,

         style:TextStyle(fontSize:28.0),

)

)

}

}


}


class KString{

static const String mainTitle="Flutter商城";

static const String homeTitle="首页";

}



Flutter所有布局组件的详细介绍及例子

Container

描述: 一个多功能的布局组件,可以添加padding、margin、边框等。

示例:

Container(

  margin: EdgeInsets.all(10),

  padding: EdgeInsets.all(8),

  decoration: BoxDecoration(

    color: Colors.blue,

    borderRadius: BorderRadius.circular(4),

  ),

  child: Text('Hello'),

)



Row

描述: 水平排列子组件。

示例:

Row(

  mainAxisAlignment: MainAxisAlignment.spaceEvenly,

  children: <Widget>[

    Icon(Icons.star),

    Icon(Icons.star),

    Icon(Icons.star),

  ],

)


Column

描述: 垂直排列子组件。

示例:

Column(

  crossAxisAlignment: CrossAxisAlignment.start,

  children: <Widget>[

    Text('Title'),

    Text('Subtitle'),

    Text('Body'),

  ],

)



Stack

描述: 允许子组件堆叠。

示例:

Stack(

  children: <Widget>[

    Container(color: Colors.yellow, width: 300, height: 300),

    Positioned(

      right: 20,

      top: 20,

      child: Icon(Icons.star, size: 50),

    ),

  ],

)


Expanded

描述: 在Row、Column或Flex中占用剩余空间。

示例:

Row(

  children: <Widget>[

    Container(width: 100, color: Colors.red),

    Expanded(

      child: Container(color: Colors.blue),

    ),

  ],

)


Flexible

描述: 类似Expanded,但可以设置flex参数控制占比。

示例:

Row(

  children: <Widget>[

    Flexible(

      flex: 2,

      child: Container(color: Colors.red),

    ),

    Flexible(

      flex: 1,

      child: Container(color: Colors.blue),

    ),

  ],

)


Wrap

描述: 当内容超出一行时自动换行。

示例:

Wrap(

  spacing: 8.0,

  runSpacing: 4.0,

  children: <Widget>[

    Chip(label: Text('Hamilton')),

    Chip(label: Text('Lafayette')),

    Chip(label: Text('Mulligan')),

    Chip(label: Text('Laurens')),

  ],

)


ListView

描述: 可滚动的列表。

示例:

ListView(

  children: <Widget>[

    ListTile(title: Text('Item 1')),

    ListTile(title: Text('Item 2')),

    ListTile(title: Text('Item 3')),

  ],

)


GridView

描述: 网格布局。

示例:

GridView.count(

  crossAxisCount: 2,

  children: List.generate(4, (index) {

    return Center(

      child: Text('Item $index'),

    );

  }),

)


SizedBox

描述: 指定固定大小的盒子。

示例:

SizedBox(

  width: 200,

  height: 100,

  child: Card(

    child: Text('Fixed Size Box'),

  ),

)


Padding

描述: 为其子组件添加内边距。

示例:

Padding(

  padding: EdgeInsets.all(16.0),

  child: Text('Hello, World!'),

)



Center

描述: 将子组件居中。

示例:

Center(

  child: Text('Centered Text'),

)


Align

描述: 根据设置的alignment参数对齐子组件。

示例:

Align(

  alignment: Alignment.topRight,

  child: Text('Top Right'),

)

这些是Flutter中最常用的布局组件。每个组件都有其特定的用途和属性,可以组合使用以创建复杂的布局。在实际开发中,你会经常使用这些组件的组合来构建用户界面。









Flutter 的UI组件有哪些

Flutter 提供了丰富的 UI 组件,可以帮助开发者快速构建美观、高性能的用户界面。以下是 Flutter 中常用的一些 UI 组件:


基础组件

Text:文本组件,用于显示文字内容。

Image:图片组件,用于显示图片。

Icon:图标组件,用于显示图标。

Button:按钮组件,包括 ElevatedButton、TextButton、OutlinedButton 等。


布局组件

Container:容器组件,可以设置背景色、边框、内边距等属性。

Row:行组件,用于水平排列子组件。

Column:列组件,用于垂直排列子组件。

Stack:堆叠组件,用于将子组件堆叠在一起。

Expanded:扩展组件,用于控制子组件在父组件中的尺寸。

Flexible

Flexible 组件与 Expanded 类似,用于控制子组件在 Row 或 Column 中的尺寸。

通过设置 Flexible 的 fit 属性,可以控制子组件在有限空间内的布局方式。

Flexible 组件常用于创建自适应的布局,如根据内容自动调整大小的文本框等。

Wrap

Wrap 组件用于在水平或垂直方向上依次排列子组件,当空间不足时会自动换行。

可以通过 direction、alignment、spacing 等属性来控制 Wrap 的布局行为。

Wrap 组件常用于创建流式布局,如标签云、动态网格等。




滚动组件

SingleChildScrollView:单子组件滚动视图,用于包裹单个可滚动的子组件。

ListView:列表组件,用于显示可滚动的列表。

GridView:网格组件,用于显示可滚动的网格列表。

CustomScrollView:自定义滚动视图,可以组合多个滚动组件。


表单组件

TextField:文本输入框组件,用于接收用户输入的文本。

Checkbox:复选框组件,用于多选。

Radio:单选框组件,用于单选。

Switch:开关组件,用于切换状态。

Slider:滑块组件,用于在指定范围内选择值。


对话框和弹出框

AlertDialog:警告对话框,用于显示警告信息。

SimpleDialog:简单对话框,用于显示简单的信息或选项。

BottomSheet:底部弹出框,从屏幕底部弹出的对话框。

SnackBar:消息提示框,在屏幕底部显示短暂的消息提示。


导航组件

Scaffold:脚手架组件,提供基本的页面结构,包括顶部应用栏、底部导航栏等。

AppBar:应用栏组件,通常放置在 Scaffold 的顶部,用于显示页面标题、导航按钮等。

TabBar 和 TabBarView:选项卡组件,用于实现页面的选项卡切换。

Drawer:抽屉组件,从左侧滑出的导航菜单。


其他组件

GestureDetector:手势检测器,用于处理各种手势事件,如点击、双击、拖动等。

AnimatedContainer:动画容器组件,可以实现容器属性的平滑过渡动画。

FutureBuilder 和 StreamBuilder:异步构建器组件,用于根据异步数据构建界面。

这只是 Flutter 中 UI 组件的一部分,Flutter 还提供了许多其他的组件和自定义组件的能力,可以满足各种复杂的 UI 设计需求。你可以参考 Flutter 的官方文档和示例来进一步了解和使用这些组件。



Flutter发票图片寻边实现

在 Flutter 中实现发票图片寻边可以使用图像处理库,如 OpenCV。以下是一个使用 OpenCV 实现发票图片寻边的示例:


添加依赖:在 pubspec.yaml 文件中添加 opencv 依赖:


dependencies:

  opencv: ^1.0.0

  image_picker: ^0.8.7+5 # 请确保使用最新的版本

导入必要的包:


import 'package:flutter/material.dart';

import 'package:image_picker/image_picker.dart';

import 'package:opencv/opencv.dart';

选择图片:


final picker = ImagePicker();

final pickedFile = await picker.getImage(source: ImageSource.gallery);

if (pickedFile != null) {

  final file = File(pickedFile.path);

  // 处理选择的图片

}

使用 OpenCV 进行图像处理:


final bytes = await file.readAsBytes();

final mat = await ImgProc.imdecode(bytes, ImgProc.IMREAD_GRAYSCALE);


// 高斯模糊

final blurredMat = await ImgProc.gaussianBlur(

  mat,

  Size(5, 5),

  0,

);


// Canny 边缘检测

final edges = await ImgProc.canny(

  blurredMat,

  50,

  150,

);


// 寻找轮廓

final contours = await ImgProc.findContours(

  edges,

  ImgProc.RETR_EXTERNAL,

  ImgProc.CHAIN_APPROX_SIMPLE,

);


// 找到最大的轮廓

final maxContour = contours.fold<List<Point>>(null, (prev, current) {

  if (prev == null || ImgProc.contourArea(current) > ImgProc.contourArea(prev)) {

    return current;

  }

  return prev;

});


// 绘制轮廓

final color = Colors.red;

final contourMat = await mat.clone();

await ImgProc.drawContours(

  contourMat,

  [maxContour],

  -1,

  color,

  2,

);


// 将处理后的图像转换为 Flutter 图像

final processedBytes = await ImgProc.imencode('.jpg', contourMat);

final processedImage = Image.memory(processedBytes);

显示处理后的图像:


Image(

  image: processedImage,

  fit: BoxFit.contain,

)

以上示例中,我们首先选择一张发票图片,然后使用 OpenCV 对图像进行处理。具体步骤包括:


将图像转换为灰度图像。

对图像进行高斯模糊,以减少噪声。

使用 Canny 算法进行边缘检测。

寻找图像中的轮廓。

找到最大的轮廓,假设它是发票的边框。

在原始图像上绘制最大轮廓。

将处理后的图像转换为 Flutter 图像并显示。

请注意,以上示例仅提供了一个基本的发票图片寻边实现。实际应用中,你可能需要进行更多的图像预处理和后处理,以获得更好的结果。



Flutter列表的例子,每一项包含图片、图片说明以及删除、编辑和添加功能

import 'package:flutter/material.dart';


void main() {

  runApp(MyApp());

}


class MyApp extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      title: 'Flutter List Example',

      theme: ThemeData(

        primarySwatch: Colors.blue,

      ),

      home: MyHomePage(),

    );

  }

}


class MyHomePage extends StatefulWidget {

  @override

  _MyHomePageState createState() => _MyHomePageState();

}


class _MyHomePageState extends State<MyHomePage> {

  List<Map<String, dynamic>> items = [

    {'image': 'assets/image1.jpg', 'description': 'Image 1'},

    {'image': 'assets/image2.jpg', 'description': 'Image 2'},

    {'image': 'assets/image3.jpg', 'description': 'Image 3'},

  ];


  void _addItem() {

    setState(() {

      items.add({'image': 'assets/image4.jpg', 'description': 'New Image'});

    });

  }


  void _editItem(int index) {

    // 实现编辑功能

  }


  void _deleteItem(int index) {

    setState(() {

      items.removeAt(index);

    });

  }


  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: Text('Flutter List Example'),

      ),

      body: ListView.builder(

        itemCount: items.length,

        itemBuilder: (context, index) {

          return ListTile(

            leading: Image.asset(items[index]['image']),

            title: Text(items[index]['description']),

            trailing: Row(

              mainAxisSize: MainAxisSize.min,

              children: [

                IconButton(

                  icon: Icon(Icons.edit),

                  onPressed: () => _editItem(index),

                ),

                IconButton(

                  icon: Icon(Icons.delete),

                  onPressed: () => _deleteItem(index),

                ),

              ],

            ),

          );

        },

      ),

      floatingActionButton: FloatingActionButton(

        onPressed: _addItem,

        child: Icon(Icons.add),

      ),

    );

  }

}

在这个例子中,我们创建了一个MyHomePage类作为主页面,它继承自StatefulWidget。

在_MyHomePageState类中,我们定义了一个items列表,用于存储列表项的数据。每个列表项是一个包含image和description的Map。

我们使用ListView.builder构建列表,并在itemBuilder中为每个列表项创建一个ListTile。在ListTile中,我们使用Image.asset显示图片,并使用Text显示图片说明。同时,我们在trailing属性中添加了编辑和删除按钮。

点击编辑按钮会调用_editItem方法,传入当前列表项的索引。你可以在这个方法中实现编辑功能。

点击删除按钮会调用_deleteItem方法,传入当前列表项的索引。在这个方法中,我们使用removeAt方法从items列表中删除对应的项,并调用setState方法更新UI。

最后,我们添加了一个浮动操作按钮(FAB),点击它会调用_addItem方法,在该方法中,我们向items列表中添加一个新的项,并调用setState方法更新UI。


请注意,在上述例子中,我们还可以进一步优化和扩展功能。以下是一些建议:

使用自定义类来表示列表项数据:

我们可以创建一个单独的类来表示列表项的数据,而不是使用Map。这样可以使代码更加清晰和易于维护。例如:


class ListItem {

  final String image;

  final String description;


  ListItem({required this.image, required this.description});

}

然后,我们可以将items列表的类型更改为List<ListItem>。

实现编辑功能:

在_editItem方法中,我们可以弹出一个对话框或导航到一个新的页面,以允许用户编辑列表项的图片和描述。编辑完成后,我们可以更新items列表中对应项的数据,并调用setState方法更新UI。

持久化数据:

当前的例子中,列表项的数据是存储在内存中的,当应用程序关闭时,数据会丢失。我们可以使用数据持久化的方法,如SharedPreferences或SQLite,将数据保存到设备的存储中,以便在应用程序重新打开时恢复数据。

添加加载状态和空状态:

当列表项数据从网络或数据库中加载时,我们可以显示一个加载状态,如进度指示器或骨架屏幕,以提供更好的用户体验。同时,当列表为空时,我们可以显示一个空状态的视图,提示用户列表为空,并引导用户添加新的项。

使用动画和过渡效果:

为了增强用户体验,我们可以在列表项的添加、删除和编辑操作中使用动画和过渡效果。例如,在添加新项时使用渐变效果,在删除项时使用滑动删除效果等。Flutter提供了丰富的动画和过渡效果的API,如AnimatedList、SlideTransition等。

以上是一些可以进一步优化和扩展Flutter列表例子的建议。根据实际需求和应用场景,你可以选择适合的功能和改进方向。



编辑删除操作改成该项侧滑时出现

我们可以使用Flutter的Dismissible组件来实现列表项的侧滑操作,在侧滑时显示编辑和删除按钮。以下是修改后的代码:

import 'package:flutter/material.dart';


void main() {

  runApp(MyApp());

}


class MyApp extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      title: 'Flutter List Example',

      theme: ThemeData(

        primarySwatch: Colors.blue,

      ),

      home: MyHomePage(),

    );

  }

}


class MyHomePage extends StatefulWidget {

  @override

  _MyHomePageState createState() => _MyHomePageState();

}


class _MyHomePageState extends State<MyHomePage> {

  List<Map<String, dynamic>> items = [

    {'image': 'assets/image1.jpg', 'description': 'Image 1'},

    {'image': 'assets/image2.jpg', 'description': 'Image 2'},

    {'image': 'assets/image3.jpg', 'description': 'Image 3'},

  ];


  void _addItem() {

    setState(() {

      items.add({'image': 'assets/image4.jpg', 'description': 'New Image'});

    });

  }


  void _editItem(int index) {

    // 实现编辑功能

  }


  void _deleteItem(int index) {

    setState(() {

      items.removeAt(index);

    });

  }


  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: Text('Flutter List Example'),

      ),

      body: ListView.builder(

        itemCount: items.length,

        itemBuilder: (context, index) {

          return Dismissible(

            key: Key(items[index]['description']),

            direction: DismissDirection.endToStart,

            onDismissed: (direction) {

              _deleteItem(index);

            },

            background: Container(

              alignment: Alignment.centerRight,

              padding: EdgeInsets.only(right: 20.0),

              color: Colors.red,

              child: Icon(Icons.delete, color: Colors.white),

            ),

            confirmDismiss: (direction) async {

              return await showDialog(

                context: context,

                builder: (BuildContext context) {

                  return AlertDialog(

                    title: Text("确认删除"),

                    content: Text("你确定要删除这个项吗?"),

                    actions: <Widget>[

                      TextButton(

                        child: Text("取消"),

                        onPressed: () {

                          Navigator.of(context).pop(false);

                        },

                      ),

                      TextButton(

                        child: Text("删除"),

                        onPressed: () {

                          Navigator.of(context).pop(true);

                        },

                      ),

                    ],

                  );

                },

              );

            },

            child: ListTile(

              leading: Image.asset(items[index]['image']),

              title: Text(items[index]['description']),

              trailing: IconButton(

                icon: Icon(Icons.edit),

                onPressed: () => _editItem(index),

              ),

            ),

          );

        },

      ),

      floatingActionButton: FloatingActionButton(

        onPressed: _addItem,

        child: Icon(Icons.add),

      ),

    );

  }

}

在修改后的代码中,我们使用Dismissible组件包裹每个列表项的ListTile。Dismissible组件允许我们通过侧滑手势来触发操作。


我们设置direction属性为DismissDirection.endToStart,表示只允许从右向左滑动。

在onDismissed回调中,我们调用_deleteItem方法来删除对应的列表项。

我们使用background属性来设置侧滑时显示的背景,这里我们设置了一个红色背景,并在右侧显示一个删除图标。

为了增加删除操作的确认步骤,我们使用confirmDismiss属性来显示一个确认对话框。当用户侧滑列表项时,会弹出一个对话框,询问用户是否确认删除。如果用户点击"删除"按钮,我们返回true,表示确认删除操作;如果用户点击"取消"按钮,我们返回false,表示取消删除操作。

对于编辑操作,我们将编辑按钮移动到ListTile的trailing属性中,当用户点击编辑按钮时,会调用_editItem方法,并传入对应的列表项索引。

这样修改后,列表项的删除操作就变成了侧滑触发,并且在删除前会显示一个确认对话框。编辑操作则通过点击列表项右侧的编辑按钮来触发。


你可以根据需要进一步自定义侧滑操作的样式和行为,例如添加更多的操作按钮、自定义滑动阈值等。

希望这个修改后的例子能够满足你的需求!如果有任何其他问题,欢迎随时提出。



Flutter的GridView.builder详细介绍下并给出例子

GridView.builder是 Flutter 中用于创建网格视图(grid view)的构建器。它允许你高效地构建一个二维的可滚动网格布局,适用于展示图片、图标、商品列表等以网格形式排列的内容。与ListView.builder类似,GridView.builder也是懒加载的,只有当网格中的子项进入屏幕可视范围时才会构建,这有助于提高性能和节省资源。

参数说明:

gridDelegate:这是一个必需的参数,用于定义网格的布局方式。最常用的是SliverGridDelegateWithFixedCrossAxisCount和SliverGridDelegateWithMaxCrossAxisExtent。

SliverGridDelegateWithFixedCrossAxisCount:用于创建具有固定列数(对于垂直滚动的网格)或行数(对于水平滚动的网格)的网格布局。它的参数包括crossAxisCount(交叉轴方向上的子项数量,例如垂直滚动时的列数)、mainAxisSpacing(主轴方向上子项之间的间距)、crossAxisSpacing(交叉轴方向上子项之间的间距)和childAspectRatio(子项的宽高比)。

SliverGridDelegateWithMaxCrossAxisExtent:用于创建根据最大交叉轴范围自动计算列数或行数的网格布局。它的主要参数是maxCrossAxisExtent(交叉轴方向上子项的最大尺寸),同时也可以设置mainAxisSpacing、crossAxisSpacing和childAspectRatio。

itemCount:用于指定网格中总的子项数量,这是必需的参数。

itemBuilder:也是必需的参数,是一个构建器函数,用于为每个网格子项构建对应的组件。它接收BuildContext context和int index两个参数,其中context是构建上下文,index是当前子项的索引,通过index可以返回不同的组件来构建网格。

scrollDirection:指定网格的滚动方向,默认值是Axis.vertical(垂直滚动),也可以设置为Axis.horizontal(水平滚动)。

示例代码

以下是一个使用SliverGridDelegateWithFixedCrossAxisCount创建垂直滚动网格视图的例子,展示一个简单的包含 9 个彩色方块的网格:

import 'package:flutter/material.dart';


void main() {

  runApp(MyApp());

}


class MyApp extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      home: Scaffold(

        appBar: AppBar(

          title: Text('GridView.builder Example'),

        ),

        body: GridView.builder(

          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(

            crossAxisCount: 3,

            mainAxisSpacing: 10,

            crossAxisSpacing: 10,

            childAspectRatio: 1.0,

          ),

          itemCount: 9,

          itemBuilder: (BuildContext context, int index) {

            return Container(

              color: Colors.blue[index * 100],

              child: Center(

                child: Text('Item $index'),

              ),

            );

          },

        ),

      ),

    );

  }

}

在这个示例中:

首先导入package:flutter/material.dart,这是使用 Flutter 的 Material Design 组件所必需的。

在MyApp类的build方法中,构建了一个MaterialApp,其中home属性是一个Scaffold。Scaffold包含一个AppBar用于显示标题,和一个body部分用于显示网格视图。

在body部分的GridView.builder中:

gridDelegate使用了SliverGridDelegateWithFixedCrossAxisCount,将crossAxisCount设置为 3,表示网格有 3 列;mainAxisSpacing和crossAxisSpacing都设置为 10,这意味着在主轴(垂直方向)和交叉轴(水平方向)上,子项之间的间距都是 10;childAspectRatio设置为 1.0,表示子项的宽高比为 1:1。

itemCount设置为 9,说明网格中总共有 9 个子项。

itemBuilder函数根据索引index返回一个Container作为网格子项。每个Container的颜色根据索引index设置(这里只是简单地通过Colors.blue[index * 100]来设置不同深浅的蓝色),并且在Container内部使用Center组件将一个Text组件居中显示,文本内容为Item $index。

如果要使用SliverGridDelegateWithMaxCrossAxisExtent创建一个水平滚动的网格视图,可以参考以下示例代码


import 'package:flutter/material.dart';


void main() {

  runApp(MyApp());

}


class MyApp extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      home: Scaffold(

        appBar: AppBar(

          title: Text('Horizontal GridView.builder Example'),

        ),

        body: Container(

          height: 150,

          child: GridView.builder(

            scrollDirection: Axis.horizontal,

            gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(

              maxCrossAxisExtent: 100,

              mainAxisSpacing: 10,

              crossAxisSpacing: 10,

              childAspectRatio: 1.5,

            ),

            itemCount: 6,

            itemBuilder: (BuildContext context, int index) {

              return Container(

                decoration: BoxDecoration(

                  border: Border.all(color: Colors.black),

                ),

                child: Center(

                  child: Text('Item $index'),

                ),

              );

            },

          ),

        ),

      ),

    );

  }

}

在这个水平滚动网格视图示例中:

首先将GridView.builder放置在一个Container中,并设置了height为 150,用于限制网格视图的高度。

scrollDirection设置为Axis.horizontal,使网格可以水平滚动。

gridDelegate使用了SliverGridDelegateWithMaxCrossAxisExtent,将maxCrossAxisExtent设置为 100,表示交叉轴(垂直于滚动方向,在这里是高度方向)上子项的最大尺寸为 100;mainAxisSpacing设置为 10,crossAxisSpacing设置为 10,分别表示主轴(水平滚动方向)和交叉轴上子项之间的间距;childAspectRatio设置为 1.5,确定了子项的宽高比。

itemCount设置为 6,表示网格中有 6 个子项。

在itemBuilder函数中,返回一个带有黑色边框的Container作为网格子项,内部使用Center组件将一个Text组件居中显示,文本内容为Item $index。



创建一个Flutter项目后,打开MainActivity 出现Can't resolve symbol FlutterActivity 错误

废话不多,直接解决办法哈

删除项目中的android或ios文件夹,在项目的Terminal中运行

flutter create  .  以重新生成这些目录

but,出现一个问题,我原本想用java语言开发,使用这个命令直接生成Kotlin语言模式。。。。待解决更新

接着。。哈哈 

默认的是Kotlin语言,个人还是喜欢用java开发,使用:

flutter create -a java  .  命令即可生成java语言模式


当以下界面出现Module JDK is not时候设置一下JDK

1.png

直接以项目形式打开该Flutter项目下的Android目录

1.png

打开Flutter项目下的Android项目

1.png

Dart代码在Flutter主工程里编写,要改Android代码就在Android项目里,原生代码修改后需要在Android项目里编译

1.png

修改的 MainActivity.java代码:

package com.example.hiflutter;

import android.os.Bundle;

import android.os.Handler;

import android.app.AlertDialog;

import io.flutter.embedding.android.FlutterActivity;


public class MainActivity extends FlutterActivity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);


        // 使用 Handler 延迟显示,确保 Flutter 引擎初始化完成

        new Handler().postDelayed(new Runnable() {

            @Override

            public void run() {

                showStartupDialog();

            }

        }, 500); // 延迟 500ms

    }


    private void showStartupDialog() {

        // 在当前活动的上下文中创建对话框

        AlertDialog.Builder builder = new AlertDialog.Builder(this);

        builder.setTitle("欢迎提示")

                .setMessage("应用程序已成功启动!")

                .setPositiveButton("确定", null) // 不需要点击事件

                .setCancelable(false); // 阻止用户通过返回键关闭


        // 创建并显示对话框

        AlertDialog dialog = builder.create();

        dialog.show();

    }

}

打开虚拟机

1.png





























Top