Flutter 生成图片保存至相册的示例

目录
  • 基本思路
  • 添加依赖
  • 实现代码

遇到一个需求,需要用 Flutter 生成图片,最终实现的效果如下:

基本思路

使用 Canvas 绘制图片中各元素,然后使用 PictureRecorder 进行记录生成。

添加依赖

  qr_flutter: ^3.1.0
  image_gallery_saver: ^1.2.2
  fluttertoast: ^4.0.0

实现代码

import 'dart:ui' as ui;
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter_platforms/generator/qrcode_generator.dart';

class ImageGenerator {
  generate(ui.Image topImg, ui.Image bottomImg, double screenWidth,
      String title, String content, String time) async {
    print("screenWidth = $screenWidth");

    final recorder = ui.PictureRecorder();

    ui.Paint paint = new Paint()
      ..isAntiAlias = true
      ..filterQuality = ui.FilterQuality.high;
    double rectTextTop = 150; // 文本显示矩形顶部距离图片最顶部的距离
    double textMargin = 20; // 文字间间距,包括距离矩形边框左右间距
    double pagePadding = 22; // 页面内容左右边距
    double bottomHeight = 160; // 底部区域高度
    // 获取标题高度等信息
    double textMaxWidth = screenWidth - pagePadding * 2 - textMargin * 2;
    TextPainter titlePainter = new TextPainter(
        text: TextSpan(
          text: title,
          style: TextStyle(
              fontSize: 20,
              color: Colors.black87,
              fontWeight: FontWeight.bold,
              height: 1.2),
        ),
        textDirection: TextDirection.ltr)
      ..layout(maxWidth: textMaxWidth);
    var titleHeight = titlePainter.height;
    print("titleHeight =  $titleHeight");

    TextPainter contentPainter = new TextPainter(
        text: TextSpan(
          text: content,
          style: TextStyle(
              fontSize: 16,
              color: Colors.black87,
              fontWeight: FontWeight.normal,
              height: 1.5),
        ),
        textDirection: TextDirection.ltr)
      ..layout(maxWidth: textMaxWidth);
    var contentHeight = contentPainter.height;
    print("contentheight = $contentHeight");

    double textHeight = titleHeight + contentHeight + 3 * textMargin;
    double bottom = textHeight + rectTextTop + textMargin * 2 + bottomHeight;
    double shadowBottom = textHeight + rectTextTop;
    print("bottom = $bottom");
    if (bottom < 300) {
      bottom = 300;
    }
    // 利用矩形左边的X坐标、矩形顶部的Y坐标、矩形右边的X坐标、矩形底部的Y坐标确定矩形的大小和位置
    var canvasRect = Rect.fromLTWH(0, 0, screenWidth, bottom);
    final canvas = Canvas(recorder, canvasRect);
    // 0. 绘制背景
    canvas.drawColor(Color(0xfffefefe), BlendMode.color);

    // 1. 绘制图片
    canvas.drawImageRect(
        topImg,
        Rect.fromLTWH(0, 0, topImg.width.toDouble(), topImg.height.toDouble()),
        Rect.fromLTWH(
            0, 0, screenWidth, topImg.height * screenWidth / topImg.width),
        paint);

    // 2. 绘制时间
    new TextPainter(
        text: TextSpan(
          text: time,
          style: TextStyle(
              fontSize: 16,
              color: Colors.white,
              fontWeight: FontWeight.normal,
              height: 1.5),
        ),
        textDirection: TextDirection.ltr)
      ..layout(maxWidth: textMaxWidth)
      ..paint(canvas, Offset(pagePadding, rectTextTop - 40));

    // 2. 绘制矩形,先绘制矩形,否则文字被覆盖
    paint.color = Color(0x00ffffffff);
    var rrect = RRect.fromRectAndRadius(
        Rect.fromLTWH(pagePadding, rectTextTop, screenWidth - pagePadding * 2,
            textHeight),
        Radius.circular(6));

    var path = Path()
      ..moveTo(pagePadding, rectTextTop)
      ..lineTo(screenWidth - pagePadding, rectTextTop)
      ..lineTo(screenWidth - pagePadding, shadowBottom)
      ..lineTo(pagePadding, shadowBottom)
      ..close();
    canvas.drawShadow(path, Colors.black, 6, true);
    canvas.drawRRect(rrect, paint);

    // 3. 绘制文字
    titlePainter.paint(
        canvas, Offset(pagePadding + textMargin, rectTextTop + textMargin));
    contentPainter.paint(
        canvas,
        Offset(pagePadding + textMargin,
            rectTextTop + textMargin * 2 + titleHeight));

    double bottomTextWidth = screenWidth * 2 / 5; // 底部文案宽度
    double bottomTextTopMargin = bottomHeight * 2 / 5; // 底部文案距离上面文字间距

    canvas.drawImageRect(
        bottomImg,
        Rect.fromLTWH(
            0, 0, bottomImg.width.toDouble(), bottomImg.height.toDouble()),
        // height / width = h / sc
        Rect.fromLTWH(
            screenWidth * 2 / 5,
            shadowBottom + bottomTextTopMargin + 5,
            bottomTextWidth,
            bottomImg.height.toDouble() *
                bottomTextWidth /
                bottomImg.width.toDouble()),
        paint);
    // 绘制二维码
    new QrCodeGenerator(data: "123456", version: 2).drawQrCode(
        canvas, new Size(90, 90), 45, shadowBottom + bottomTextTopMargin);

    // 转换成图片
    final picture = recorder.endRecording();
    ui.Image img = await picture.toImage(screenWidth.toInt(), bottom.toInt());

    print('img的尺寸: $img');
    final byteData = await img.toByteData(format: ui.ImageByteFormat.png);
    return byteData;
  }
}
import 'package:flutter/material.dart';
import 'package:flutter_platforms/generator/paint_cache.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'dart:ui' as ui;

