likes
comments
collection
share

Flutter 组件集录 | RawMagnifier 组件 - 拿起你的八倍镜

作者站长头像
站长
· 阅读数 20
1. 前言

今天看 Flutter 源码,偶然发现 Magnifier 组件,这单词不就是 放大镜 嘛! 再结合新版 Flutter 中输入文本的放大镜效果,直觉告诉我这玩意应该可以放大任何组件。如下所示,背景是一张图片,使用 RawMagnifier 实现了点击拖拽局部放大的效果,看起来还是蛮酷的:

Flutter 组件集录 | RawMagnifier 组件 - 拿起你的八倍镜

另外,也可以自定义放大镜的形状,如下的五角星:

Flutter 组件集录 | RawMagnifier 组件 - 拿起你的八倍镜

该组件已收录入 FlutterUnit ,可以在应用中查看相关源码:

桌面端移动端
Flutter 组件集录 | RawMagnifier 组件 - 拿起你的八倍镜Flutter 组件集录 | RawMagnifier 组件 - 拿起你的八倍镜

2. RawMagnifier 组件的简单使用

下面来简单使用一下:案例中通过 Stack 将 Image 和 RawMagnifier 叠放在一起,并且居中对齐。可以看到 RawMagnifier 组件的展示内容是对应图片位置的局部放大图。是不是用起来非常简单,就能实现很酷的效果:

Flutter 组件集录 | RawMagnifier 组件 - 拿起你的八倍镜

class MagnifierExampleApp extends StatelessWidget{
 final Size magnifierSize =  const Size(120, 120);

  const MagnifierExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Stack(
          alignment: Alignment.center,
          children: <Widget>[
            Image.asset('assets/images/sabar_bar.webp'),
            _buildMagnifier(),
          ],
        ),
      ),
    );
  }
  
  Widget _buildMagnifier(){
    return RawMagnifier(
      decoration: const MagnifierDecoration(
        shape: CircleBorder(
          side: BorderSide(color: Colors.blue, width: 2),
        ),
      ),
      size: magnifierSize,
      magnificationScale: 3,
    );
  }
}

强调一点,它可以放大和其叠放的 任何组件 ,比如其下放置一个文本组件,展示 张风捷特烈

Flutter 组件集录 | RawMagnifier 组件 - 拿起你的八倍镜


3. RawMagnifier 组件的构造函数

了解了简单使用,下面瞄一眼 RawMagnifier 组件源码中定义的入参,它继承自 StatelessWidget ,看起来并不是很复杂。

Flutter 组件集录 | RawMagnifier 组件 - 拿起你的八倍镜

属性名类型介绍默认值
childWidget?子组件null
decorationMagnifierDecoration装饰对象MagnifierDecoration()
magnificationScaledouble放大倍数1
sizeSize放大镜尺寸required
focalPointOffsetSize中心偏移量Offset

其中尺寸和放大倍数非常好理解,如下 size 改成 150*150、放大倍数 magnificationScale 改成 8 倍 :

Flutter 组件集录 | RawMagnifier 组件 - 拿起你的八倍镜

focalPointOffset 表示放大中心的偏移量,如下所示偏移量设为 Offset(-10,0), 效果上来看局部区域显示的靠左一点的内容 :

Flutter 组件集录 | RawMagnifier 组件 - 拿起你的八倍镜


decoration 是一个比较重要的属性,类型为 MagnifierDecoration 。可以配置装饰效果:从源码来看,可以定义放大镜的透明度、阴影和形状:

Flutter 组件集录 | RawMagnifier 组件 - 拿起你的八倍镜

如下所示,0.9 的透明度可以看出一点底部的图案,去掉了边线。添加阴影:

Flutter 组件集录 | RawMagnifier 组件 - 拿起你的八倍镜

