Flutter未知功能插件surface的介绍与使用

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

Flutter未知功能插件surface的介绍与使用

Surface 是一个可变形、分层、内在动画的容器小部件,提供方便访问模糊处理的 ImageFilterMaterialInkResponseHapticFeedback

🎊 BouncyBall

BouncyBall 是一个令人愉悦的弹跳反应,对用户输入在 Material 上的位置进行镜像。该功能目前仍在开发中。

Animated GIF 预览 Surface Example 应用

📚 SurfaceLayer 容器分割

SurfaceLayer 提供了强大的定制功能,适用于全应用风格设置或动态更改。

  • 支持 ColorGradientBASEMATERIAL 层。
  • 支持三种不同的过滤器及其强度。
  • 边距可以通过 Shape.padLayer 进行分配。

🔰 自定义 SurfaceShape

  • 手动 Shape 定义正在开发中。
  • CornerSpec 定义并生成 SurfaceShape
  • 可选提供边框或缩放。

🔲 Peek

PeekMATERIAL 边距或“边框”,大小由参数 Peek.peek 设置。

  • 通过传递 Peek.alignment 和调整 Peek.ratio 来给选定的边特殊处理,使其看起来更厚。

👆 TapSpec

如果 SurfaceTapSpec.tappable,则:

  • TapSpec.onTap 回调可用。
  • 可以提供颜色来定制 InkResponse
  • 考虑使用 TapSpec.providesFeedback 来进行触觉反馈。
  • 使用 BouncyBallsplashFactory,可以选择自己的 splashFactory 或默认为 Theme 的。

🔬 定义 SurfaceFX 的过滤器

  • 在配置的 Filter.filteredLayers Set
  • 其半径(强度)通过 Filter.radiusMap 映射。
  • SurfaceLayer.BASE 过滤器可以通过 Surface.margin 延伸。
  • SurfaceFX 类型用于基于 SurfaceLayer 和当前 Layer 的半径传递自定义效果。

🤸‍♂️ FX 开放式扩展

目前仅负责 FX.blurry,这是 Filter 的默认 ImageFilter

📖 参考

  • 🌟 Surface - 一个可变形、分层、动画的容器小部件。
  • 🔰 Shape - 由 CornerSpec 定义并生成 SurfaceShape
  • 🔲 Peek - 一个具有可选参数的对象,用于自定义 Surface 的“peek”。
  • 👆 TapSpec - 一个具有可选参数的对象,用于自定义 Surface 的点击行为。
  • 🔬 Filter - 一个具有可选参数的对象,用于自定义 Surface 的过滤器/效果。

🎊 一些额外的好东西

  • 🔦 WithShading - Color 扩展
  • 🤚 DragNub - 一个小的圆形“手柄”指示器,用于可视化可拖动材料的印象。

🌇 路线图

  • 当前正在进行的更新可能会导致重大变化。
  • 由于发布 Surface,已经找到了更多高级和强大的包,但 Surface 的开发仍然继续前进。
  • 目前,只有 Surface 的形状变化的动画。

🌟 示例

查看 Surface 包的示例用法:

/// WORK IN PROGRESS
library surface_example;

import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';

import 'package:surface/surface.dart';
import 'package:ball/ball.dart';
import 'surface_palette.dart';
import 'ball_pit.dart';

const _COLOR_PRIMARY = Colors.red;
const _COLOR_ACCENT = Colors.blue;
const _DURATION = Duration(milliseconds: 450);
const _BACKGROUND =
    'https://apod.nasa.gov/apod/image/2102/rosette_goldman_2500.jpg';

void main() => runApp(SurfaceExample());

