赞
踩
[1]. 了解手势在画布中的使用方式
[2]. 练习绘制,并根据手指滑动完成控制杆的绘制
[3]. 练习绘制,并根据手指滑动完成【刻度尺】的绘制
[4]. 了解如何限制绘制区域
下面的控制器很能够体现出
手势操作在画布中的使用
。其中小球的圆心只能在大圆中移动。可以回调出移动的方向和距离。是一个非常好的控制案例。其中包含一些角度运算、边界控制的技巧也很值得去体会。
如下所示,是控制柄的4个瞬间。灰色区域是组件的占位空间。小圆的圆心只能在大圆区域内移动。所以组件尺寸size和校园半径handleRadius可以确定大圆的半径bgR=size/2-handleRadius 在移动的过程中。获取到触点位置。计算小圆偏移量即可。
先完成下面的静态效果
class HandleWidget extends StatefulWidget {
final double size;
final double handleRadius;
HandleWidget({Key? key, this.size = 160.0, this.handleRadius = 20.0})
: super(key: key);
@override
_HandleWidgetState createState() => _HandleWidgetState();
}
class _HandleWidgetState extends State<HandleWidget> {
@override
Widget build(BuildContext context) {
return CustomPaint(
size: Size(widget.size, widget.size),
painter: _HandlePainter(handleR: widget.handleRadius));
}
}
class _HandlePainter extends CustomPainter {
var _paint = Paint();
var handleR;
_HandlePainter({this.handleR}) {
_paint
..color = Colors.blue
..style = PaintingStyle.fill
..isAntiAlias = true;
}
@override
void paint(Canvas canvas, Size size) {
canvas.clipRect(Offset.zero & size);
final bgR = size.width / 2 - handleR;
canvas.translate(size.width / 2, size.height / 2);
_paint.color = _paint.color.withAlpha(100);
canvas.drawCircle(Offset(0, 0), bgR, _paint);
_paint.color = _paint.color.withAlpha(150);
canvas.drawCircle(Offset(0, 0), handleR, _paint);
}
@override
bool shouldRepaint(_HandlePainter oldDelegate) =>
oldDelegate.handleR != handleR;
}
通过GestureDetector可以监听到手指在组件上的触碰信息。
在onPanUpdate时可以获取到触点信息。onPanEnd是手指离开的时候。可以重置偏移。
在ValueNotifier 对象中传入画板用来触发画布更新
void main() {
// 确定初始化
WidgetsFlutterBinding.ensureInitialized();
//横屏
SystemChrome.setPreferredOrientations(
[DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
//全屏显示
SystemChrome.setEnabledSystemUIOverlays([]);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: Center(
child: HandleWidget(
),
),
));
}
}
class HandleWidget extends StatefulWidget {
final double size;
final double handleRadius;
HandleWidget({Key? key, this.size = 160, this.handleRadius = 20.0})
: super(key: key);
@override
_HandleWidgetState createState() => _HandleWidgetState();
}
class _HandleWidgetState extends State<HandleWidget> {
ValueNotifier<Offset> _offset = ValueNotifier(Offset.zero);
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanEnd: reset,
onPanUpdate: parser,
child: CustomPaint(
size: Size(widget.size, widget.size),
painter: _HandlePainter(
color: Colors.green,
handleR: widget.handleRadius,
offset: _offset)));
}
reset(DragEndDetails details) {
_offset.value = Offset.zero;
}
parser(DragUpdateDetails details) {
final offset = details.localPosition;
double dx = 0.0;
double dy = 0.0;
dx = offset.dx - widget.size / 2;
dy = offset.dy - widget.size / 2;
var rad = atan2(dx, dy);
if (dx < 0) {
rad += 2 * pi;
}
var bgR = widget.size / 2 - widget.handleRadius;
var thta = rad - pi / 2; //旋转坐标系90度
if (sqrt(dx * dx + dy * dy) > bgR) {
dx = bgR * cos(thta);
dy = -bgR * sin(thta);
}
_offset.value = Offset(dx, dy);
}
}
class _HandlePainter extends CustomPainter {
var _paint = Paint();
final ValueNotifier<Offset> offset;
final Color color;
var handleR;
_HandlePainter({this.handleR,required this.offset, this.color = Colors.blue})
: super(repaint: offset) ;
@override
void paint(Canvas canvas, Size size) {
canvas.clipRect(Offset.zero & size);
final bgR = size.width / 2 - handleR;
canvas.translate(size.width / 2, size.height / 2);
_paint.style = PaintingStyle.fill;
_paint.color = color.withAlpha(100);
canvas.drawCircle(Offset(0, 0), bgR, _paint);
_paint.color = color.withAlpha(150);
canvas.drawCircle(
Offset(offset.value.dx, offset.value.dy), handleR, _paint);
_paint.color = color;
_paint.style = PaintingStyle.stroke;
canvas.drawLine(Offset.zero, offset.value, _paint);
}
@override
bool shouldRepaint(_HandlePainter oldDelegate) =>
oldDelegate.offset != offset ||
oldDelegate.color != color ||
oldDelegate.handleR != handleR;
}
像switch和Slider这样的组件都会向外界提供回调方法。我们也可以将距离和角度回调给使用者。
比如下面通过控制器旋转来对蓝色的Container进行操作
void main() {
// 确定初始化
WidgetsFlutterBinding.ensureInitialized();
//横屏
SystemChrome.setPreferredOrientations(
[DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
//全屏显示
SystemChrome.setEnabledSystemUIOverlays([]);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage());
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
double _rotate = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Transform.rotate(
angle: _rotate,
child:
Container(
color: Colors.blue,
alignment: Alignment.center,
width: 100,
height: 100,
),
),
HandleWidget(
onMove: _onMove,
),
],
),
),
);
}
void _onMove(double rotate, double distance) {
setState(() {
_rotate= rotate;
});
}
}
class HandleWidget extends StatefulWidget {
final double size;
final double handleRadius;
final void Function(double rotate, double distance) onMove;
HandleWidget(
{Key? key,
this.size = 160,
this.handleRadius = 20.0,
required this.onMove})
: super(key: key);
@override
_HandleWidgetState createState() => _HandleWidgetState();
}
class _HandleWidgetState extends State<HandleWidget> {
ValueNotifier<Offset> _offset = ValueNotifier(Offset.zero);
@override
Widget build(BuildContext context) {
return RepaintBoundary(
child: GestureDetector(
onPanEnd: reset,
onPanUpdate: parser,
child: CustomPaint(
size: Size(widget.size, widget.size),
painter: _HandlePainter(
color: Colors.green,
handleR: widget.handleRadius,
offset: _offset))),
);
}
reset(DragEndDetails details) {
_offset.value = Offset.zero;
widget.onMove(0, 0);
}
parser(DragUpdateDetails details) {
final offset = details.localPosition;
double dx = 0.0;
double dy = 0.0;
dx = offset.dx - widget.size / 2;
dy = offset.dy - widget.size / 2;
var rad = atan2(dx, dy);
if (dx < 0) {
rad += 2 * pi;
}
var bgR = widget.size / 2 - widget.handleRadius;
var thta = rad - pi / 2; //旋转坐标系90度
var d = sqrt(dx * dx + dy * dy);
if (d > bgR) {
dx = bgR * cos(thta);
dy = -bgR * sin(thta);
}
widget.onMove(thta, d);
_offset.value = Offset(dx, dy);
}
}
class _HandlePainter extends CustomPainter {
var _paint = Paint();
final ValueNotifier<Offset> offset;
final Color color;
var handleR;
_HandlePainter({this.handleR,required this.offset, this.color = Colors.blue})
: super(repaint: offset);
@override
void paint(Canvas canvas, Size size) {
canvas.clipRect(Offset.zero & size);
final bgR = size.width / 2 - handleR;
canvas.translate(size.width / 2, size.height / 2);
_paint.style = PaintingStyle.fill;
_paint.color = color.withAlpha(100);
canvas.drawCircle(Offset(0, 0), bgR, _paint);
_paint.color = color.withAlpha(150);
canvas.drawCircle(
Offset(offset.value.dx, offset.value.dy), handleR, _paint);
_paint.color = color;
_paint.style = PaintingStyle.stroke;
canvas.drawLine(Offset.zero, offset.value, _paint);
}
@override
bool shouldRepaint(_HandlePainter oldDelegate) =>
oldDelegate.offset != offset ||
oldDelegate.color != color ||
oldDelegate.handleR != handleR;
}
通过绘制如下的滑动刻度尺,来练习一下
水平滑动的操作
以及刻度的绘制。其中包含对边界值的限制。一些数值计算等使用的技巧。
需要指定的属性有,最大值max,最小值min,刻度尺可以在这之间滑动
并且给出一个onChanged的回调方法。将当前值直接传给用户。在画板中最重要的属性就是水平滑动的距离。所以在组件中使用一个ValueNotifier的对象传给画板。用于更新重绘。
通过GestureDetector在水平方向进行监听。和和兴时进行移动过程中的点位计算。逻辑我放在_paese方法中。
import 'dart:math';
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
class RulerChooser extends StatefulWidget {
final Size size;
final void Function(double) onChanged;
final int min;
final int max;
RulerChooser(
{Key? key,
required this.onChanged,
this.max = 200,
this.min = 100,
this.size = const Size(240.0, 60)})
: super(key: key);
@override
_RulerChooserState createState() => _RulerChooserState();
}
class _RulerChooserState extends State<RulerChooser> {
ValueNotifier<double> _dx = ValueNotifier(0.0);
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanUpdate: _parser,
child: CustomPaint(
size: widget.size,
painter: _HandlePainter(dx: _dx, max: widget.max, min: widget.min)),
);
}
double dx = 0;
void _parser(DragUpdateDetails details) {
dx += details.delta.dx;
if (dx > 0) {
dx = 0.0;
}
var limitMax = -(widget.max - widget.min) * (_kSpacer + _kStrokeWidth);
if (dx < limitMax) {
dx = limitMax;
}
_dx.value = dx;
if (widget.onChanged != null) {
widget.onChanged(details.delta.dx / (_kSpacer + _kStrokeWidth));
}
}
}
const double _kHeightLevel1 = 20; // 短线长
const double _kHeightLevel2 = 25; // 5 线长
const double _kHeightLevel3 = 30; //10 线长
const double _kPrefixOffSet = 5; // 左侧偏移
const double _kVerticalOffSet = 12; // 线顶部偏移
const double _kStrokeWidth = 2; // 刻度宽
const double _kSpacer = 4; // 刻度间隙
const List<Color> _kRulerColors = [
// 渐变色
Color(0xFF1426FB),
Color(0xFF6080FB),
Color(0xFFBEE0FB),
];
const List<double> _kRulerColorStops = [0.0, 0.2, 0.8];
class _HandlePainter extends CustomPainter {
var _paint = Paint();
Paint _pointPaint = Paint();
final ValueNotifier<double> dx;
final int max;
final int min;
_HandlePainter({required this.dx,required this.max,required this.min}) : super(repaint: dx) {
_paint
..strokeWidth = _kStrokeWidth
..shader = ui.Gradient.radial(
Offset(0, 0), 25, _kRulerColors, _kRulerColorStops, TileMode.mirror);
_pointPaint
..color = Colors.purple
..strokeWidth = 4
..strokeCap = StrokeCap.round;
}
@override
void paint(Canvas canvas, Size size) {
canvas.clipRect(Offset.zero & size);
drawArrow(canvas);
canvas.translate(dx.value, 0);
drawRuler(canvas);
}
// 绘制刻度
void drawRuler(Canvas canvas) {
double y = _kHeightLevel1;
for (int i = min; i < max + 5; i++) {
if (i % 5 == 0 && i % 10 != 0) {
y = _kHeightLevel2;
} else if (i % 10 == 0) {
y = _kHeightLevel3;
_simpleDrawText(canvas, i.toString(),
offset: Offset(-3, _kHeightLevel3 + 5));
} else {
y = _kHeightLevel1;
}
canvas.drawLine(Offset.zero, Offset(0, y), _paint);
canvas.translate(_kStrokeWidth + _kSpacer, 0);
}
}
// 绘制三角形尖角
void drawArrow(Canvas canvas) {
var path = Path()
..moveTo(_kStrokeWidth / 2 + _kPrefixOffSet, 3)
..relativeLineTo(-3, 0)
..relativeLineTo(3, _kPrefixOffSet)
..relativeLineTo(3, -_kPrefixOffSet)
..close();
canvas.drawPath(path, _pointPaint);
canvas.translate(_kStrokeWidth / 2 + _kPrefixOffSet, _kVerticalOffSet);
}
void _simpleDrawText(Canvas canvas, String str,
{Offset offset = Offset.zero}) {
var builder = ui.ParagraphBuilder(ui.ParagraphStyle())
..pushStyle(
ui.TextStyle(
color: Colors.black, textBaseline: ui.TextBaseline.alphabetic),
)
..addText(str);
canvas.drawParagraph(
builder.build()
..layout(ui.ParagraphConstraints(width: 11.0 * str.length)),
offset);
}
@override
bool shouldRepaint(_HandlePainter oldDelegate) =>
oldDelegate.dx != dx || oldDelegate.min != min || oldDelegate.max != max;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。