I would like to be able to build collages and for that I tried using CustomPaint
to draw the shape and then filling that shape with an image
. This is what I tried:
Container(
height: 300,
width: 300,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
border: Border.all(color: Colors.white, width: 5),
),
child: CustomPaint(
painter: DrawTriangleShape(color: Colors.blue),
child: Image.asset('assets/images/exampleImage.jpg'),
),
),
And my DrawTriangleShape
:
class DrawTriangleShape extends CustomPainter {
Paint painter;
DrawTriangleShape({color: Colors}) {
painter = Paint()
..color = color
..style = PaintingStyle.fill;
}
@override
void paint(Canvas canvas, Size size) {
var path = Path();
path.moveTo(size.width / 1, 0);
path.lineTo(0, size.height);
path.lineTo(size.height, size.width);
path.close();
canvas.drawPath(path, painter);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
The outcome:
As you can see it is not really the desired outcome as the image is not filling the shape. I didn't find anything on this. Is it even possible? And if not, what would be the best approach to build collages with custom shapes?
Let me know if you need more info!
Instead of a CustomPaint
, use a ClipPath
widget.
Using the same path as your CustomPaint
:
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;
}
}
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;
}
Using a GestureDetector
to detect the change of scale
and localFocalPoint
. And then using a Matrix4
to Transform
our Image.asset
:
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;
}