class SurfaceExample extends StatelessWidget {
  Widget build(BuildContext context) {
    return MaterialApp(
      // debugShowCheckedModeBanner: false,
      title: 'Surface Example',
      themeMode: ThemeMode.dark,
      darkTheme: ThemeData.from(
        colorScheme: ColorScheme.fromSwatch(
          /// `ColorScheme.primary.withBlack(100)` 是 `Surface.baseColor` 的备用。
          primarySwatch: _COLOR_PRIMARY,
          brightness: Brightness.light,
          accentColor: _COLOR_ACCENT,

          /// Color 扩展 `.withBlack(int subtract)` 作为 Surface 包的一个额外好东西。
          backgroundColor: _COLOR_PRIMARY.withBlack(150),
        ).copyWith(
          /// `ColorScheme.surface` 是 `Surface.color` 的备用。
          surface: _COLOR_ACCENT.withWhite(50).withOpacity(0.3),
        ),
      ).copyWith(
        /// 🏓 [BouncyBall] 是 Surface 包中的另一个好东西。
        splashFactory: BouncyBall.splashFactory,
        // splashFactory: BouncyBall.splashFactory2,
        // splashFactory: BouncyBall.splashFactory3,
        // splashFactory: BouncyBall.splashFactory4,
        // splashFactory: BouncyBall.marbleFactory,
        // splashFactory: moldedBouncyBalls,

        /// Surface `TapSpec.inkHighlightColor` 和 `inkSplashColor` 默认为 ThemeData。
        splashColor: _COLOR_ACCENT,
        highlightColor: _COLOR_PRIMARY.withOpacity(0.3),
      ),
      home: const Landing(),
    );
  }
}

