问题描述
我是 Flutter 的新手,我正在尝试编写一个库来允许用户平移/缩放他们的个人资料图片.
为了使其视觉化,我想用倒置"ClipOval 堆叠他们的图片,以显示边界.
到目前为止,这是我得到的结果:
这显示了边界,但这对用户不友好,我想反转"ClipOval,以便剪辑的中心清晰"并且外部变灰(类似于面具).
有什么办法可以实现吗?
这是我到目前为止的代码(其中一部分来自
(我是通过 Photoshop 制作的,因为我不知道如何使用 Flutter 实现)
非常感谢您的帮助.
还有其他几种方法可以做到这一点 - 您可以简单地使用带有圆圈的路径在 CustomCanvas 中绘制叠加层矩形,因为您真正需要的是一个带有孔的矩形半透明矩形.但是您也可以使用 CustomClipper
,它可以在将来为您提供更大的灵活性,而无需手动绘制内容.
void main() {int i = 0;运行应用程序(新材料应用程序(主页:新的安全区(孩子:新堆栈(孩子们:<小部件>[新的手势检测器(onTap: () {打印(点击!${i++}");},孩子:新容器(颜色:Colors.white,孩子:新中心(孩子:新容器(宽度:400.0,高度:300.0,颜色:Colors.red.shade100,),),),),新的忽略指针(孩子:新的剪辑路径(剪刀:新的 InvertedCircleClipper(),孩子:新容器(颜色:新的 Color.fromRGBO(0, 0, 0, 0.5),),),)],),),));}类 InvertedCircleClipper 扩展了 CustomClipper{@覆盖路径getClip(大小尺寸){返回新路径()..addOval(new Rect.fromCircle(中心:新的偏移量(size.width/2,size.height/2),半径:size.width * 0.45))..addRect(new Rect.fromLTWH(0.0, 0.0, size.width, size.height))..fillType = PathFillType.evenOdd;}@覆盖bool shouldReclip(CustomClipper oldClipper) =>错误的;}
IgnorePointer
是必需的,否则事件将不会通过半透明部分传播(假设您需要触摸事件).
这是如何工作的,clipPath使用的Path
是中间的一个圆(需要手动调整大小),一个矩形占据整个大小.fillType = PathFillType.evenOdd
很重要,因为它告诉路径的填充应该在圆形和矩形之间.
如果您想改用 customPainter,路径将相同,您只需绘制它即可.
这一切都会导致:
I am new to Flutter and I am trying to write a library to allow users to pan/zoom their profile picture.
In order to make it visual, I would like to stack their picture with an "inverted" ClipOval, to show the boundaries.
So far, this is the result I obtain:
This shows the boundaries but this is not user friendly and I would like to "invert" the ClipOval so that the center of the clip is "clear" and the outside is grayed out (something like a mask).
Is there any way to achieve this?
Here is the code I have so far (part of it comes from flutter_zoomable_image):
import 'dart:ui' as ui;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class ImagePanner extends StatefulWidget {
ImagePanner(this.image, {Key key}) : super(key: key);
/// The image to be panned
final ImageProvider image;
@override
_ImagePannerState createState() => new _ImagePannerState();
}
class _ImagePannerState extends State<ImagePanner> {
ImageStream _imageStream;
ui.Image _image;
double _zoom = 1.0;
Offset _offset = Offset.zero;
double _scale = 16.0;
@override
void didChangeDependencies() {
_resolveImage();
super.didChangeDependencies();
}
@override
void reassemble() {
_resolveImage();
super.reassemble();
}
@override
Widget build(BuildContext context) {
if (_image == null) {
return new Container();
}
return new Container(
width: double.INFINITY,
color: Colors.amber,
child: new Padding(
padding: new EdgeInsets.all(50.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new AspectRatio(
aspectRatio: 1.0,
child: new Stack(
children: [
_child(),
new Opacity(
opacity: 0.5,
child: new ClipOval(
child: new Container(
color: Colors.black,
),
),
),
],
),
),
],
)),
);
}
Widget _child() {
Widget bloated = new CustomPaint(
child: new Container(),
painter: new _ImagePainter(
image: _image,
offset: _offset,
zoom: _zoom / _scale,
),
);
bloated = new Stack(
children: [
new Container(
),
bloated
],
);
return new Transform(
transform: new Matrix4.diagonal3Values(_scale, _scale, _scale),
child: bloated);
}
void _resolveImage() {
_imageStream = widget.image.resolve(createLocalImageConfiguration(context));
_imageStream.addListener(_handleImageLoaded);
}
void _handleImageLoaded(ImageInfo info, bool synchronousCall) {
print("image loaded: $info $synchronousCall");
setState(() {
_image = info.image;
});
}
}
class _ImagePainter extends CustomPainter {
const _ImagePainter({this.image, this.offset, this.zoom});
final ui.Image image;
final Offset offset;
final double zoom;
@override
void paint(Canvas canvas, Size size) {
paintImage(canvas: canvas, rect: offset & (size * zoom), image: image);
}
@override
bool shouldRepaint(_ImagePainter old) {
return old.image != image || old.offset != offset || old.zoom != zoom;
}
}
The outcome I would like to obtain is the following so that users will directly see the boundaries and will be able to center, pan, zoom their profile picture INSIDE the oval.
(I made this via Photoshop, since I don't know how to achieve this with Flutter)
Many thanks for your help.
There's a couple other ways you could do this - you could simply draw an overlay in a CustomCanvas using a path that has a circle & rectangle, as all you really need is a rectangular semi-transparent rectangle with a hole in it. But you can also use a CustomClipper
which gives you more flexibility in the future without having to draw stuff manually.
void main() {
int i = 0;
runApp(new MaterialApp(
home: new SafeArea(
child: new Stack(
children: <Widget>[
new GestureDetector(
onTap: () {
print("Tapped! ${i++}");
},
child: new Container(
color: Colors.white,
child: new Center(
child: new Container(
width: 400.0,
height: 300.0,
color: Colors.red.shade100,
),
),
),
),
new IgnorePointer(
child: new ClipPath(
clipper: new InvertedCircleClipper(),
child: new Container(
color: new Color.fromRGBO(0, 0, 0, 0.5),
),
),
)
],
),
),
));
}
class InvertedCircleClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
return new Path()
..addOval(new Rect.fromCircle(
center: new Offset(size.width / 2, size.height / 2),
radius: size.width * 0.45))
..addRect(new Rect.fromLTWH(0.0, 0.0, size.width, size.height))
..fillType = PathFillType.evenOdd;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
IgnorePointer
is needed, or events won't be propagated through the semi-transparent part (assuming you need touch events).
How this works is that the Path
used by clipPath is a circle in the middle (you need to adjust the size manually) with a rectangle taking up the entire size. fillType = PathFillType.evenOdd
is important because it tells the path's fill should be between the circle and the rectangle.
If you wanted to use a customPainter instead, the path would be the same and you'd just draw it instead.
This all results in this:
这篇关于Flutter:倒置的ClipOval的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!