[译] 用 Flutter 打造一个圆形滑块(Slider)
针对于上面的问题,我总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料。(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。《Android学习笔记总
}
可以看到,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移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。





既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
针对于上面的问题,我总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料。
(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
P框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。**
[外链图片转存中…(img-nZt5eSvj-1712305919752)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
更多推荐



所有评论(0)