Flutter自定义渲染插件render的使用

发布于 1周前 作者 yuanlaile 来自 Flutter

Flutter自定义渲染插件render的使用

render 是一个用于Flutter的强大插件,允许开发者将Widget渲染为多种导出格式(如PNG、JPEG、GIF、MP4等)。本文将介绍如何使用 render 插件进行图像和动画的渲染。

🚀 Getting started

Installing

首先,在你的 pubspec.yaml 文件中添加 render 依赖:

dependencies:
  render: ^x.x.x

然后运行 flutter pub get 来安装依赖。

在你的Dart代码中导入 render 包:

import 'package:render/render.dart';

Quick start

要使用 Render 插件,需要将要渲染的Widget包裹在 Render widget中,并提供一个 RenderController 来控制渲染操作。

示例代码如下:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final controller = RenderController();

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Render Example')),
        body: Center(
          child: Render(
            controller: controller,
            child: Container(
              color: Colors.blue,
              width: 200,
              height: 200,
            ),
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () async {
            // 捕获静态图像
            final imageResult = await controller.captureImage(
              format: ImageFormat.png,
              settings: ImageSettings(pixelRatio: 3),
            );
            print('Image saved to: ${imageResult.output.path}');
            
            // 捕获动态动画
            final motionResult = await controller.captureMotion(
              Duration(seconds: 5),
              settings: MotionSettings(pixelRatio: 3),
              format: Mp4Format(),
            );
            print('Motion saved to: ${motionResult.output.path}');
          },
          child: Icon(Icons.camera_alt),
        ),
      ),
    );
  }
}

Usage

Image Rendering

捕获静态图像的方法有以下几种:

  • captureImage():渲染当前 Render widget中的子widget。
  • captureImageFromWidget(Widget widget):渲染不在widget树中的widget。
  • captureImageWithStream():带有通知流的渲染。
  • captureImageFromWidgetWithStream(Widget widget):渲染不在widget树中的widget,并带有通知流。

示例代码:

final imageResult = await renderController.captureImage(
  format: ImageFormat.png,
  settings: const ImageSettings(pixelRatio: 3),
);

// 显示结果图像
Image.file(imageResult.output);

Motion Rendering

捕获动态动画的方法有以下几种:

  • captureMotion():渲染当前 Render widget中的子widget。
  • captureMotionFromWidget(Widget widget):渲染不在widget树中的widget。
  • captureMotionWithStream():带有通知流的渲染。
  • captureMotionFromWidgetWithStream(Widget widget):渲染不在widget树中的widget,并带有通知流。

示例代码:

final result = await renderController.captureMotionWithStream(
  functionController.duration,
  settings: const MotionSettings(pixelRatio: 4),
  format: Format.gif,
);

final videoController = VideoPlayerController.file(result.output);
await videoController.initialize();
await videoController.play();

VideoPlayer(videoController); // 显示结果视频

Recording Motion

录制动态动画的方法有以下两种:

  • recordMotion():录制当前 Render widget中的子widget。
  • recordMotionFromWidget(Widget widget):录制不在widget树中的widget。

示例代码:

final recorder = renderController.recordMotion(
  functionController.duration,
  settings: const MotionSettings(pixelRatio: 5),
  format: const GifFormat(),
);

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

final result = await recorder.stop(); // 结果可以显示(见运动渲染)

Out of Context

通过 ...fromWidget() 方法可以直接渲染不在widget树中的widget。注意,这并不会减少处理时间。

示例代码:

final imageResult = await renderController.captureImageFromWidget(
  Container(), // 要渲染的widget
  format: ImageFormat.png,
  settings: const ImageSettings(pixelRatio: 3),
);

Image.file(imageResult.output); // 显示结果图像

Handling Stream & Information Flow

使用信息流可以更好地处理长时间的渲染操作。以下是处理渲染过程的示例:

final stream = renderController.captureMotionWithStream(
  functionController.duration,
  settings: const MotionSettings(pixelRatio: 10),
  format: const GifFormat(),
);

stream.listen((event) {
  if (event.isActivity) {
    final activity = event as RenderActivity;
    print("Process: ${activity.progressPercentage}");
  }
});

final result = await stream.firstWhere((event) => event.isResult || event.isFatalError);

示例Demo

下面是一个完整的示例demo,展示了如何使用 render 插件进行图像和动画的渲染:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final controller = RenderController();

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Render Example')),
        body: Center(
          child: Render(
            controller: controller,
            child: Container(
              color: Colors.blue,
              width: 200,
              height: 200,
            ),
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () async {
            // 捕获静态图像
            final imageResult = await controller.captureImage(
              format: ImageFormat.png,
              settings: ImageSettings(pixelRatio: 3),
            );
            print('Image saved to: ${imageResult.output.path}');
            
            // 捕获动态动画
            final motionResult = await controller.captureMotion(
              Duration(seconds: 5),
              settings: MotionSettings(pixelRatio: 3),
              format: Mp4Format(),
            );
            print('Motion saved to: ${motionResult.output.path}');
          },
          child: Icon(Icons.camera_alt),
        ),
      ),
    );
  }
}