// default color for the qr code pixels
const _qrDefaultColor = Color(0xff111111);
const _finderPatternLimit = 7;

class QrCodeGenerator {
  ui.Image topImage;
  ui.Image bottomImage;

  /// The QR code version.
  final int version; // the qr code version
  /// The error correction level of the QR code.
  final int errorCorrectionLevel; // the qr code error correction level
  /// The color of the squares.
  final Color color; // the color of the dark squares
  /// The color of the non-squares (background).
  @Deprecated(
      'You should us the background color value of your container widget')
  final Color emptyColor; // the other color
  /// If set to false, the painter will leave a 1px gap between each of the
  /// squares.
  final bool gapless;

  /// The image data to embed (as an overlay) in the QR code. The image will
  /// be added to the center of the QR code.
  ui.Image embeddedImage;

  /// Styling options for the image overlay.
  final QrEmbeddedImageStyle embeddedImageStyle;

  /// The base QR code data
  QrCode _qr;

  /// This is the version (after calculating) that we will use if the user has
  /// requested the 'auto' version.
  int _calcVersion;

  /// The size of the 'gap' between the pixels
  final double _gapSize = 0.25;

  /// Cache for all of the [Paint] objects.
  final _paintCache = PaintCache();

  QrCodeGenerator(
      {@required String data,
      @required this.version,
      this.errorCorrectionLevel = QrErrorCorrectLevel.L,
      this.color = _qrDefaultColor,
      this.emptyColor,
      this.gapless = false,
      this.embeddedImage,
      this.embeddedImageStyle}) {
    _init(data);
  }

  bool _hasAdjacentVerticalPixel(int x, int y, int moduleCount) {
    if (y + 1 >= moduleCount) return false;
    return _qr.isDark(y + 1, x);
  }

  bool _hasAdjacentHorizontalPixel(int x, int y, int moduleCount) {
    if (x + 1 >= moduleCount) return false;
    return _qr.isDark(y, x + 1);
  }

  Size _scaledAspectSize(
      Size widgetSize, Size originalSize, Size requestedSize) {
    if (requestedSize != null && !requestedSize.isEmpty) {
      return requestedSize;
    } else if (requestedSize != null && _hasOneNonZeroSide(requestedSize)) {
      final maxSide = requestedSize.longestSide;
      final ratio = maxSide / originalSize.longestSide;
      return Size(ratio * originalSize.width, ratio * originalSize.height);
    } else {
      final maxSide = 0.25 * widgetSize.shortestSide;
      final ratio = maxSide / originalSize.longestSide;
      return Size(ratio * originalSize.width, ratio * originalSize.height);
    }
  }