Widget _buildMagnifier(){
  return RawMagnifier(
    decoration:  MagnifierDecoration(
      opacity: 0.9,
      shadows: [
        BoxShadow(
          offset: Offset(1,1),
          blurRadius: 4,
          spreadRadius: 6,
          color: Colors.black.withOpacity(0.1)
        )
      ],
      shape: CircleBorder(),
    ),
    size: magnifierSize,
    focalPointOffset:  Offset(-10, 0),
    magnificationScale: 3,
  );
}

除此之外, MagnifierDecoration 还有一个 shape 属性,类型为 ShapeBorder,可以设置放大镜的形状。在 《【Flutter高级玩法-shape】Path在手,天下我有》 一文中详细介绍了该类型的使用。比如下面自定义一个五角星的形状:

Flutter 组件集录 | RawMagnifier 组件 - 拿起你的八倍镜

class _StarShapeBorder extends ShapeBorder {
  final Path _path = Path();

  @override
  EdgeInsetsGeometry get dimensions => EdgeInsets.zero;

  @override
  Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
    return Path();
  }

  @override
  Path getOuterPath(Rect rect, {TextDirection? textDirection}) =>
      nStarPath(5, rect.height / 2, rect.height / 2 * 0.5,
          dx: rect.width / 2, dy: rect.height / 2);

  @override
  void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {
    Paint paint = Paint()..style=PaintingStyle.stroke..color=Colors.blue..strokeWidth =2;
    canvas.drawPath(getOuterPath(rect), paint);
  }

  Path nStarPath(int num, double R, double r, {dx = 0, dy = 0}) {
    double perRad = 2 * pi / num;
    double radA = perRad / 2 / 2;
    double radB = 2 * pi / (num - 1) / 2 - radA / 2 + radA;
    _path.moveTo(cos(radA) * R + dx, -sin(radA) * R + dy);
    for (int i = 0; i < num; i++) {
      _path.lineTo(
          cos(radA + perRad * i) * R + dx, -sin(radA + perRad * i) * R + dy);
      _path.lineTo(
          cos(radB + perRad * i) * r + dx, -sin(radB + perRad * i) * r + dy);
    }
    _path.close();
    return _path;
  }

  @override
  ShapeBorder scale(double t) => this;
}

4. 手势交互

上面就是 RawMagnifier 组件的使用方式,那如何实现按下展示放大镜、拖拽更新位置、抬起取消呢?答案很简单:监听手势事件。首先,由于需要在手势交互中更新位置和显示信息,所以需要 StatefulWidget 进行处理;然后添加如下两个状态数据用于表示放大镜位置和是否显示:

Offset _dragGesturePosition = Offset.zero;
bool _show = false;

然后通过 GestureDetector 组件监听拖拽事件、通过 Positioned 组件控制放大镜的位置:

Flutter 组件集录 | RawMagnifier 组件 - 拿起你的八倍镜

最后在手势交互中更新两个状态数据即可:

void _onPanDown(DragDownDetails details) {
  _dragGesturePosition = details.localPosition-Offset(magnifierSize.width/2,magnifierSize.height/2);
  _show = true;
  setState(() {
  });
}
void _onPanEnd(DragEndDetails details) {
  setState(() => _show = false);
}
void _onPanUpdate(DragUpdateDetails details) {
  _dragGesturePosition = details.localPosition-Offset(magnifierSize.width/2,magnifierSize.height/2);
  setState(() {
  });
}
void _onPanCancel() {
  setState(() => _show = false);
}

5. Magnifier 组件

总的来说 RawMagnifier 的使用方式还是比较简单的,表现效果却非常炫酷。另外,基于 RawMagnifier 组件,官方还提供了一个 Magnifier 组件便于使用,从源码中可以看出它在构造函数在给了默认的参数:

Flutter 组件集录 | RawMagnifier 组件 - 拿起你的八倍镜

从构建逻辑中可以看出 Magnifier 组件只是借用了 RawMagnifier 组件,提供一个圆角矩形的装饰形状而言,没有什么非常特别的。

Flutter 组件集录 | RawMagnifier 组件 - 拿起你的八倍镜

至于 RawMagnifier 内部的实现原理,有机会再单独分析一下。那本文就到这里,谢谢观看~