Instead of a CustomPaint
, use a ClipPath
widget.
Using the same path as your CustomPaint
:

Full source code
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ClipPath(
clipper: CustomClipperImage(),
child: Image.asset('images/abstract.jpg'),
),
),
);
}
}
class CustomClipperImage extends CustomClipper<Path> {
@override
getClip(Size size) {
return Path()
..moveTo(size.width / 1, 0)
..lineTo(0, size.height)
..lineTo(size.height, size.width)
..close();
}
@override
bool shouldReclip(CustomClipper oldClipper) {
return false;
}
}
Additional example with a white border and scaling the image.

Full source code
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width * .8;
final height = width * .7;
return Scaffold(
body: Container(
color: Colors.black54,
child: Center(
child: Stack(
children: [
CustomPaint(
painter: MyPainter(),
child: Container(width: width, height: height),
),
ClipPath(
clipper: CustomClipperImage(),
child: Transform.scale(
scale: 3,
child: Image.asset('images/abstract.jpg',
width: width, height: height),
),
),
],
),
),
),
);
}
}
class CustomClipperImage extends CustomClipper<Path> {
@override
getClip(Size size) {
return Path()
..moveTo(size.width, 0)
..lineTo(0, size.height)
..lineTo(size.height, size.height)
..close();
}
@override
bool shouldReclip(CustomClipper oldClipper) {
return false;
}
}
class MyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final path = Path()
..moveTo(size.width, 0)
..lineTo(0, size.height)
..lineTo(size.height, size.height)
..close();
final paint = Paint()
..color = Colors.white
..strokeWidth = 8.0
..style = PaintingStyle.stroke;
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Solution with zoom and pan on the image
Using a GestureDetector
to detect the change of scale
and localFocalPoint
. And then using a Matrix4
to Transform
our Image.asset
:

Full source code:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
class HomePage extends HookWidget {
@override
Widget build(BuildContext context) {
final _width = MediaQuery.of(context).size.width * .8;
final _height = _width * .6;
final _zoom = useState<double>(1.0);
final _previousZoom = useState<double>(1.0);
final _offset = useState<Offset>(Offset.zero);
final _previousOffset = useState<Offset>(Offset.zero);
final _startingFocalPoint = useState<Offset>(Offset.zero);
return Scaffold(
body: Container(
color: Colors.black54,
child: Center(
child: GestureDetector(
onScaleStart: (details) {
_startingFocalPoint.value = details.localFocalPoint;
_previousOffset.value = _offset.value;
_previousZoom.value = _zoom.value;
},
onScaleUpdate: (details) {
_zoom.value = _previousZoom.value * details.scale;
final Offset normalizedOffset =
(_startingFocalPoint.value - _previousOffset.value) /
_previousZoom.value;
_offset.value =
details.localFocalPoint - normalizedOffset * _zoom.value;
},
child: Stack(
children: [
CustomPaint(
painter: MyPainter(),
child: Container(width: _width, height: _height),
),
ClipPath(
clipper: CustomClipperImage(),
child: Transform(
transform: Matrix4.identity()
..translate(_offset.value.dx, _offset.value.dy)
..scale(_zoom.value),
// ..rotateZ(_rotation.value),
child: Image.asset('images/milkyway.jpg',
width: _width, height: _height),
),
),
],
),
),
),
),
);
}
}
class CustomClipperImage extends CustomClipper<Path> {
@override
getClip(Size size) {
return Path()
..moveTo(size.width, 0)
..lineTo(0, size.height)
..lineTo(size.height, size.height)
..close();
}
@override
bool shouldReclip(CustomClipper oldClipper) {
return false;
}
}
class MyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final path = Path()
..moveTo(size.width, 0)
..lineTo(0, size.height)
..lineTo(size.height, size.height)
..close();
final paint = Paint()
..color = Colors.white
..strokeWidth = 8.0
..style = PaintingStyle.stroke;
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}