class SurfaceExampleDrawer extends StatelessWidget {
  const SurfaceExampleDrawer({Key? key}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Drawer(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: <Widget>[
          buildTile(context,
              onSurface: '🎨\n'
                  'Surface\n'
                  'Palette',
              newView: const SurfacePalette()),
          buildTile(context,
              onSurface: '🏓\n'
                  'Ball\n'
                  'Pit',
              newView: const BallPit()),
        ],
      ),
    );
  }

  Surface buildTile(
    BuildContext context, {
    required Widget newView,
    required String onSurface,
  }) {
    return Surface(
      width: 250.0,
      height: 250.0,
      padding: const EdgeInsets.all(25),
      peek: const Peek(peek: 10),
      shape: const Shape(
        corners: CornerSpec.ROUNDED,
        baseCorners: CornerSpec.SQUARED,
      ),
      baseColor: _COLOR_ACCENT[700],
      color: _COLOR_PRIMARY[900],
      tapSpec: TapSpec(
        onTap: () => Navigator.push(
          context,
          MaterialPageRoute(
            builder: (BuildContext context) => newView,
          ),
        ),
      ),
      child: Text(
        onSurface,
        textAlign: TextAlign.center,
        style: const TextStyle(
          color: Colors.white,
          fontSize: 50,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }
}

class Landing extends StatefulWidget {
  const Landing({Key? key}) : super(key: key);
  _LandingState createState() => _LandingState();
}

class _LandingState extends State<Landing> {
  int _counter = 0;
  late double _width, _height;
  late Color _primary, _accent;
  bool _isExampleBeveled = true,
      _showExamplePopup = false,
      _flipGradient = false;
  late Timer appBarGradientTimer;

  /// 因为这是一个示例应用...
  void _incrementCounter() => setState(() => _counter++);

  /// 重写 initState 并设置一个 Timer
  [@override](/user/override)
  void initState() {
    super.initState();

    /// 通过改变渐变来展示 Surface 的内在动画
    appBarGradientTimer = Timer(
      Duration(milliseconds: 2600),
      () => setState(() => _flipGradient = true),
    );
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    /// 存储每次构建 [_LandingState] 时的显示分辨率。
    _width = MediaQuery.of(context).size.width;
    _height = MediaQuery.of(context).size.height;

    /// 并提供对 [Theme] 颜色的便捷访问。
    _primary = Theme.of(context).primaryColor;
    _accent = Theme.of(context).accentColor;

    /// 这个基础 Surface 的颜色仅在 [_buildBackground] 加载背景图形之前可见。
    return WillPopScope(
      onWillPop: () async {
        if (_showExamplePopup) {
          setState(() => _showExamplePopup = false);
          return false;
        }

        return true;
      },
      child: Surface(
        shape: const Shape(
          corners: CornerSpec.SQUARED,
        ),
        peek: const Peek(peek: 0),
        color: Theme.of(context).backgroundColor,

        /// 因为 Surface 默认是 `TapSpec.tappable`,
        /// 这两个 `Color` 参数将自定义长按 InkResponse 的外观...这些主题颜色在主 MaterialApp `ThemeData` 中初始化。
        ///
        /// 由于这些主题颜色被 TapSpec 默认使用,
        /// 我们将在未来的 TapSpecs 中省略它们的初始化。
        ///
        /// 也可以参见主 ThemeData,其中 Surface 提供了额外的 [CustomInk.splashFactory] 用于墨水样式化。
        tapSpec: TapSpec(
          inkSplashColor: _accent,
          inkHighlightColor: _primary.withOpacity(0.5),
        ),

        /// 应用程序 Scaffold
        child: Scaffold(
          backgroundColor: Colors.transparent,
          drawer: const SurfaceExampleDrawer(),
          appBar: AppBar(
            title: const Text('Surface Example'),

            /// ➖ Surface 作为 AppBar
            flexibleSpace: _surfaceAsAppBar(),

            /// 这个按钮在 AppBar 中将切换一个布尔值,显示位于此堆栈上方的 [_surfaceAsPopup] (在 Z 轴上)。
            actions: <Widget>[
              IconButton(
                icon: Icon((_showExamplePopup)
                    ? Icons.close
                    : Icons.note_add_outlined),
                onPressed: () =>
                    setState(() => _showExamplePopup = !_showExamplePopup),
              )
            ],
          ),

          /// Scaffold Body
          body: Stack(
            children: [
              /// 🌆 背景图像
              _buildBackground(),

              /// 🔳 Surface 作为窗口
              Stack(
                children: [
                  Positioned(
                    top: _height * 0.075,
                    left: _width / 10,
                    child: _surfaceAsWindow(
                      context,
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          Text(
                            'A nice, basic counter example'.toUpperCase(),
                            style: Theme.of(context).textTheme.overline,
                          ),
                          Text(
                            'Number of + Presses:',
                            style: Theme.of(context).textTheme.headline4,
                          ),
                          Text(
                            '$_counter',
                            style: Theme.of(context).textTheme.headline1,
                          )
                        ],
                      ),
                    ),
                  ),

                  /// ✂ 状态控制按钮交换一些颜色并切换 [corners] 属性。
                  _stateControlButton(isShadow: true),
                  _stateControlButton(),
                ],
              ),

              /// ❗ Surface 作为弹出窗口
              /// 除非 [_showExamplePopup] == true,否则视觉上不显示。
              Center(
                child: _surfaceAsPopup(),
              ),
            ],
          ),

          /// FAB
          floatingActionButton: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.end,

            /// 🔘 Surface 作为浮动操作按钮
            children: <Widget>[
              _surfaceAsFAB(
                filteredLayers: const {SurfaceLayer.MATERIAL},
                passedString: 'filteredLayers:\nMATERIAL',
              ),
              Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  _surfaceAsFAB(
                    filteredLayers: Filter.BASE, // const {SurfaceLayer.BASE}
                    passedString: 'filteredLayers:\nBASE',
                  ),
                  _surfaceAsFAB(
                    filteredLayers: Filter.BASE_AND_MATERIAL,
                    // filteredLayers: const {
                    //   SurfaceLayer.BASE,
                    //   SurfaceLayer.MATERIAL
                    // },
                    passedString: 'filteredLayers:\nBASE &\nMATERIAL',
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }

  /// 🌆 背景图像
  Image _buildBackground() {
    return Image.network(
      _BACKGROUND,
      // 这个 frameBuilder 简单地在加载时淡入照片。
      frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
        if (wasSynchronouslyLoaded) return child;
        return AnimatedOpacity(
          child: child,
          opacity: frame ==
                  null // 动画 gif 有多个帧,静态图片有 1 到加载,未加载的图片有 `null`
              ? 0
              : 1,
          duration: _DURATION * 2,
          curve: _CURVE,
        );
      },
      // 拉伸照片到应用程序的大小,并使其覆盖 Surface。
      fit: BoxFit.cover,
      width: _width,
      height: _height,
    );
  }

  /// ### ➖ Surface 作为 AppBar
  Surface _surfaceAsAppBar() {
    return Surface(
      duration: _DURATION * 4,
      curve: _CURVE,
      width: _width,
      height: double.infinity,
      shape: const Shape(corners: CornerSpec.SQUARED),

      /// 创建的 Timer 在几秒后倒计时,然后翻转此渐变以产生酷炫的效果。
      gradient: LinearGradient(
        begin: (_flipGradient) ? Alignment.centerRight : Alignment.centerLeft,
        end: (_flipGradient) ? Alignment.centerLeft : Alignment.centerRight,
        colors: [_accent, _primary],
        stops: (_flipGradient) ? [0, 0.25] : [0, 0.75],
      ),

      /// 确保边缘的边框非常薄,以不遮挡系统导航栏,但使用 `alignment` 和 `ratio` 给底部边缘一些厚度。
      peek: const Peek(
        peek: 1.5,
        ratio: 2,
        alignment: Alignment.bottomCenter,
      ),

      /// 轻松地通过此渐变 `Alignment` 和 `baseGradient` 参数给系统导航栏顶部左边缘一个明亮的高光。
      baseGradient: LinearGradient(
        begin: const Alignment(-1, -1),
        end: const Alignment(-0.97, 1),
        colors: [
          _primary.withWhite(100),
          _primary,
          _primary.withBlack(50),
        ],
      ),
    );
  }

  /// ### 🔳 Surface 作为窗口
  Surface _surfaceAsWindow(
    BuildContext context, {
    required Widget child,
  }) {
    return Surface(
      child: child,
      duration: _DURATION,
      curve: _CURVE,
      width: _width * 0.8,
      height: _height * 0.75,
      padding: const EdgeInsets.all(50),
      shape: Shape(
        // childScale: 0.75,
        shapeScaleMaterial: 0.7,
        // corners: (_isExampleBeveled)
        //     ? CornerSpec.BIBEVELED_50_FLIP
        //     : CornerSpec.CIRCLE,
        corners: CornerSpec(
          topLeft: (_isExampleBeveled) ? Corner.BEVEL : Corner.NONE,
          topRight: (_isExampleBeveled) ? Corner.NONE : Corner.ROUND,
          bottomRight: (_isExampleBeveled) ? Corner.SQUARE : Corner.BEVEL,
          bottomLeft: (_isExampleBeveled) ? Corner.ROUND : Corner.SQUARE,
          radius: BorderRadius.all(Radius.circular(35)),
        ),
        // baseCorners: CornerSpec(
        //   topLeft: (_isExampleBeveled) ? Corner.BEVEL : Corner.SQUARE,
        //   topRight: Corner.NONE,
        //   radius: BorderRadius.all(Radius.circular(165)),
        // ),
      ),
      peek: Peek(
        peek: 20,
        ratio: (_isExampleBeveled) ? 2.5 : 5,
        alignment:
            (_isExampleBeveled) ? Alignment.bottomRight : Alignment.topCenter,
      ),
      tapSpec: const TapSpec(
        // tappable: false,
        inkSplashColor: Colors.deepPurpleAccent,
      ),
      filter: Filter(
        // filteredLayers: FilterSpec.TRILAYER,
        filteredLayers: Filter.NONE, // 覆盖 `radii` 下面
        radiusBase: 1.0,
        radiusMaterial: 2.0,
        // radiusChild: 20.0,

        /// `filteredLayers: FilterSpec.NONE` 上面所以 `specRadius` == 0,
        /// 但是 `SurfaceLayer` 仍然交付。
        effect: (double specRadius, SurfaceLayer layerForRender) =>

            /// 覆盖上面的 `filteredLayers` 和 `radii`
            // FX.b(specRadius), // 但是当 `FilterSpec.NONE` -> `specRadius` == 0,所以
            FX.b(layerForRender == SurfaceLayer.CHILD ? specRadius : 2.5),
      ),
      baseColor: Colors.black38,
      gradient: LinearGradient(
        begin: Alignment.topLeft,
        end: Alignment.bottomRight,
        colors: (_isExampleBeveled)
            ? [
                _primary.withWhite(50).withOpacity(0.5),
                _primary.withBlack(50).withOpacity(0.5)
              ]
            : [
                _accent.withWhite(50).withOpacity(0.5),
                _accent.withBlack(50).withOpacity(0.5)
              ],
      ),
    );
  }

  /// ### ✂ 状态控制按钮
  Widget _stateControlButton({bool isShadow = false}) {
    double top = (_isExampleBeveled) ? _height * 0.1 : (_height * 0.1) / 2;
    double left = (_isExampleBeveled) ? _width / 7 : (_width / 7) / 3;
    Color color = (_isExampleBeveled) ? _accent : _primary;

    return AnimatedPositioned(
      duration: _DURATION * 4,
      curve: Curves.elasticOut,
      top: (isShadow) ? top + 1 : top,
      left: (isShadow) ? left + 1 : left,

      /// 这个按钮将控制我们的主要中央 [surfaceAsWindow] 的状态。
      ///
      /// `bool _isExampleBeveled` 在整个构建中用于控制外观。
      child: IconButton(
        icon: Icon(
          (_isExampleBeveled) ? Icons.add_box_rounded : Icons.cut_sharp,
        ),
        color: (isShadow) ? color.withBlack(75) : color,
        iconSize: 50,
        onPressed: () => setState(() => _isExampleBeveled = !_isExampleBeveled),
      ),
    );
  }

  /// 🔘 Surface 作为 FAB
  Surface _surfaceAsFAB({
    required Set<SurfaceLayer> filteredLayers,
    required String passedString,
  }) {
    return Surface(
      /// `surfaceAsPopup` 是一个叠加窗口,但如果未考虑 `_showExamplePopup`,
      /// 则 FABs 仍然会在其上方。
      width: (_showExamplePopup) ? 0 : 175,
      height: (_showExamplePopup) ? 0 : 175,
      padding: const EdgeInsets.all(10),
      // padLayer: SurfaceLayer.MATERIAL,  // 默认为 [SurfacePadding.PAD_CHILD]。
      duration: _DURATION,
      peek: const Peek(
        peek: 30,
        // alignment: Alignment.bottomCenter,
        // ratio: 1.25,
      ),

      shape: const Shape(
        corners: CornerSpec.CIRCLE,
        baseBorder: BorderSide(color: Colors.black38, width: 5.0),
        border: BorderSide.none,
      ),

      /// 透明颜色允许在这些示例情况下纯粹看到模糊效果。
      color: Colors.transparent,
      // color: Colors.white12,

      /// 使用 [_stateControlButton] 时的有趣颜色交换。
      baseColor: (_isExampleBeveled)
          ? _accent.withWhite(25).withOpacity(0.25)
          : _primary.withWhite(25).withOpacity(0.25),
      filter: Filter(
        filteredLayers: filteredLayers,
        // 声明 `radiusMap` 就像显式声明这些双精度值一样:
        // baseRadius: 3.0,
        // materialRadius: 15.0,
        radiusMap: const {
          SurfaceLayer.BASE: 3.0,
          SurfaceLayer.MATERIAL: 15.0,
        },
      ),

      /// 强制性计数器示例实现;
      tapSpec: TapSpec(
        // tappable: false, // `true` by default
        providesFeedback: true, // `false` by default
        onTap: _incrementCounter,
      ),

      /// 加号图标和标签
      child: Column(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          const Flexible(child: Icon(Icons.add, color: Colors.white)),
          Flexible(
            child: Text(
              passedString,
              textAlign: TextAlign.center,
              style: const TextStyle(fontSize: 12, color: Colors.white),
            ),
          )
        ],
      ),
    );
  }

  /// ❗ Surface 作为弹出窗口
  Surface _surfaceAsPopup() {
    return Surface(
      /// 覆盖大部分屏幕
      width: (_showExamplePopup) ? _width - 50 : 0,
      height: (_showExamplePopup) ? _height / 2 : 0,
      padding: const EdgeInsets.all(50),
      shape: Shape(
        // childScale: 1.0,
        // materialScale: 1.0,
        // childScale: 0.5,
        // materialScale: 0.8,
        padLayer: SurfaceLayer.MATERIAL, // 区分的层用于 Filter
        corners: CornerSpec.beveledWith(
          topLeft: Corner.SQUARE,
          topRight: Corner.ROUND,
          bottomRight: Corner.ROUND,
          radius: BorderRadius.vertical(
            bottom: Radius.elliptical(200, 40),
            top: Radius.elliptical(80, 200),
          ),
        ),
      ),
      duration: _DURATION,
      curve: _CURVE,

      /// 从 Material 原色中随机选择颜色
      color: Colors.primaries[Random().nextInt(Colors.accents.length)]
          .withBlack(75)
          .withOpacity(0.4),
      baseColor: Colors.black38, // 默认为 `ColorScheme.primaryVariant`

      /// 当 [_surfaceAsPopup] 隐藏 `!(_showExamplePopup)` 时,给一个更厚的边框,
      /// 结果是在进入动画期间的一个整洁的扩展。
      peek: Peek(
        // peek: 0,
        peek: (_showExamplePopup) ? 25 : 30,
        ratio: (_showExamplePopup) ? 4 : 7,
        alignment: Alignment.topLeft,
      ),

      tapSpec: TapSpec(
        /// 此处的 onTap 将刷新构建并给出新的随机颜色
        onTap: () => setState(() {}),
        providesFeedback: true,
      ),

      // Child 和 Material filters 占据相同的空间,除非
      // `ShapeSpec(padLayer: SurfaceLayer.MATERIAL)`
      filter: const Filter(
        // filteredLayers: Filter.TRILAYER,
        filteredLayers: Filter.BASE_AND_MATERIAL,
        radiusMap: {
          SurfaceLayer.BASE: 3.0,
          SurfaceLayer.MATERIAL: 4.0,
          // SurfaceLayer.CHILD: 20.0
        },
      ),

      /// [_surfaceAsPopup] 的内容
      child: Container(
        padding: const EdgeInsets.all(20),
        color: Colors.black12,
        alignment: Alignment.center,
        child: const FittedBox(
          /// 使用 FittedBox,可以自由使用巨大的 fontSize。
          child: Text(
            'p o p u p',
            style: TextStyle(color: Colors.white, fontSize: 100),
          ),
        ),
      ),
    );
  }
}

更多关于Flutter未知功能插件surface的介绍与使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter未知功能插件surface的介绍与使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


Flutter未知功能插件Surface的介绍与使用

在Flutter开发中,插件(Plugins)是连接Flutter代码与原生平台(如Android和iOS)功能的重要桥梁。尽管Flutter官方库和社区已经提供了大量的插件来满足常见的开发需求,但有时候开发者可能会遇到一些特定的、未被广泛认知的“未知功能”需求,这时候就需要创建或使用自定义插件。

surface这个词在Flutter官方文档和插件库中并不是一个具体的插件名称,但我们可以将其理解为一个泛指,代表那些可能涉及到底层渲染、图形处理、或者与原生平台深度交互的自定义插件。下面我将介绍如何创建并使用一个假设的“Surface”插件,这个插件可能用于在Flutter应用中展示一些自定义渲染的内容。

1. 创建插件项目

首先,使用Flutter的插件生成工具创建一个新的插件项目。假设我们命名为surface_plugin

flutter create --org com.example --template=plugin surface_plugin

2. 实现Android平台代码

surface_plugin/android/src/main/kotlin/com/example/surface_plugin目录下,创建一个SurfaceView.kt文件,用于实现自定义的SurfaceView。

package com.example.surface_plugin

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.SurfaceHolder
import android.view.SurfaceView

class CustomSurfaceView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : SurfaceView(context, attrs, defStyleAttr), SurfaceHolder.Callback {

    private val paint: Paint = Paint()

    init {
        holder.addCallback(this)
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        // 在这里开始绘制
        val canvas = holder.lockCanvas()
        if (canvas != null) {
            draw(canvas)
            holder.unlockCanvasAndPost(canvas)
        }
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
        // 处理Surface变化
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        // 清理资源
    }

    private fun draw(canvas: Canvas) {
        // 简单的绘制示例
        paint.color = android.graphics.Color.RED
        canvas.drawRect(0f, 0f, canvas.width.toFloat(), canvas.height.toFloat(), paint)
    }
}

然后在SurfacePlugin.kt中注册这个自定义View。

package com.example.surface_plugin

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.MethodChannel

class SurfacePlugin: FlutterPlugin, ActivityAware {
  private var channel: MethodChannel? = null
  private var context: Context? = null

  override fun onAttachedToEngine(@NonNull flutterEngine: FlutterEngine) {
    channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "surface_plugin")
    context = flutterEngine.context
    channel?.setMethodCallHandler { call, result ->
      if (call.method == "showSurface") {
        // 在这里你可以创建并显示CustomSurfaceView
        // 例如,你可以通过某种方式将View添加到当前的Activity或Fragment中
        result.success(null)
      } else {
        result.notImplemented()
      }
    }
  }

  override fun onDetachedFromEngine(@NonNull flutterEngine: FlutterEngine) {
    channel?.setMethodCallHandler(null)
    channel = null
  }

  override fun onAttachedToActivity(binding: ActivityPluginBinding) {
    // 可以在这里保存Activity引用,用于后续操作
  }

  override fun onDetachedFromActivityForConfigChanges() {
    // 配置变化时处理
  }

  override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
    // 配置恢复时处理
  }

  override fun onDetachedFromActivity() {
    // Activity销毁时处理
  }
}

3. 实现iOS平台代码

由于iOS与Android的差异,你可能需要实现一个自定义的UIViewUIViewController来替代Android中的SurfaceView。这里仅提供一个简单的框架,具体实现取决于你的需求。

surface_plugin/ios/Classes/SurfacePlugin.swift中:

import Flutter
import UIKit

public class SurfacePlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "surface_plugin", binaryMessenger: registrar.messenger())
    let instance = SurfacePlugin()
    instance.setup(channel: channel, registrar: registrar)
  }

  private var channel: FlutterMethodChannel?

  private func setup(channel: FlutterMethodChannel, registrar: FlutterPluginRegistrar) {
    self.channel = channel
    channel?.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
      switch call.method {
      case "showSurface":
        // 在这里创建并展示自定义的UIView或UIViewController
        result(nil)
      default:
        result(.notImplemented)
      }
    })
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    // 其他方法调用处理
  }
}

4. 在Flutter中使用插件

在Flutter项目中,添加对surface_plugin的依赖,并在Dart代码中调用。

dependencies:
  surface_plugin:
    path: ../path_to_your_plugin

然后在Dart代码中:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Surface Plugin Demo'),
        ),
        body: Center(
          child: ElevatedButton(
            onPressed: _showSurface,
            child: Text('Show Surface'),
          ),
        ),
      ),
    );
  }

  void _showSurface() async {
    try {
      await SurfacePlugin.showSurface();
    } catch (e) {
      print(e);
    }
  }
}

请注意,以上代码是一个简化的示例,旨在展示如何开始创建和使用一个自定义的Flutter插件。在实际项目中,你可能需要处理更多的细节,比如生命周期管理、错误处理、以及更复杂的原生代码实现。

回到顶部