Flutter颜色拾取插件simple_eye_dropper的使用

Flutter颜色拾取插件simple_eye_dropper的使用

Simple Eye Dropper 是一个依赖于标准库的简单颜色拾取小部件。

使用方法

使用asset图片

构建 EyeDropper 小部件的方法如下:

final byteData = await rootBundle.load('asset/example.png');

(snip)
      
EyeDropper.of(
  // 编码后的Uint8List。
  bytes: byteData.buffer.asUint8List(),
  // 显示的大小。
  size: const Size(200, 400),
  // 当选择颜色时调用的回调。
  onSelected: (color) => print('Selected color is $color'),
);
  • bytes 参数是一个通过 image_picker 等方式获得的图像字节的 Uint8List。如果 bytes 参数为 null,则会显示一个空白区域。
  • onSelected 指定一个当颜色被选择时调用的回调。

指针

选择指针实现

默认情况下,使用带有放大功能的指针:

EyeDropper.of(
  bytes: bytes,
  size: const Size(200, 400),
  // 默认情况。
  pointerBuilder: MagnifierPointer.new,
  onSelected: (color) => print('Selected color is $color'),
);

也可以指定一个简单的指针(不带放大功能):

EyeDropper.of(
  bytes: bytes,
  size: const Size(200, 400),
  // 简单的小正方形指针,不带放大功能。
  pointerBuilder: (_, __) => SimplePointer(),
  onSelected: (color) => print('Selected color is $color'),
);

还可以使用圆形放大镜指针:

EyeDropper.of(
  bytes: bytes,
  size: const Size(200, 400),
  pointerBuilder: CircularMagnifierPointer.new,
  onSelected: (color) => print('Selected color is $color'),
);

这些指针都有多个参数,可以进行一些自定义设置:

EyeDropper.of(
  bytes: bytes,
  size: const Size(200, 400),
  // 自定义指针,带有放大功能。
  pointerBuilder: (uiImage, ratio) => MagnifierPointer(
    uiImage,
    ratio,
    magnification: 2.5,
    outerRectSize: 101,
    outerStrokeWidth: 3,
    innerRectSize: 9,
    innerStrokeWidth: 3,
  ),
  onSelected: (color) => print('Selected color is $color'),
);
实现指针

你也可以通过继承 Pointer 类来创建自己的指针。参考 CircleMagnifierPointer 类的代码来实现 Pointer

图像相关

支持的图像格式和错误处理

支持的图像格式类似于 dart:ui 中的 instantiateImageCodec 函数。至少支持以下图像格式:JPEG、PNG、GIF、Animated GIF、WebP、Animated WebP、BMP 和 WBMP。

当传递一个不受支持的图像格式的 Uint8List 给 bytes 时,将调用 errorBuildererrorBuilder 的使用方式与 Image.errorBuilder 相同。默认情况下,会显示一个灰色的错误图标。如果传递 nullbytes,则会显示一个空白区域。

使用 image_picker

例如,你可以使用 image_picker 来选择图片:

final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.gallery);
if(image == null) {
  return;
}
final bytes = await image.readAsBytes();

(snip)
    
EyeDropper.of(
  bytes: bytes,
  size: const Size(200, 400),
  onSelected: (color) => print('Selected color is $color'),
);

在实际应用中,你可能会使用 FutureBuilder 来支持异步操作。具体示例请参见 example/lib/main.dart

使用网络图片

例如,如果你使用 http 包,可以这样做:

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

(snip)

final response = await http.get(Uri.parse('https://example.org/sample.jpg'));

(snip)

EyeDropper.of(
  bytes: response.bodyBytes,
  size: const Size(200, 400),
  onSelected: (color) => print('Selected color is $color'),
);

只需将响应体直接传递给 bytes

使用Dart Image Library

如果你想传递一个经过 image(Dart Image Library)处理过的图像到 EyeDropper,可以按如下方式传递一个重新编码的 Uint8List:

import 'package:image/image.dart' as img;

(snip)

final imgImage = img.decodeImage(bytes);
final grayImage = img.grayscale(imgImage!);
grayBytes = img.encodeJpg(grayImage);

(snip)

EyeDropper.of(
  bytes: grayBytes,
  size: const Size(200, 400),
  onSelected: (color) => print('Selected color is $color'),
);

使用 Riverpod

EyeDropperRiverpodConsumerWidgetConsumerStatefulWidget 一起使用时,由于指针会被完全重绘,可能会导致指针无法正确显示。

// 不好的例子。
import 'package:flutter_riverpod/flutter_riverpod.dart';

final colorProvider = StateProvider<Color>((ref) => Colors.white);

(snip)

class MyHomePage extends ConsumerWidget {

(snip)

  // 显示颜色代码。
  Text(ref.watch(colorProvider).toString()),

(snip)

}

在这种情况下,不要直接使用 ConsumerWidgetConsumerStatefulWidgetref,而是使用 Consumer 来指定重绘范围。

// 好的例子。
import 'package:flutter_riverpod/flutter_riverpod.dart';

final colorProvider = StateProvider<Color>((ref) => Colors.white);

class MyHomePage extends ConsumerWidget {

(snip)

  // 显示颜色代码。
  Consumer(
    builder: (_, ref, __) {
      return Text(ref.watch(colorProvider).toString());
    },
  ),

(snip)

}

完整示例

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';
import 'package:simple_eye_dropper/simple_eye_dropper.dart';

