떨림 튜토리얼: 이미지 확대경
43831 단어 dartappdevelopmenttutorialflutter
우리의 목표
상술한 묘사는 매우 광범위하다.돋보기의 주요 기능을 정의하여 자세한 내용을 살펴보겠습니다.
실시
우리는 우선 확대 가능성을 높이려는 이미지를 포함하는 샘플 화면을 실현한다.이를 위해, 우리는 가장 유행하는 테스트 이미지를 사용합니다: Lena Forsén, better known as Lenna.
@override
Widget build(BuildContext context) {
return Stack(
children: [
Image(
image: AssetImage('assets/lenna.png')
)
],
);
}
우리는 잠시 후에 터치 기포와 확대경을 위에 두기를 원하기 때문에 Stack
의 작은 부품을 사용한다.
거품을 만지다
현재 우리는 터치 거품이 필요하다. 사용자는 확대해야 할 이미지 부분에 위치를 정할 수 있어야 한다.
class _SampleImageState extends State<SampleImage> {
static const double touchBubbleSize = 20;
Offset position;
double currentBubbleSize;
bool magnifierVisible = false;
@override
void initState() {
currentBubbleSize = touchBubbleSize;
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
super.initState();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Magnifier(
position: position,
visible: magnifierVisible,
child: Image(
image: AssetImage('assets/lenna.png')
),
),
_getTouchBubble()
],
);
}
Positioned _getTouchBubble() {
return Positioned(
top: position == null ? 0 : position.dy - currentBubbleSize / 2,
left: position == null ? 0 : position.dx - currentBubbleSize / 2,
child: GestureDetector(
child: Container(
width: currentBubbleSize,
height: currentBubbleSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).accentColor.withOpacity(0.5)
),
)
)
);
}
우리는 이 정보를 확대경에 제공할 수 있도록 터치 기포의 위치를 저장해야 한다.이것은 확대경이 터치 기포로 확정된 특정 구역만 확대할 수 있도록 필요한 것이다.
우리는 또 하나의 터치 기포의 정적 크기가 있는데, 그것은 상호작용이 일어나지 않을 때 기포가 되돌아오는 기본적인 크기를 결정한다.사용자가 기포를 드래그할 때 실제 전류 크기가 달라질 수도 있습니다.드래그 이벤트를 강조하기 위해서 우리는 거품이 50% 증가하기를 희망한다.
우리는 위와 같이 그것을 실현할 수 있다. 기포를 만지는 시각적 묘사를 자신만의 방법으로 추출하기만 하면 이 방법은 예시 이미지의 작은 부품에 주재한다.그러나 가장 좋은 것은 put it into its own widget이다.
이렇게 하면 다음과 같이 보입니다.
import 'package:flutter/material.dart';
class TouchBubble extends StatelessWidget {
TouchBubble({
@required this.position,
this.bubbleSize
});
final Offset position;
final double bubbleSize;
@override
Widget build(BuildContext context) {
return Positioned(
top: position == null ? 0 : position.dy - bubbleSize / 2,
left: position == null ? 0 : position.dx - bubbleSize / 2,
child: GestureDetector(
child: Container(
width: bubbleSize,
height: bubbleSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).accentColor.withOpacity(0.5)
),
)
)
);
}
}
지금까지 줄곧 괜찮았다.그러나 상호작용의 가능성은 없다.
터치 기포는 Positioned
클래스 중 TouchBubble
소부품의 왼쪽 상단 속성의 반환값이기 때문에 왼쪽 상단에서 볼 수 있습니다.
돋보기에 상호작용을 추가하기 위해서, 우리는 TouchBubble
의 구조 함수에서 약간의 리셋을 예상합니다.
import 'package:flutter/material.dart';
class TouchBubble extends StatelessWidget {
TouchBubble({
this.position,
@required this.onStartDragging,
@required this.onDrag,
@required this.onEndDragging,
@required this.bubbleSize,
}) : assert(onStartDragging != null),
assert(onDrag != null),
assert(onEndDragging != null),
assert(bubbleSize != null && bubbleSize > 0);
final Offset position;
final double bubbleSize;
final Function onStartDragging;
final Function onDrag;
final Function onEndDragging;
@override
Widget build(BuildContext context) {
return Positioned(
...
child: GestureDetector(
onPanStart: (details) => onStartDragging(details.globalPosition),
onPanUpdate: (details) => onDrag(details.globalPosition),
onPanEnd: (_) => onEndDragging(),
...
)
);
}
}
현재 우리는 세 개의 새로운 추가 매개 변수가 있다. onStartDragging
, onDrag
과 onEndDragging
이다.이러한 함수로 트리거되는 동작은 부모 창의 작은 위젯에 정의됩니다.이 모든 주석(위치 제외, 환불이 있기 때문에)을 @required로 주석합니다. 이것은 IDE에 이 인자를 무시하면 경고를 보냅니다.또한 IDE에 대한 권장사항을 무시할 경우 올바른 위치에서 실행될 때 오류에 대한 단언을 제공합니다.onStartDragging
과 onDrag
은 새로운 절대(전역) 위치를 제공할 것으로 예상된다.onEndDragging
에는 매개변수가 필요하지 않습니다.
새로운 SampleImage
소부품은 다음과 같습니다.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_magnifier/touch_bubble.dart';
class SampleImage extends StatefulWidget {
@override
_SampleImageState createState() => _SampleImageState();
}
class _SampleImageState extends State<SampleImage> {
static const double touchBubbleSize = 20;
Offset position;
double currentBubbleSize;
bool magnifierVisible = false;
@override
void initState() {
currentBubbleSize = touchBubbleSize;
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
super.initState();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Image(
image: AssetImage('assets/lenna.png')
),
TouchBubble(
position: position,
bubbleSize: currentBubbleSize,
onStartDragging: _startDragging,
onDrag: _drag,
onEndDragging: _endDragging,
)
],
);
}
void _startDragging(Offset newPosition) {
setState(() {
magnifierVisible = true;
position = newPosition;
currentBubbleSize = touchBubbleSize * 1.5;
});
}
void _drag(Offset newPosition) {
setState(() {
position = newPosition;
});
}
void _endDragging() {
setState(() {
currentBubbleSize = touchBubbleSize;
magnifierVisible = false;
});
}
}
위치는 드래그 시작과 드래그 업데이트 시 업데이트됩니다.드래그가 시작되면,mag를 표시하고 크기를 50% 증가합니다.드래그가 끝나면 크기가 정상적으로 회복되고 확대경이 보이지 않습니다.
그러면 다음과 같은 동작으로 사용자 인터페이스가 생성됩니다.
확대경
우리가 실현하기 전에 확대가 실제로 무엇을 의미하는지 먼저 생각해 보자.우리는 그림을 한 폭 촬영하여 확대하고, 그림을mag의 중심 위치로 이동한 다음, 그것을mag의 내부에만 번역된 그림을 표시하도록 재단한다.
번역도 비례인자의 영향을 받는다.시각적으로 다음과 같이 설명할 수 있습니다.
먼저 돋보기를 나타내는 새 작은 위젯을 만듭니다.
class Magnifier extends StatefulWidget {
const Magnifier({
@required this.child,
@required this.position,
this.visible = true,
this.scale = 1.5,
this.size = const Size(160, 160)
}) : assert(child != null);
final Widget child;
final Offset position;
final bool visible;
final double scale;
final Size size;
@override
_MagnifierState createState() => _MagnifierState();
}
class _MagnifierState extends State<Magnifier> {
Size _magnifierSize;
double _scale;
Matrix4 _matrix;
@override
void initState() {
_magnifierSize = widget.size;
_scale = widget.scale;
_calculateMatrix();
super.initState();
}
@override
void didUpdateWidget(Magnifier oldWidget) {
super.didUpdateWidget(oldWidget);
_calculateMatrix();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
widget.child,
if (widget.visible && widget.position != null)
_getMagnifier(context)
],
);
}
void _calculateMatrix() {
if (widget.position == null) {
return;
}
setState(() {
double newX = widget.position.dx - (_magnifierSize.width / 2 / _scale);
double newY = widget.position.dy - (_magnifierSize.height / 2 / _scale);
final Matrix4 updatedMatrix = Matrix4.identity()
..scale(_scale, _scale)
..translate(-newX, -newY);
_matrix = updatedMatrix;
});
}
}
우리는 여기에 몇 개의 구조 함수 파라미터가 있다.가장 관련된 것은 child
과 position
이다.이 기능이 모든 작은 위젯에 적용되기를 원하기 때문에 이 작은 위젯 (하위 파라미터) 을 제공해야 합니다.밑바닥 작은 부품의 어느 부분을 확대해야 하는지 확인하기 위해서, 우리는 position
파라미터를 필요로 한다.visible
은 사용자가 기포를 드래그할 때만 표시됩니다.scale
과 size
은 자동으로 해석되고 기본값이 있어야 합니다.
이곳의 관건적인 업무 논리는 _calculateMatrix()
방법이다. 왜냐하면 기포를 터치하는 위치에 따라 축소와 전환을 확정하기 때문이다.우리는 단위 행렬을 사용하고 비례와 평이 방법을 사용하여 그것을 업데이트한다.중요한 힌트: 우리는 기포를 만지는 중심을 닻점으로 사용해야 한다.이것이 바로 우리가 높이와 너비의 절반을 빼서 위치를 확정하는 이유다.
네, 현재 실제 확대경을 그리는 부분은 여전히 부족합니다.우리는 _getMagnifier()
이라고 부르지만 아직 실현되지 않았다.
Widget _getMagnifier(BuildContext context) {
return Align(
alignment: Alignment.topLeft,
child: ClipOval(
child: BackdropFilter(
filter: ImageFilter.matrix(_matrix.storage),
child: Container(
width: _magnifierSize.width,
height: _magnifierSize.height,
decoration: BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle,
border: Border.all(color: Colors.blue, width: 2)
),
),
),
);
}
이것은 약간의 해석할 가치가 있다.현재 이곳의 핵심 부품은 BackdropFilter입니다.이 필터는 필터와 하위 항목을 포함합니다.이것은 주어진 필터를 하위 레벨에 적용합니다.우리가 사용하는 필터는 행렬이다.이것은 우리가 계산한 행렬을 하위 대상에 적용할 수 있게 한다.그것을 ClipOval에 싸는 것은 매우 중요하다. 왜냐하면 우리는 원본 이미지의 전체 축소와 평이 복사본이 아니라 하나의 크기의 원만 보고 싶기 때문이다.
기포를 우리가 원하는 위치에 정확하게 놓을 가능성을 높이기 위해 우리는 확대경에 십자선을 추가할 수 있다.이것은 우리가 직접 그린 작은 부품으로 용기를 교체해야 한다.
import 'package:flutter/material.dart';
class MagnifierPainter extends CustomPainter {
const MagnifierPainter({
@required this.color,
this.strokeWidth = 5
});
final double strokeWidth;
final Color color;
@override
void paint(Canvas canvas, Size size) {
_drawCircle(canvas, size);
_drawCrosshair(canvas, size);
}
void _drawCircle(Canvas canvas, Size size) {
Paint paintObject = Paint()
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..color = color;
canvas.drawCircle(
size.center(
Offset(0, 0)
),
size.longestSide / 2, paintObject
);
}
void _drawCrosshair(Canvas canvas, Size size) {
Paint crossPaint = Paint()
..strokeWidth = strokeWidth / 2
..color = color;
double crossSize = size.longestSide * 0.04;
canvas.drawLine(
size.center(Offset(-crossSize, -crossSize)),
size.center(Offset(crossSize, crossSize)),
crossPaint
);
canvas.drawLine(
size.center(Offset(crossSize, -crossSize)),
size.center(Offset(-crossSize, crossSize)),
crossPaint
);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
우리는 우선 주어진 크기의 원을 그리지만, 획 모드에 있기 때문에, 우리는 윤곽만 그린다.그 후에 우리는 중심을 이용하여 십자를 그려서 확대경 크기의 4%에서 왼쪽 상단과 오른쪽 하단으로 이동시켰다.
현재 _getMagnifier()
클래스의 Magnifier
방법은 다음과 같다.
Widget _getMagnifier(BuildContext context) {
return Align(
alignment: Alignment.topLeft,
child: ClipOval(
child: BackdropFilter(
filter: ImageFilter.matrix(_matrix.storage),
child: CustomPaint(
painter: MagnifierPainter(
color: Theme.of(context).accentColor
),
size: _magnifierSize,
),
),
),
);
}
우리가 지금 주의해야 할 것은 기포의 위치를 만지면 확대경을 방해할 때 확대경이 오른쪽 상단으로 튀어나온다는 것이다.
Alignment _getAlignment() {
if (_bubbleCrossesMagnifier()) {
return Alignment.topRight;
}
return Alignment.topLeft;
}
bool _bubbleCrossesMagnifier() => widget.position.dx < widget.size.width &&
widget.position.dy < widget.size.height;
우리는 Alignment.topLeft
을 정태적으로 확대경에 분배하는 것이 아니라 그 위치에 따라 topLeft
을 사용할지 topRight
을 사용할지 결정한다.
이제 calculateMatrix()
메서드에 추가 블록을 추가해야 합니다.
if (_bubbleCrossesMagnifier()) {
final box = context.findRenderObject() as RenderBox;
newX -= ((box.size.width - _magnifierSize.width) / _scale);
}
조건을 충족시키기만 하면 newX
의 전체 너비를mag너비로 줄여 행렬의 평이에 영향을 줄 것입니다. 이것은 바로 왼쪽에서 오른쪽으로 정렬을 바꿀 때 확대경이 이동하는 양이기 때문입니다.
끝말
비싸지만 편리한 소부품 BackdropFilter
과 일점 행렬 계산을 사용하면 우리는 주어진 위치의 모든 하위 부품을 확대할 수 있는 확대경을 실현할 수 있다.확대경은 왼쪽 상단에 있지만 우리의 터치와 확대경의 위치가 충돌할 때 확대경은 튀어나온다.
my edge detection project을 보면 실제 동작을 볼 수 있고 코드를 볼 수 있습니다.확대경은 동적 이미지 주위에서 검출된 가장자리를 모의하는 터치 기포에 사용된다.
Reference
이 문제에 관하여(떨림 튜토리얼: 이미지 확대경), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/flutterclutter/flutter-tutorial-image-magnifier-1851
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
@override
Widget build(BuildContext context) {
return Stack(
children: [
Image(
image: AssetImage('assets/lenna.png')
)
],
);
}
class _SampleImageState extends State<SampleImage> {
static const double touchBubbleSize = 20;
Offset position;
double currentBubbleSize;
bool magnifierVisible = false;
@override
void initState() {
currentBubbleSize = touchBubbleSize;
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
super.initState();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Magnifier(
position: position,
visible: magnifierVisible,
child: Image(
image: AssetImage('assets/lenna.png')
),
),
_getTouchBubble()
],
);
}
Positioned _getTouchBubble() {
return Positioned(
top: position == null ? 0 : position.dy - currentBubbleSize / 2,
left: position == null ? 0 : position.dx - currentBubbleSize / 2,
child: GestureDetector(
child: Container(
width: currentBubbleSize,
height: currentBubbleSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).accentColor.withOpacity(0.5)
),
)
)
);
}
import 'package:flutter/material.dart';
class TouchBubble extends StatelessWidget {
TouchBubble({
@required this.position,
this.bubbleSize
});
final Offset position;
final double bubbleSize;
@override
Widget build(BuildContext context) {
return Positioned(
top: position == null ? 0 : position.dy - bubbleSize / 2,
left: position == null ? 0 : position.dx - bubbleSize / 2,
child: GestureDetector(
child: Container(
width: bubbleSize,
height: bubbleSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).accentColor.withOpacity(0.5)
),
)
)
);
}
}
import 'package:flutter/material.dart';
class TouchBubble extends StatelessWidget {
TouchBubble({
this.position,
@required this.onStartDragging,
@required this.onDrag,
@required this.onEndDragging,
@required this.bubbleSize,
}) : assert(onStartDragging != null),
assert(onDrag != null),
assert(onEndDragging != null),
assert(bubbleSize != null && bubbleSize > 0);
final Offset position;
final double bubbleSize;
final Function onStartDragging;
final Function onDrag;
final Function onEndDragging;
@override
Widget build(BuildContext context) {
return Positioned(
...
child: GestureDetector(
onPanStart: (details) => onStartDragging(details.globalPosition),
onPanUpdate: (details) => onDrag(details.globalPosition),
onPanEnd: (_) => onEndDragging(),
...
)
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_magnifier/touch_bubble.dart';
class SampleImage extends StatefulWidget {
@override
_SampleImageState createState() => _SampleImageState();
}
class _SampleImageState extends State<SampleImage> {
static const double touchBubbleSize = 20;
Offset position;
double currentBubbleSize;
bool magnifierVisible = false;
@override
void initState() {
currentBubbleSize = touchBubbleSize;
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
super.initState();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Image(
image: AssetImage('assets/lenna.png')
),
TouchBubble(
position: position,
bubbleSize: currentBubbleSize,
onStartDragging: _startDragging,
onDrag: _drag,
onEndDragging: _endDragging,
)
],
);
}
void _startDragging(Offset newPosition) {
setState(() {
magnifierVisible = true;
position = newPosition;
currentBubbleSize = touchBubbleSize * 1.5;
});
}
void _drag(Offset newPosition) {
setState(() {
position = newPosition;
});
}
void _endDragging() {
setState(() {
currentBubbleSize = touchBubbleSize;
magnifierVisible = false;
});
}
}
class Magnifier extends StatefulWidget {
const Magnifier({
@required this.child,
@required this.position,
this.visible = true,
this.scale = 1.5,
this.size = const Size(160, 160)
}) : assert(child != null);
final Widget child;
final Offset position;
final bool visible;
final double scale;
final Size size;
@override
_MagnifierState createState() => _MagnifierState();
}
class _MagnifierState extends State<Magnifier> {
Size _magnifierSize;
double _scale;
Matrix4 _matrix;
@override
void initState() {
_magnifierSize = widget.size;
_scale = widget.scale;
_calculateMatrix();
super.initState();
}
@override
void didUpdateWidget(Magnifier oldWidget) {
super.didUpdateWidget(oldWidget);
_calculateMatrix();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
widget.child,
if (widget.visible && widget.position != null)
_getMagnifier(context)
],
);
}
void _calculateMatrix() {
if (widget.position == null) {
return;
}
setState(() {
double newX = widget.position.dx - (_magnifierSize.width / 2 / _scale);
double newY = widget.position.dy - (_magnifierSize.height / 2 / _scale);
final Matrix4 updatedMatrix = Matrix4.identity()
..scale(_scale, _scale)
..translate(-newX, -newY);
_matrix = updatedMatrix;
});
}
}
Widget _getMagnifier(BuildContext context) {
return Align(
alignment: Alignment.topLeft,
child: ClipOval(
child: BackdropFilter(
filter: ImageFilter.matrix(_matrix.storage),
child: Container(
width: _magnifierSize.width,
height: _magnifierSize.height,
decoration: BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle,
border: Border.all(color: Colors.blue, width: 2)
),
),
),
);
}
import 'package:flutter/material.dart';
class MagnifierPainter extends CustomPainter {
const MagnifierPainter({
@required this.color,
this.strokeWidth = 5
});
final double strokeWidth;
final Color color;
@override
void paint(Canvas canvas, Size size) {
_drawCircle(canvas, size);
_drawCrosshair(canvas, size);
}
void _drawCircle(Canvas canvas, Size size) {
Paint paintObject = Paint()
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..color = color;
canvas.drawCircle(
size.center(
Offset(0, 0)
),
size.longestSide / 2, paintObject
);
}
void _drawCrosshair(Canvas canvas, Size size) {
Paint crossPaint = Paint()
..strokeWidth = strokeWidth / 2
..color = color;
double crossSize = size.longestSide * 0.04;
canvas.drawLine(
size.center(Offset(-crossSize, -crossSize)),
size.center(Offset(crossSize, crossSize)),
crossPaint
);
canvas.drawLine(
size.center(Offset(crossSize, -crossSize)),
size.center(Offset(-crossSize, crossSize)),
crossPaint
);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
Widget _getMagnifier(BuildContext context) {
return Align(
alignment: Alignment.topLeft,
child: ClipOval(
child: BackdropFilter(
filter: ImageFilter.matrix(_matrix.storage),
child: CustomPaint(
painter: MagnifierPainter(
color: Theme.of(context).accentColor
),
size: _magnifierSize,
),
),
),
);
}
Alignment _getAlignment() {
if (_bubbleCrossesMagnifier()) {
return Alignment.topRight;
}
return Alignment.topLeft;
}
bool _bubbleCrossesMagnifier() => widget.position.dx < widget.size.width &&
widget.position.dy < widget.size.height;
if (_bubbleCrossesMagnifier()) {
final box = context.findRenderObject() as RenderBox;
newX -= ((box.size.width - _magnifierSize.width) / _scale);
}
비싸지만 편리한 소부품
BackdropFilter
과 일점 행렬 계산을 사용하면 우리는 주어진 위치의 모든 하위 부품을 확대할 수 있는 확대경을 실현할 수 있다.확대경은 왼쪽 상단에 있지만 우리의 터치와 확대경의 위치가 충돌할 때 확대경은 튀어나온다.my edge detection project을 보면 실제 동작을 볼 수 있고 코드를 볼 수 있습니다.확대경은 동적 이미지 주위에서 검출된 가장자리를 모의하는 터치 기포에 사용된다.
Reference
이 문제에 관하여(떨림 튜토리얼: 이미지 확대경), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/flutterclutter/flutter-tutorial-image-magnifier-1851텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)