  bool _isFinderPatternPosition(int x, int y) {
    final isTopLeft = (y < _finderPatternLimit && x < _finderPatternLimit);
    final isBottomLeft = (y < _finderPatternLimit &&
        (x >= _qr.moduleCount - _finderPatternLimit));
    final isTopRight = (y >= _qr.moduleCount - _finderPatternLimit &&
        (x < _finderPatternLimit));
    return isTopLeft || isBottomLeft || isTopRight;
  }

  bool _hasOneNonZeroSide(Size size) => size.longestSide > 0;

  void _drawFinderPatternItem(
    FinderPatternPosition position,
    Canvas canvas,
    _PaintMetrics metrics,
  ) {
    final totalGap = (_finderPatternLimit - 1) * metrics.gapSize;
    final radius = ((_finderPatternLimit * metrics.pixelSize) + totalGap) -
        metrics.pixelSize;
    final strokeAdjust = (metrics.pixelSize / 2.0);
    final edgePos =
        (metrics.inset + metrics.innerContentSize) - (radius + strokeAdjust);
    Offset offset;
    if (position == FinderPatternPosition.topLeft) {
      offset =
          Offset(metrics.inset + strokeAdjust, metrics.inset + strokeAdjust);
    } else if (position == FinderPatternPosition.bottomLeft) {
      offset = Offset(metrics.inset + strokeAdjust, edgePos);
    } else {
      offset = Offset(edgePos, metrics.inset + strokeAdjust);
    }
    // configure the paints
    final outerPaint = _paintCache.firstPaint(QrCodeElement.finderPatternOuter,
        position: position);
    outerPaint.strokeWidth = metrics.pixelSize;
    outerPaint.color = color;
    final innerPaint = _paintCache.firstPaint(QrCodeElement.finderPatternInner,
        position: position);
    innerPaint.strokeWidth = metrics.pixelSize;
    innerPaint.color = emptyColor ?? Color(0x00ffffff);
    final dotPaint = _paintCache.firstPaint(QrCodeElement.finderPatternDot,
        position: position);
    dotPaint.color = color;
    final outerRect = Rect.fromLTWH(offset.dx, offset.dy, radius, radius);
    canvas.drawRect(outerRect, outerPaint);
    final innerRadius = radius - (2 * metrics.pixelSize);
    final innerRect = Rect.fromLTWH(offset.dx + metrics.pixelSize,
        offset.dy + metrics.pixelSize, innerRadius, innerRadius);
    canvas.drawRect(innerRect, innerPaint);
    final gap = metrics.pixelSize * 2;
    final dotSize = radius - gap - (2 * strokeAdjust);
    final dotRect = Rect.fromLTWH(offset.dx + metrics.pixelSize + strokeAdjust,
        offset.dy + metrics.pixelSize + strokeAdjust, dotSize, dotSize);
    canvas.drawRect(dotRect, dotPaint);
  }

  void _drawImageOverlay(
      Canvas canvas, Offset position, Size size, QrEmbeddedImageStyle style) {
    final paint = Paint()
      ..isAntiAlias = true
      ..filterQuality = FilterQuality.high;
    if (style != null) {
      if (style.color != null) {
        paint.colorFilter = ColorFilter.mode(style.color, BlendMode.srcATop);
      }
    }
    final srcSize =
        Size(embeddedImage.width.toDouble(), embeddedImage.height.toDouble());
    final src = Alignment.center.inscribe(srcSize, Offset.zero & srcSize);
    final dst = Alignment.center.inscribe(size, position & size);
    canvas.drawImageRect(embeddedImage, src, dst, paint);
  }

  void _init(String data) {
    if (!QrVersions.isSupportedVersion(version)) {
      throw QrUnsupportedVersionException(version);
    }
    // configure and make the QR code data
    final validationResult = QrValidator.validate(
      data: data,
      version: version,
      errorCorrectionLevel: errorCorrectionLevel,
    );
    if (!validationResult.isValid) {
      throw validationResult.error;
    }
    _qr = validationResult.qrCode;
    _calcVersion = _qr.typeNumber;
    _initPaints();
  }

