本文将讲解如何通过 Flutter 实现点击放大图片的功能。首先,我们根据之前的文章Flutter系列 - 开始你的第一个应用新建一个名为 flutter_scale_img 的应用。
flutter create flutter_scale_img
然后我们通过 Android Studio 启动 macOS(desktop) 的应用。
启动之后,是我们很熟悉的一个 counter 效果👇:
接下来,我们就是改写 main.dart 文件,实现点击放大图片的效果。

如果不想在本地开启一个应用的话,可以通过在线
IDE实现效果,推荐 flutterlab
我们准备的图片资源是 AI 生成的图片👇
👌,下面,我们一步一步来实现,文末会展示完整的 main.dart 的代码。
Widget 上获取鼠标点相对自身 dx 和 dy
在指定的挂件上点击,获取当前的鼠标点相对该挂件左上角的位置 dx 和 dy。我们可以通过 localPosition 来实现:
class _MyHomePageState extends State<MyHomePage> {
double dx = 0.0;
double dy = 0.0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
width: 200.0,
height: 200.0,
color: Colors.blue,
child: GestureDetector(
onTapDown: (TapDownDetails details) {
Offset localPosition = details.localPosition;
setState(() {
dx = localPosition.dx;
dy = localPosition.dy;
});
},
child: Text('点击的相对位置 $dx, $dy'),
),
),
),
);
}
}
当我们在 Container 这个挂件上点击的时候,会获取到该点相对其左上角的偏移坐标,效果如下:
⚠️ 关于
GestureDetector挂件,内容比较丰富也很实用,我们后面会单独出一篇文章来讲解
溢出隐藏
我们放大图片,在图片太大,会超出容器,但是我们又不需要展示溢出部分,所以需要对溢出的部分进行隐藏。
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 正常比例的图
Container(
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
border: Border.all(
width: 2,
color: Colors.red,
)
),
child: Transform.scale(
scale: 1,
child: const Image(
image: AssetImage('assets/images/background.webp'),
fit: BoxFit.cover,
),
),
),
const SizedBox(
width: 100.0,
),
// 溢出
Container(
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
border: Border.all(
width: 2,
color: Colors.red,
)
),
child: Transform.scale(
scale: 2,
child: const Image(
image: AssetImage('assets/images/background.webp'),
fit: BoxFit.cover,
),
),
),
const SizedBox(
width: 100.0,
),
// 隐藏溢出部分
Container(
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
border: Border.all(
width: 2,
color: Colors.red,
)
),
child: ClipRect(
child: Transform.scale(
scale: 2,
child: const Image(
image: AssetImage('assets/images/background.webp'),
fit: BoxFit.cover,
),
),
),
),
],
),
),
);
}
}
上面的代码,效果如下👇
图片资源,我们直接引用了本地的资源,这需要在根目录下新建 assets/images 文件夹,并在 pubspec.yaml 下配置:
flutter:
assets:
- assets/images/
当然,为了方便,读者可以直接直接通过 Image.network(url) 来引用线上的图片资源。
上图中,从左到右的效果是:
- 左边:正常比例的图,缩放是
1 - 中间:正常比例的图,缩放是
2 - 右边:正常比例的图,缩放是
2,通过ClipRect挂件对溢出的内容部分进行隐藏
更改原点
当我们放大图片的时候,我们是针对图片的原点 origin 进行放大的,所以,我们需要变更下图片的原点。
class _MyHomePageState extends State<MyHomePage> {
double dx = 100.0;
double dy = 100.0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GestureDetector(
onTapDown: (TapDownDetails details) {
Offset localPosition = details.localPosition;
setState(() {
dx = localPosition.dx;
dy = localPosition.dy;
});
},
child: Transform.scale(
scale: 1,
origin: Offset(dx, dy),
child: Stack(
children: [
Container(
width: 200.0,
height: 200.0,
color: Colors.blue,
),
Positioned(
top: dy - 6.0 / 2,
left: dx - 6.0 / 2,
child: Container(
width: 6.0,
height: 6.0,
color: Colors.red,
),
),
],
),
),
),
),
);
}
}
在上面的代码中,我们进行了 Position 定位,为了就是在视觉上看到原点的变化。这里的原点更改即鼠标点击点的位置。
最终效果
嗯~点击放大图片我们使用到技术点,上面已经分解讲解完成。
现在,我们整合上面的知识点。实现如下的效果👇
整个 main.dart 文件的代码如下👇
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// 该挂件是应用的根挂件
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter scale image',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
double offsetOriginX = 0.0;
double offsetOriginY = 0.0;
double scale = 1.0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ClipRect(
child: GestureDetector(
onTapDown: (TapDownDetails details) {
Offset localPosition = details.localPosition;
// 点击点相对原点的实际距离,非缩放后的距离
double diffDx = (localPosition.dx - (200.0 / 2 + offsetOriginX)) / scale;
double diffDy = (localPosition.dy - (200.0 / 2 + offsetOriginY)) / scale;
setState(() {
scale = scale * 1.5;
offsetOriginX = offsetOriginX + diffDx;
offsetOriginY = offsetOriginY + diffDy;
});
},
child: Container(
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
border: Border.all(
width: 2.0,
color: Colors.red,
)
),
child: Transform.scale(
scale: scale,
origin: Offset(offsetOriginX, offsetOriginY),
child: const Image(
image: AssetImage('assets/images/background.webp'),
fit: BoxFit.cover,
),
),
),
),
)
),
);
}
}
上面的代码中,我们需要留意计算 origin,因为这里我们只是需要计算针对 origin (200.0 / 2, 200.0 / 2) 的相对偏移,所以,我们需要考虑实际的相对位移即可。
【完✅】感谢阅读🌹