[译] 用 Flutter 打造一个圆形滑块(Slider)

}

可以看到,paint() 方法获得一个 Canvas 和一个 Size 参数。Canvas 提供一组方法可以让我们绘制任何形状:圆形、直线、圆弧、矩形等等。Size 参数即是画布的尺寸,由画布适配的部件尺寸决定。我们还需要一个 Paint,允许我们定制样式、颜色以及其他东西。

现在 BasePainter 的功能用法已经不言自明,然而 SliderPainter 却有一点儿不寻常,现在我们不仅要绘制一个圆弧而非圆,还需要绘制 Handler。

import ‘dart:math’;

import ‘package:flutter/material.dart’;
import ‘package:flutter_circular_slider/src/utils.dart’;

class SliderPainter extends CustomPainter {
double startAngle;
double endAngle;
double sweepAngle;
Color selectionColor;

Offset initHandler;
Offset endHandler;
Offset center;
double radius;

SliderPainter(
{@required this.startAngle,
@required this.endAngle,
@required this.sweepAngle,
@required this.selectionColor});

@override
void paint(Canvas canvas, Size size) {
if (startAngle == 0.0 && endAngle == 0.0) return;

Paint progress = _getPaint(color: selectionColor);

center = Offset(size.width / 2, size.height / 2);
radius = min(size.width / 2, size.height / 2);

canvas.drawArc(Rect.fromCircle(center: center, radius: radius),
-pi / 2 + startAngle, sweepAngle, false, progress);

Paint handler = _getPaint(color: selectionColor, style: PaintingStyle.fill);
Paint handlerOutter = _getPaint(color: selectionColor, width: 2.0);

// 绘制 handler
initHandler = radiansToCoordinates(center, -pi / 2 + startAngle, radius);
canvas.drawCircle(initHandler, 8.0, handler);
canvas.drawCircle(initHandler, 12.0, handlerOutter);

endHandler = radiansToCoordinates(center, -pi / 2 + endAngle, radius);
canvas.drawCircle(endHandler, 8.0, handler);
canvas.drawCircle(endHandler, 12.0, handlerOutter);
}

Paint _getPaint({@required Color color, double width, PaintingStyle style}) =>
Paint()
…color = color
…strokeCap = StrokeCap.round
…style = style ?? PaintingStyle.stroke
…strokeWidth = width ?? 12.0;

@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}

再一次地,我们获取了 center 和 radius 的值,但我们这次绘制的是圆弧。SliderPainter 将根据用户交互反馈的值作为 start、end 和 sweap 属性的值,以便于我们根据这些参数来绘制圆弧。值得一提的是我们需要从初始角度中减去 pi/2,因为我们的滑块的圆弧的起始位置是在圆形的正上方,而 drawArc() 方法使用 x 轴正轴作为起始位置。

当我们绘制好圆弧以后我们就需要准备绘制 Handler 了。为此,我们将分别绘制两个圆,一个在内部填充,一个在外部包裹。我调用了一些工具集函数用来将弧度转换为圆的坐标。你可以在 Github 仓库内查阅这些函数

让滑块响应交互

目前来看,仅仅使用 CustomPaint 以及两个 Painter 就已经足够绘制想要的东西了。然而它们还是不能够进行交互。因此就要使用 GestureDetector 来对它进行封装。这样一来我们就可以在画布上对用户事件做出相应处理。

一开始我们将为 Handler 赋初值,当获取这些 Handler 的坐标后,我们将按照以下策略执行操作:

  • 监听对于 Handler 的点击(按下)事件并更新相应 Handler 的状态。(_xHandlerSelected = true)。
  • 监听被选中 Handler 的拖动更新事件,更新其坐标,同时分别向下、向上传递给 SliderPainter 和我们的回调函数。
  • 监听 Handler 的点击(抬起)事件并重置未选中 Handler 的状态。

因为我们需要分别计算出坐标值、新的角度值再传递给 Handler 和 Painter,所以我们的 CircularSliderPaint 必须是一个 StatefulWidget

import ‘package:flutter/material.dart’;
import ‘package:flutter_circular_slider/src/base_painter.dart’;
import ‘package:flutter_circular_slider/src/slider_painter.dart’;
import ‘package:flutter_circular_slider/src/utils.dart’;

class CircularSliderPaint extends StatefulWidget {
final int init;
final int end;
final int intervals;
final Function onSelectionChange;
final Color baseColor;
final Color selectionColor;
final Widget child;

CircularSliderPaint(
{@required this.intervals,
@required this.init,
@required this.end,
this.child,
@required this.onSelectionChange,
@required this.baseColor,
@required this.selectionColor});

@override
_CircularSliderState createState() => _CircularSliderState();
}

class _CircularSliderState extends State {
bool _isInitHandlerSelected = false;
bool _isEndHandlerSelected = false;

SliderPainter _painter;

/// 用弧度制表示的起始角度,用来确定 init Handler 的位置。
double _startAngle;

/// 用弧度制表示的结束角度,用来确定 end Handler 的位置。
double _endAngle;

/// 用弧度制表示的选择区间的绝对角度(夹角)
double _sweepAngle;

@override
void initState() {
super.initState();
_calculatePaintData();
}

// 我们需要使用 gesture detector 来更新此部件,
// 当父部件重建自己时也是如此。
@override
void didUpdateWidget(CircularSliderPaint oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.init != widget.init || oldWidget.end != widget.end) {
_calculatePaintData();
}
}

@override
Widget build(BuildContext context) {
return GestureDetector(
onPanDown: _onPanDown,
onPanUpdate: _onPanUpdate,
onPanEnd: _onPanEnd,
child: CustomPaint(
painter: BasePainter(
baseColor: widget.baseColor,
selectionColor: widget.selectionColor),
foregroundPainter: _painter,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: widget.child,
),
),
);
}

void _calculatePaintData() {
double initPercent = valueToPercentage(widget.init, widget.intervals);
double endPercent = valueToPercentage(widget.end, widget.intervals);
double sweep = getSweepAngle(initPercent, endPercent);

_startAngle = percentageToRadians(initPercent);
_endAngle = percentageToRadians(endPercent);
_sweepAngle = percentageToRadians(sweep.abs());

_painter = SliderPainter(
startAngle: _startAngle,
endAngle: _endAngle,
sweepAngle: _sweepAngle,
selectionColor: widget.selectionColor,
);
}

_onPanUpdate(DragUpdateDetails details) {
if (!_isInitHandlerSelected && !_isEndHandlerSelected) {
return;
}
if (_painter.center == null) {
return;
}
RenderBox renderBox = context.findRenderObject();
var position = renderBox.globalToLocal(details.globalPosition);

var angle = coordinatesToRadians(_painter.center, position);
var percentage = radiansToPercentage(angle);
var newValue = percentageToValue(percentage, widget.intervals);

if (_isInitHandlerSelected) {
widget.onSelectionChange(newValue, widget.end);
} else {
widget.onSelectionChange(widget.init, newValue);
}
}

onPanEnd() {
_isInitHandlerSelected = false;
_isEndHandlerSelected = false;
}

_onPanDown(DragDownDetails details) {
if (_painter == null) {
return;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

尾声

最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

进阶学习视频

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-XwXETRl3-1712822484016)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 24
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值