  void _initPaints() {
    // Cache the pixel paint object. For now there is only one but we might
    // expand it to multiple later (e.g.: different colours).
    _paintCache.cache(
        Paint()..style = PaintingStyle.fill, QrCodeElement.codePixel);
    // Cache the empty pixel paint object. Empty color is deprecated and will go
    // away.
    _paintCache.cache(
        Paint()..style = PaintingStyle.fill, QrCodeElement.codePixelEmpty);
    // Cache the finder pattern painters. We'll keep one for each one in case
    // we want to provide customization options later.
    for (final position in FinderPatternPosition.values) {
      _paintCache.cache(Paint()..style = PaintingStyle.stroke,
          QrCodeElement.finderPatternOuter,
          position: position);
      _paintCache.cache(Paint()..style = PaintingStyle.stroke,
          QrCodeElement.finderPatternInner,
          position: position);
      _paintCache.cache(
          Paint()..style = PaintingStyle.fill, QrCodeElement.finderPatternDot,
          position: position);
    }
  }

  /// 绘制二维码
  drawQrCode(Canvas canvas, Size size, double dx, double dy) async {
    canvas.save();
    canvas.translate(dx, dy);
    // if the widget has a zero size side then we cannot continue painting.
    if (size.shortestSide == 0) {
      print("[QR] WARN: width or height is zero. You should set a 'size' value "
          "or nest this painter in a Widget that defines a non-zero size");
      return;
    }
    final paintMetrics = _PaintMetrics(
      containerSize: size.shortestSide,
      moduleCount: _qr.moduleCount,
      gapSize: (gapless ? 0 : _gapSize),
    );
    // draw the finder pattern elements
    _drawFinderPatternItem(FinderPatternPosition.topLeft, canvas, paintMetrics);
    _drawFinderPatternItem(
        FinderPatternPosition.bottomLeft, canvas, paintMetrics);
    _drawFinderPatternItem(
        FinderPatternPosition.topRight, canvas, paintMetrics);
    double left;
    double top;
    final gap = !gapless ? _gapSize : 0;
    // get the painters for the pixel information
    final pixelPaint = _paintCache.firstPaint(QrCodeElement.codePixel);
    pixelPaint.color = color;
    Paint emptyPixelPaint;
    if (emptyColor != null) {
      emptyPixelPaint = _paintCache.firstPaint(QrCodeElement.codePixelEmpty);
      emptyPixelPaint.color = emptyColor;
    }
    for (var x = 0; x < _qr.moduleCount; x++) {
      for (var y = 0; y < _qr.moduleCount; y++) {
        // draw the finder patterns independently
        if (_isFinderPatternPosition(x, y)) continue;
        final paint = _qr.isDark(y, x) ? pixelPaint : emptyPixelPaint;
        if (paint == null) continue;
        // paint a pixel
        left = paintMetrics.inset + (x * (paintMetrics.pixelSize + gap));
        top = paintMetrics.inset + (y * (paintMetrics.pixelSize + gap));
        var pixelHTweak = 0.0;
        var pixelVTweak = 0.0;
        if (gapless && _hasAdjacentHorizontalPixel(x, y, _qr.moduleCount)) {
          pixelHTweak = 0.5;
        }
        if (gapless && _hasAdjacentVerticalPixel(x, y, _qr.moduleCount)) {
          pixelVTweak = 0.5;
        }
        final squareRect = Rect.fromLTWH(
          left,
          top,
          paintMetrics.pixelSize + pixelHTweak,
          paintMetrics.pixelSize + pixelVTweak,
        );
        canvas.drawRect(squareRect, paint);
      }
    }
    if (embeddedImage != null) {
      final originalSize = Size(
        embeddedImage.width.toDouble(),
        embeddedImage.height.toDouble(),
      );
      final requestedSize =
          embeddedImageStyle != null ? embeddedImageStyle.size : null;
      final imageSize = _scaledAspectSize(size, originalSize, requestedSize);
      final position = Offset(
        (size.width - imageSize.width) / 2.0,
        (size.height - imageSize.height) / 2.0,
      );
      // draw the image overlay.
      _drawImageOverlay(canvas, position, imageSize, embeddedImageStyle);
    }
    canvas.restore();
  }
}

class _PaintMetrics {
  _PaintMetrics(
      {@required this.containerSize,
      @required this.gapSize,
      @required this.moduleCount}) {
    _calculateMetrics();
  }