/// 示例:Simple Eye Dropper 小部件。
///
/// 在iOS上使用时,需要在info.plist中添加以下设置:
/// <key>NSPhotoLibraryUsageDescription</key>
/// <string>此应用需要访问照片库。</string>
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  /// 图像显示区域占整个屏幕的比例。
  static const imageAreaWidthRatio = 0.95;
  static const imageAreaHeightRatio = 0.65;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Eye Dropper',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Builder(
        builder: (context) {
          // 图像的尺寸。
          final screenSize = MediaQuery.of(context).size;
          final imageAreaSize = Size(
            screenSize.width * imageAreaWidthRatio,
            screenSize.height * imageAreaHeightRatio,
          );
          return MyHomePage(title: 'Eye Dropper', imageAreaSize: imageAreaSize);
        },
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({super.key, required this.title, required this.imageAreaSize});

  final String title;
  final Size imageAreaSize;
  final ValueNotifier<Uint8List?> _bytes = ValueNotifier(null);
  final ValueNotifier<Color> _color = ValueNotifier(Colors.white);

  [@override](/user/override)
  Widget build(BuildContext context) {
    // 如果发生错误,显示 SnackBar。
    Object? vError;
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (vError != null) {
        final snackBar = SnackBar(
          behavior: SnackBarBehavior.floating,
          content: Text('$vError'),
        );
        ScaffoldMessenger.of(context).showSnackBar(snackBar);
      }
    });
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 提取的颜色部分。
            ValueListenableBuilder(
              valueListenable: _color,
              builder: (_, color, __) {
                return Column(
                  children: [
                    // 选中颜色的样本。
                    CustomPaint(
                      size: const Size(50, 50),
                      painter: PickedPainter(color),
                    ),
                    // 选中颜色的十六进制三元组。
                    Text(color.hexTriplet()),
                  ],
                );
              },
            ),
            // 从图像中提取颜色的部分。
            ValueListenableBuilder(
              valueListenable: _bytes,
              builder: (_, bytes, __) {
                // Eye dropper 实例化。
                return EyeDropper.of(
                  bytes: bytes, // 原始图像字节。
                  size: imageAreaSize,
                  errorBuilder: (_, error, stackTrace) {
                    // print('$stackTrace');
                    vError = error;
                    return const Icon(
                      Icons.error,
                      color: Colors.black54,
                    );
                  },
                  // 颜色选择时的回调。
                  onSelected: (color) => _color.value = color,
                );
              },
            ),
            ImagePickerButton(
              onSelected: (bytes) => _bytes.value = bytes,
            ),
          ],
        ),
      ),
    );
  }
}

/// 用于从相册中选择图像的按钮。
class ImagePickerButton extends StatelessWidget {
  const ImagePickerButton({super.key, required this.onSelected});

  /// 图像选择时的回调。
  final ValueChanged<Uint8List> onSelected;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: selectImage,
      child: const Text('Select Image'),
    );
  }

  Future<void> selectImage() async {
    final picker = ImagePicker();
    final image = await picker.pickImage(source: ImageSource.gallery);
    if (image == null) {
      return;
    }
    final bytes = await image.readAsBytes();
    onSelected(bytes);
  }
}

/// 显示指定颜色。
[@immutable](/user/immutable)
class PickedPainter extends CustomPainter {
  const PickedPainter(this.color);

  static const double rectSize = 50;
  final Color color;

  [@override](/user/override)
  void paint(Canvas canvas, Size size) {
    final paint = Paint();
    const rect = Rect.fromLTWH(0, 0, rectSize, rectSize);
    paint
      ..color = color
      ..style = PaintingStyle.fill;
    canvas.drawRect(rect, paint);
  }

  [@override](/user/override)
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

/// 显示十六进制三元组,如#FFFFFF。
extension HexTriplet on Color {
  String hexTriplet() {
    return '#${value.toRadixString(16).padLeft(8, '0').substring(2, 8).toUpperCase()}';
  }
}

更多关于Flutter颜色拾取插件simple_eye_dropper的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter颜色拾取插件simple_eye_dropper的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


simple_eye_dropper 是一个用于 Flutter 的简单颜色拾取插件。它允许用户从屏幕上选择颜色,并将其返回给应用程序。以下是如何在 Flutter 项目中使用 simple_eye_dropper 插件的步骤。

1. 添加依赖

首先,在 pubspec.yaml 文件中添加 simple_eye_dropper 依赖:

dependencies:
  flutter:
    sdk: flutter
  simple_eye_dropper: ^1.0.0  # 请使用最新版本

然后运行 flutter pub get 来获取依赖。

2. 导入包

在需要使用颜色拾取功能的 Dart 文件中导入 simple_eye_dropper 包:

import 'package:simple_eye_dropper/simple_eye_dropper.dart';

3. 使用颜色拾取器

你可以通过调用 SimpleEyeDropper.pickColor() 方法来启动颜色拾取器。该方法将返回一个 Future<Color?>,表示用户选择的颜色。

以下是一个简单的示例,展示如何在按钮点击时启动颜色拾取器,并将选择的颜色应用到某个 Widget 的背景色上:

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

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

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ColorPickerDemo(),
    );
  }
}

class ColorPickerDemo extends StatefulWidget {
  [@override](/user/override)
  _ColorPickerDemoState createState() => _ColorPickerDemoState();
}

class _ColorPickerDemoState extends State<ColorPickerDemo> {
  Color? _selectedColor;

  Future<void> _pickColor() async {
    final Color? color = await SimpleEyeDropper.pickColor();
    if (color != null) {
      setState(() {
        _selectedColor = color;
      });
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Color Picker Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              width: 100,
              height: 100,
              color: _selectedColor ?? Colors.grey,
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _pickColor,
              child: Text('Pick Color'),
            ),
          ],
        ),
      ),
    );
  }
}
回到顶部