效果圖
案例分析
1愿汰、效果功能分析
- 滑動選擇刻度尺
- 支持中間選擇刻度值
- 支持設(shè)置最大最小值
- 支持設(shè)置默認(rèn)值
- 支持設(shè)置大刻度的子刻度數(shù)
- 支持設(shè)置步長
- 支持設(shè)置刻度尺、數(shù)字的顏色及大小
- 支持滑動選中回調(diào)
- 支持刻度尺回彈效果
2续搀、功能拆解
- 自定義Widget(繼承StatefulWidget)。
- 使用ListView實(shí)現(xiàn)水平滑動效果(3個(gè)子Widget,左右為空白扛拨,中間為刻度尺)猫缭。
- 繪制刻度尺Widget(刻度線葱弟、刻度值)。
- 監(jiān)聽滑動獲取中間值并回調(diào)猜丹。
- 手指抬起滑動停止粘性回彈芝加。
3、功能參數(shù)
- 默認(rèn)值
- 最小值
- 最大值
- 步長
- 刻度尺的寬高
- 大刻度子子刻度數(shù)
- 單刻度寬度
- 刻度線顏色及寬度
- 刻度尺數(shù)值顏色及寬度
- 中間刻度線顏色
- 選擇回調(diào)
4射窒、功能代碼實(shí)現(xiàn)
小知識點(diǎn):
NotificationListener:
if (notification is ScrollStartNotification) {
print('滾動開始');
}
if (notification is ScrollUpdateNotification) {
print('滾動中');
}
if (notification is ScrollEndNotification) {
print('停止?jié)L動');
if (_scrollController.position.extentAfter == 0) {
print('滾動到底部');
}
if (_scrollController.position.extentBefore == 0) {
print('滾動到頭部');
}
}
完整代碼
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
///自定義尺子
class RulerView extends StatefulWidget {
//默認(rèn)值
final int value;
//最小值
final int minValue;
//最大值
final int maxValue;
//步數(shù) 一個(gè)刻度的值
final int step;
//尺子的寬度
final int width;
//尺子的高度
final int height;
//每個(gè)大刻度的子刻度數(shù)
final int subScaleCountPerScale;
//每一刻度的寬度
final int subScaleWidth;
//左右空白間距寬度
double paddingItemWidth;
//刻度尺選擇回調(diào)
final void Function(int) onSelectedChanged;
//刻度顏色
final Color scaleColor;
//指示器顏色
final Color indicatorColor;
//刻度文字顏色
final Color scaleTextColor;
//刻度文字的大小
final double scaleTextWidth;
//刻度線的大小
final double scaleWidth;
//計(jì)算總刻度數(shù)
int totalSubScaleCount;
RulerView({
Key key,
this.value = 10,
this.minValue = 0,
this.maxValue = 100,
this.step = 1,
this.width = 200,
this.height = 60,
this.subScaleCountPerScale = 10,
this.subScaleWidth = 8,
this.scaleColor = Colors.black,
this.scaleWidth = 2,
this.scaleTextColor = Colors.black,
this.scaleTextWidth = 15,
this.indicatorColor = Colors.red,
@required this.onSelectedChanged,
}) : super(key: key) {
//檢查最大數(shù)-最小數(shù)必須是步數(shù)的倍數(shù)
if ((maxValue - minValue) % step != 0) {
throw Exception("(maxValue - minValue)必須是 step 的整數(shù)倍");
}
//默認(rèn)值 不能低于最小值 或者大于最大值
if (value < minValue || value > maxValue) {
throw Exception(
"value 必須在minValue和maxValue范圍內(nèi)(minValue<=value<=maxValue)");
}
//總刻度數(shù)
totalSubScaleCount = (maxValue - minValue) ~/ step;
//檢查總刻度數(shù)必須是大刻度子刻度數(shù)的倍數(shù)
if (totalSubScaleCount % subScaleCountPerScale != 0) {
throw Exception(
"(maxValue - minValue)~/step 必須是 subScaleCountPerScale 的整數(shù)倍");
}
//空白item的寬度
paddingItemWidth = width / 2;
}
@override
State<StatefulWidget> createState() {
return RulerState();
}
}
class RulerState extends State<RulerView> {
ScrollController _scrollController;
@override
void initState() {
super.initState();
_scrollController = ScrollController(
//初始位置
initialScrollOffset:
// ((默認(rèn)值-最小值)/步長 )=第幾個(gè)刻度藏杖,再乘以每個(gè)刻度的寬度就是初始位置
(widget.value - widget.minValue) / widget.step * widget.subScaleWidth,
);
}
@override
Widget build(BuildContext context) {
return Container(
width: widget.width.toDouble(),
height: widget.height.toDouble(),
child: Stack(
alignment: Alignment.topCenter,
children: <Widget>[
NotificationListener(
onNotification: _onNotification,
child: ListView.builder(
physics: ClampingScrollPhysics(),
padding: EdgeInsets.all(0),
controller: _scrollController,
scrollDirection: Axis.horizontal,
itemCount: 3,
itemBuilder: (BuildContext context, int index) {
//2邊的空白控件
if (index == 0 || index == 2) {
return Container(
width: widget.paddingItemWidth,
height: 0,
);
} else {
//刻度尺
return Container(
child: RealRulerView(
subGridCount: widget.totalSubScaleCount,
subScaleWidth: widget.subScaleWidth,
step: widget.step,
minValue: widget.minValue,
height: widget.height,
scaleColor: widget.scaleColor,
scaleWidth: widget.scaleWidth,
scaleTextWidth: widget.scaleTextWidth,
scaleTextColor: widget.scaleTextColor,
subScaleCountPerScale: widget.subScaleCountPerScale,
),
);
}
},
),
),
//指示器
Container(
width: 2,
height: widget.height / 2,
color: widget.indicatorColor,
),
],
),
);
}
///監(jiān)聽刻度尺滾動通知
bool _onNotification(Notification notification) {
//ScrollNotification是基類 (ScrollStartNotification/ScrollUpdateNotification/ScrollEndNotification)
if (notification is ScrollNotification) {
print("-------metrics.pixels-------${notification.metrics.pixels}");
//距離widget中間最近的刻度值
int centerValue = widget.minValue +
//notification.metrics.pixels水平滾動的偏移量
//先計(jì)算出滾動偏移量是滾動了多少個(gè)刻度,然后取整脉顿,在乘以每個(gè)刻度的刻度值就是當(dāng)前選中的值
(notification.metrics.pixels / widget.subScaleWidth).round() *
widget.step;
// 選中值回調(diào)
if (widget.onSelectedChanged != null) {
widget.onSelectedChanged(centerValue);
}
//如果是否滾動停止蝌麸,停止則滾動到centerValue
if (_scrollingStopped(notification, _scrollController)) {
select(centerValue);
}
}
return true; //停止通知
}
///判斷是否滾動停止
bool _scrollingStopped(
Notification notification,
ScrollController scrollController,
) {
return
//停止?jié)L動
notification is UserScrollNotification
//沒有滾動正在進(jìn)行
&&
notification.direction == ScrollDirection.idle &&
scrollController.position.activity is! HoldScrollActivity;
}
///選中值
void select(int centerValue) {
//根據(jù)(中間值-最小值)/步長=第幾個(gè)刻度,然后第幾個(gè)刻度乘以每個(gè)刻度的寬度就是移動的寬度
double x =
(centerValue - widget.minValue) / widget.step * widget.subScaleWidth;
_scrollController.animateTo(x,
duration: Duration(milliseconds: 200), curve: Curves.decelerate);
}
}
///真實(shí)刻度尺View
class RealRulerView extends StatelessWidget {
const RealRulerView({
Key key,
this.subGridCount,
this.subScaleWidth,
this.minValue,
this.height,
this.step,
this.scaleColor,
this.scaleWidth,
this.scaleTextColor,
this.scaleTextWidth,
this.subScaleCountPerScale,
}) : super(key: key);
//刻度總數(shù)
final int subGridCount;
//每個(gè)刻度的寬度
final int subScaleWidth;
//刻度尺的高度
final int height;
//刻度尺最小值
final int minValue;
//每個(gè)大刻度的小刻度數(shù)
final int subScaleCountPerScale;
//步長 一刻度的值
final int step;
//刻度尺顏色
final Color scaleColor;
//刻度尺寬度
final double scaleTextWidth;
//刻度線寬度
final double scaleWidth;
//數(shù)字顏色
final Color scaleTextColor;
@override
Widget build(BuildContext context) {
double rulerWidth = (subScaleWidth * subGridCount).toDouble();
double rulerHeight = this.height.toDouble();
return CustomPaint(
size: Size(rulerWidth, rulerHeight),
painter: RulerViewPainter(
this.subScaleWidth,
this.step,
this.minValue,
this.scaleColor,
this.scaleWidth,
this.scaleTextColor,
this.scaleTextWidth,
this.subScaleCountPerScale,
),
);
}
}
class RulerViewPainter extends CustomPainter {
final int subScaleWidth;
final int step;
final int minValue;
final Color scaleColor;
final Color scaleTextColor;
final double scaleTextWidth;
final int subScaleCountPerScale;
final double scaleWidth;
Paint linePaint;
TextPainter textPainter;
RulerViewPainter(
this.subScaleWidth,
this.step,
this.minValue,
this.scaleColor,
this.scaleWidth,
this.scaleTextColor,
this.scaleTextWidth,
this.subScaleCountPerScale,
) {
//刻度尺
linePaint = Paint()
..isAntiAlias = true
..style = PaintingStyle.stroke
..strokeWidth = scaleWidth
..color = scaleColor;
//數(shù)字
textPainter = TextPainter(
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
);
}
@override
void paint(Canvas canvas, Size size) {
//繪制線
drawLine(canvas, size);
//繪制數(shù)字
drawNum(canvas, size);
}
///繪制線
void drawLine(Canvas canvas, Size size) {
//繪制橫線
canvas.drawLine(
Offset(0, 0 + scaleWidth / 2),
Offset(size.width, 0 + scaleWidth / 2),
linePaint,
);
//第幾個(gè)小格子
int index = 0;
//繪制豎線
for (double x = 0; x <= size.width; x += subScaleWidth) {
if (index % subScaleCountPerScale == 0) {
canvas.drawLine(
Offset(x, 0), Offset(x, size.height * 3 / 8), linePaint);
} else {
canvas.drawLine(Offset(x, 0), Offset(x, size.height / 4), linePaint);
}
index++;
}
}
///繪制數(shù)字
void drawNum(Canvas canvas, Size size) {
canvas.save();
//坐標(biāo)移動(0弊予,0)點(diǎn)
canvas.translate(0, 0);
//每個(gè)大格子的寬度
double offsetX = (subScaleWidth * subScaleCountPerScale).toDouble();
int index = 0;
//繪制數(shù)字
for (double x = 0; x <= size.width; x += offsetX) {
textPainter.text = TextSpan(
text: "${minValue + index * step * subScaleCountPerScale}",
style: TextStyle(color: scaleTextColor, fontSize: scaleTextWidth),
);
textPainter.layout();
textPainter.paint(
canvas,
new Offset(
-textPainter.width / 2,
size.height - textPainter.height,
),
);
index++;
canvas.translate(offsetX, 0);
}
canvas.restore();
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
github:https://github.com/yixiaolunhui/my_flutter
好了祥楣,話不多說,一笑輪回~~~~~