  final int moduleCount;
  final double containerSize;
  final double gapSize;
  double _pixelSize;

  double get pixelSize => _pixelSize;
  double _innerContentSize;

  double get innerContentSize => _innerContentSize;
  double _inset;

  double get inset => _inset;

  void _calculateMetrics() {
    final gapTotal = (moduleCount - 1) * gapSize;
    var pixelSize = (containerSize - gapTotal) / moduleCount;
    _pixelSize = (pixelSize * 2).roundToDouble() / 2;
    _innerContentSize = (_pixelSize * moduleCount) + gapTotal;
    _inset = (containerSize - _innerContentSize) / 2;
  }
}
import 'dart:typed_data';

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

import 'package:flutter/services.dart';
import 'package:flutter_platforms/generator/image_generator.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';

class ImageGeneratorPage extends StatefulWidget {
  @override
  _ImageGeneratorPageState createState() => _ImageGeneratorPageState();
}

class _ImageGeneratorPageState extends State<ImageGeneratorPage> {
  ByteData _imgBytes;
  ui.Image _topImage;
  ui.Image _bottomImage;

  @override
  void initState() {
    super.initState();
    _loadImage('images/icon2.jpg').then((image) {
      setState(() {
        _topImage = image;
      });
    });
    _loadImage('images/bottom_text.png').then((image) {
      setState(() {
        _bottomImage = image;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    double screenWidth = MediaQuery.of(context).size.width;
    return Scaffold(
      backgroundColor: Colors.teal,
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(12.0),
              child: RaisedButton(
                child: Text("Image generate"),
                onPressed: () {
                  _generate(screenWidth);
                },
              ),
            ),
            _imgBytes != null
                ? Container(
                    child: Image.memory(
                    Uint8List.view(_imgBytes.buffer),
                    height: 500,
                  ))
                : Container()
          ],
        ),
      ),
    );
  }

  /// 加载图片
  Future<ui.Image> _loadImage(String path) async {
    var data = await rootBundle.load(path);
    var codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
    var info = await codec.getNextFrame();
    return info.image;
  }

  void _generate(double screenWidth) async {
    ByteData byteData = await ImageGenerator().generate(
        _topImage,
        _bottomImage,
        screenWidth,
        "90后海归硕士多次偷快递 压力太大只为看看里面是什么",
        "3月20日中午,一名年轻女子来取快递,3月20日中午,一名年轻女子来取快递,3月20日中午,一名年轻女子来取快递,3月20日中午,一名年轻女子来取快递,3月20日中午,一名年轻女子来取快递,3月20日中午,一名年轻女子来取快递,3月20日中午,一名年轻女子来取快递,3月20日中午,一名年轻女子来取快递,3月20日中午,一名年轻女子来取快递,3月20日中午,一名年轻女子来取快递,3月20日中午,一名年轻女子来取快递,3月20日中午,一名年轻女子来取快递,3月20日中午,一名年轻女子来取快递,3月20日中午,一名年轻女子来取快递,3月20日中午,一名年轻女子来取快递,3月20日中午,一名年轻女子来取快递,3月20日中午,一名年轻女子来取快递,1111111112222欧某问了她门牌号码并帮她找到了该住户的快递。但她离开后不久,此住户真正的物主来找快递未果,向欧某反映自己的快递丢失。欧某再次查找监控,11",
        "2019年7月1日 英山网");

    saveFile(byteData);

    setState(() {
      _imgBytes = byteData;
    });
  }

  saveFile(ByteData byteData) async {
    Uint8List pngBytes = byteData.buffer.asUint8List();
    final result = await ImageGallerySaver.saveImage(pngBytes); //这个是核心的保存图片的插件
    print("result = $result");
    Fluttertoast.showToast(
        msg: "filePath = $result",
        toastLength: Toast.LENGTH_SHORT,
        gravity: ToastGravity.CENTER,
        timeInSecForIosWeb: 1,
        backgroundColor: Colors.yellow,
        textColor: Colors.black,
        fontSize: 16.0);
  }
}

以上就是Flutter 生成图片保存至相册的示例的详细内容,更多关于Flutter 生成图片保存至相册的资料请关注我们其它相关文章!