以上就是 render 插件的基本用法,希望对你有所帮助!如果你有任何问题或建议,请随时在评论区留言。


更多关于Flutter自定义渲染插件render的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter自定义渲染插件render的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中,自定义渲染插件通常涉及到底层平台(如Android和iOS)的渲染逻辑。由于Flutter的渲染机制是基于Skia图形库的,自定义渲染插件需要利用平台通道(MethodChannel或BasicMessageChannel)与原生代码进行交互。以下是一个简单的示例,展示如何创建一个自定义渲染插件。

Flutter端代码

首先,我们需要在Flutter端定义一个接口来与原生代码通信。这里我们将创建一个简单的插件,该插件将在原生平台上绘制一个自定义形状。

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

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

class MyApp extends StatelessWidget {
  static const platform = MethodChannel('com.example.custom_render');

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Custom Render Plugin'),
        ),
        body: Center(
          child: CustomRenderWidget(),
        ),
      ),
    );
  }
}

class CustomRenderWidget extends StatefulWidget {
  @override
  _CustomRenderWidgetState createState() => _CustomRenderWidgetState();
}

class _CustomRenderWidgetState extends State<CustomRenderWidget> {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: FutureBuilder<void>(
        future: MyApp.platform.invokeMethod('drawCustomShape'),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            if (snapshot.hasError) {
              return Text('Failed to draw shape: ${snapshot.error}');
            } else {
              return CustomPaint(
                size: Size(300, 300),
                painter: CustomShapePainter(),
              );
            }
          } else {
            return CircularProgressIndicator();
          }
        },
      ),
    );
  }
}

class CustomShapePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.fill;
    final path = Path()
      ..moveTo(size.width / 2, 0)
      ..lineTo(size.width, size.height)
      ..lineTo(0, size.height)
      ..close();
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

Android端代码

android/app/src/main/java/com/example/your_app_id/目录下创建CustomRenderPlugin.java文件:

package com.example.your_app_id;

import android.content.Context;
import androidx.annotation.NonNull;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;

public class CustomRenderPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware {
    private MethodChannel channel;
    private Context applicationContext;

    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
        applicationContext = flutterPluginBinding.getApplicationContext();
        channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "com.example.custom_render");
        channel.setMethodCallHandler(this);
    }

    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
        if (call.method.equals("drawCustomShape")) {
            // Here you can add any native rendering logic if needed.
            // For simplicity, we'll just return success.
            result.success(null);
        } else {
            result.notImplemented();
        }
    }

    @Override
    public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
        channel.setMethodCallHandler(null);
    }

    @Override
    public void onAttachedToActivity(ActivityPluginBinding binding) {
        // This method is optional.
    }

    @Override
    public void onDetachedFromActivityForConfigChanges() {
        // This method is optional.
    }

    @Override
    public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
        // This method is optional.
    }

    @Override
    public void onDetachedFromActivity() {
        // This method is optional.
    }
}

android/app/src/main/kotlin/com/example/your_app_id/(或Java目录)的MainActivity.kt(或MainActivity.java)中注册插件:

package com.example.your_app_id

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant

class MainActivity: FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        GeneratedPluginRegistrant.registerWith(flutterEngine)
        CustomRenderPlugin().registerWith(flutterEngine.dartExecutor.binaryMessenger)
    }
}

iOS端代码

ios/Runner/目录下,打开AppDelegate.swift文件,并添加以下内容来注册插件(如果使用的是Objective-C,则流程略有不同):

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    // Register the custom render plugin (if needed, usually handled automatically if using Swift Package Manager or CocoaPods)
    // CustomRenderPlugin.register(with: self.registrar(forPlugin: "com.example.custom_render")!)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

由于iOS端的自定义渲染通常更加复杂,且大多数情况下不需要通过MethodChannel单独调用绘制方法(因为Flutter已经提供了强大的自定义绘制能力),所以这里我们省略了iOS端的详细自定义绘制代码。如果确实需要在iOS端进行底层绘制,可以考虑使用FlutterTextureFlutterPlatformView等高级功能。

注意事项

  1. 插件注册:确保在Flutter和原生平台正确注册插件。
  2. 通信机制:使用MethodChannel或其他通信机制在Flutter和原生代码之间进行通信。
  3. 权限管理:如果需要访问设备资源(如相机、存储等),请确保在原生平台请求相应权限。

这个示例展示了如何通过MethodChannel在Flutter和原生平台之间进行简单通信,并在Flutter端使用CustomPaint进行自定义绘制。实际项目中,自定义渲染插件可能会更加复杂,需要根据具体需求进行调整。

回到顶部