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

Flutter实践问题及经验汇总

  • Flutter
  • 2024-11-21
  • 380人已阅读
摘要

Flutter实践问题及经验汇总


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。





















Top