(0)

相关推荐

  • flutter 轮播图动态加载网络图片的方法

    Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面. Flutter可以与现有的代码一起工作.在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费.开源的. Swiper,网上很多例子只是加载固定的几张图,并且页面只有一个轮播图,在实际应用中,可能会遇到类似ins这种,加载列表,并且都是多图模式的情况. 需要添加依赖包 flukit: ^1.0.0 引用 import 'package:flukit/flukit.da

  • Flutter实现容器组件、图片组件 的代码

    •容器组件 容器组件(Container)可以理解为在Android中的RelativeLayout或LinearLayout等,在其中你可以放置你想布局的元素控件,从而形成最终你想要的页面布局.当然Flutter中的容器组件作为一个"容器",肯定会有一些给我们提供一些属性来约束我们容器内的组件,下面介绍一下容器组件(Container)的一些常用属性及描述: 属性名 类型 说明 key Key Container唯一标识符,用于查找更新 alignment AlignmentGeom

  • Flutter 使用cached_image_network优化图片加载体验

    在 App 中会经常遇到需要从后台拉取图片的场景,这一方面会给服务器带来网络带宽消耗,另一方面加载图片的等待过程也会影响用户体验.因此,往往会在 App 端对图片做缓存机制,以避免同一张图片反复发起请求.这个时候cached_image_network就派上用场了 上一篇Flutter 给列表增加下拉刷新和上滑加载更多功能,我们使用了列表,其中列表中有从网络下载图片.直接使用 Flutter 自带的 Image.network 下载图片一是无法缓存,二是体验不够好.熟悉 iOS 的肯定知道 SD

  • Flutter实现可以缩放拖拽的图片示例代码

    前言 在pub上面找了下,没有发现一个效果跟微信一样的支持缩放拖拽效果的image,所以就自己撸了一个,之前写过Flutter 什么功能都有的Image,于是就在这个上面新增了这个功能. 主要功能: 缩放拖拽 在PageView里面缩放拖拽 支持缩放拖拽 用法 1.将extended_image的mode参数设置为ExtendedImageMode.Gesture 2.设置GestureConfig ExtendedImage.network( imageTestUrl, fit: BoxFit

  • Flutter Image实现图片加载

    Image 简介 Android ios 原生中使用 ImageView 来加载显示图片. 在flutter 中通过Image来加载并显示图片. 所有的widget并不是直接绘制图片的,而是控制的图片的主要属性的容器,负责绘制的是RenderObject,他们中间是通过ElementTree来联系起来.有了这个基础后,所有的widget都不会提供画布(canvas)来直接绘制image RawImage 这是一个最基础图片容器Widget. Image 这是一个通用包装类,它包装了RawImag

  • Flutter实现图片滤镜效果

    本着学习的态度,研究了一下flutter里面的ColorFilter,字面意思翻译颜色过滤器,学习就是要举一反三,拓展思考就把这个功能做了一个简单的图片滤镜效果.(ps:图片有点没控制住,有点长) 效果如下: ColorFilter 介绍 两种使用方式 const ColorFilter.mode(Color color, BlendMode blendMode) const ColorFilter.matrix(List<double> matrix) 实现效果主要通过第一种方式, 首先创建

  • Flutter中网络图片加载和缓存的实现

    前言 应用开发中经常会碰到网络图片的加载,通常我们会对图片进行缓存,以便下次加载同一张图片时不用再重新下载,在包含有大量图片的应用中,会大幅提高图片展现速度.提升用户体验且为用户节省流量.Flutter本身提供的Image Widget已经实现了加载网络图片的功能,且具备内存缓存的机制,接下来一起看一下Image的网络图片加载的实现. 重温小部件Image 常用小部件Image中实现了几种构造函数,已经足够我们日常开发中各种场景下创建Image对象使用了. 有参构造函数: Image(Key k

  • Flutter 生成图片保存至相册的示例

    目录 基本思路 添加依赖 实现代码 遇到一个需求,需要用 Flutter 生成图片,最终实现的效果如下: 基本思路 使用 Canvas 绘制图片中各元素,然后使用 PictureRecorder 进行记录生成. 添加依赖 qr_flutter: ^3.1.0 image_gallery_saver: ^1.2.2 fluttertoast: ^4.0.0 实现代码 import 'dart:ui' as ui; import 'dart:ui'; import 'package:flutter/

  • Android 图片保存到相册不显示的解决方案(兼容Android 10及更高版本)

    目录 前言 问题 解决问题 前言 写了一个demo,简单逻辑就是:在一个图片上添加一行文字或者是水印,并且保存到系统相册,也就是我们手机上的图库.前面编辑图片添加水印都没有问题,到后面保存到系统相册出现了问题:显示不出来图片. 问题 在 Android 10 之前保存系统相册的三步骤: 保存图片到手机 把图片插入到手机图库 发广播更新 代码如下: public static void savePhotoAlbum(Context context, Bitmap bmp) { // 首先保存图片

  • Flutter开发Widgets 之 PageView使用示例

    目录 构造方法以及参数: 基本用法 无限滚动 实现指示器 切换动画 总结: 构造方法以及参数: PageView可用于Widget的整屏滑动切换,如当代常用的短视频APP中的上下滑动切换的功能,也可用于横向页面的切换,如APP第一次安装时的引导页面,也可用于开发轮播图功能. PageView({ Key? key, this.scrollDirection = Axis.horizontal, // 设置滚动方向 垂直 / 水平 this.reverse = false, // 反向滚动 Pag

  • Flutter状态管理Provider的使用示例详解

    目录 前言 计数器 全局状态 总结 前言 Provider是三大主流状态管理框架官方推荐使用的框架,它是基于官方数据共享组件InheritedWidget实现的,通过数据改变调用生命周期中的didChangeDependencies()方法,来实现状态的通知改变. InheritedWidget的使用可以参考我之前的这篇Flutter中几种数据传递的应用总结. 计数器 还是以计数器为例,这次通过Provider实现,provider相较于bloc并没有那么强制性分层,所以这里我们自己分为数据层(

  • iOS如何将照片保存到相册

    本文实例为大家分享了iOS将照片保存到相册的具体代码,供大家参考,具体内容如下 在使用前  请导入photos.framework 然后导入 #import <Photos/PHPhotoLibrary.h> #import <Photos/PHAssetChangeRequest.h> #import <Photos/PHImageManager.h> 方法一 使用UIImageWriteToSavedPhotosAlbum函数将图片保存到相册,如: - (void)

  • Android 同时setTag两次保存多种值的示例代码

    setTag是android的view类中很有用的一个方法,可以用它来给空间附加一些信息,在很多场合下都得到妙用. 示例代码: view.setTag(R.string.action_settings,hodler.content); 接收两个值,一个是key值,必须是唯一值,而且要写在values/string.xml 里面,例如 <resources> <item type ="id" name = "ffffff"></item&

  • Android实现长按图片保存至相册功能

    前言:前面写了一篇reactnative的学习笔记,说reactnative的Android框架中有很多福利,确实是的,也说到了我们app中的一个把图片保存到相册的功能,好吧,还是准备写一篇博客,就当笔记了- 先上几张app的图片: 一进app就是一个进度条加载图片(我待会也会说一下进度条view跟怎么监听图片加载过程): 图片加载完毕后: 长按图片进入相册可以看到我们保存的图片: 监听图片加载的loaddingview源码(不是很难,我就直接贴代码了): package com.leo.cam

  • iOS 把图片保存到相册,并获取图片文件名的实例

    实例如下所示: - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; if (picker.sourceType == UIImagePickerControllerSo

  • js 将canvas生成图片保存,或直接保存一张图片的实现方法

    将canvas数组保存 function downLoadImage(canvas,name) { var a = document.createElement("a"); a.href = canvas.toDataURL(); a.download = name; a.click(); } canvas:传入canvas的dom对象 name:保存的图片的名字 直接将图片保存的方法 function downLoadImage(img,name) { var a = documen

  • iOS实现视频下载并自动保存到相册功能

    iOS视频下载功能实现,并自动保存到相册(有MBProgressHUD 可以解开注释),供大家参考,具体内容如下 视频类定义属性 ///@property (nonatomic,strong) MBProgressHUD *hud; @property (nonatomic,strong) NSURLSession *session; ///视频播放和下载用的url @property (nonatomic,strong) NSURL *url; ///初始化session - (NSURLSe

随机推荐