| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- import 'dart:math';
- import 'package:flutter/cupertino.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/widgets.dart';
- import 'package:flutter_screenutil/flutter_screenutil.dart';
- /// a triangle painter
- class _TrianglePainter extends CustomPainter {
- // final double lineSize;
- // _TrianglePainter({this.lineSize = 16});
- @override
- void paint(Canvas canvas, Size size) {
- Path path = Path();
- path.moveTo(0, 0);
- path.lineTo(size.width, 0);
- path.lineTo(size.width / 2, tan(pi / 3) * size.width / 2);
- path.close();
- Paint paint = Paint();
- paint.color = const Color.fromARGB(255, 118, 165, 248);
- paint.style = PaintingStyle.fill;
- canvas.drawPath(path, paint);
- }
- @override
- bool shouldRepaint(CustomPainter oldDelegate) {
- return false;
- }
- }
- /// The controller for the ruler picker
- /// init the ruler value from the controller
- /// 用于 RulerPicker 的控制器,可以在构造函数里初始化默认值
- class RulerPickerController extends ValueNotifier<num> {
- RulerPickerController({num value = 0}) : super(value);
- num get value => super.value;
- set value(num newValue) {
- super.value = newValue;
- }
- }
- typedef void ValueChangedCallback(num value);
- /// RulerPicker 标尺选择器
- /// [width] 必须是具体的值,包括父级container的width,不能是 double.infinity,
- /// 可以传入MediaQuery.of(context).size.width
- class RulerPicker extends StatefulWidget {
- final ValueChangedCallback onValueChanged;
- final String Function(int index, num rulerScaleValue) onBuildRulerScaleText;
- final double width;
- final double height;
- final TextStyle rulerScaleTextStyle;
- final List<ScaleLineStyle> scaleLineStyleList;
- final List<RulerRange> ranges;
- final Widget? marker;
- final double rulerMarginTop;
- final Color rulerBackgroundColor;
- final RulerPickerController? controller;
- RulerPicker({
- required this.onValueChanged,
- required this.width,
- required this.height,
- required this.onBuildRulerScaleText,
- this.ranges = const [],
- this.rulerMarginTop = 0,
- this.scaleLineStyleList = const [
- ScaleLineStyle(
- scale: 0,
- color: Color.fromARGB(255, 188, 194, 203),
- width: 2,
- height: 32),
- ScaleLineStyle(
- color: Color.fromARGB(255, 188, 194, 203), width: 1, height: 20),
- ],
- this.rulerScaleTextStyle = const TextStyle(
- color: Color.fromARGB(255, 188, 194, 203),
- fontSize: 14,
- ),
- this.marker,
- this.rulerBackgroundColor = Colors.white,
- this.controller,
- });
- @override
- State<StatefulWidget> createState() {
- return RulerPickerState();
- }
- }
- class RulerPickerState extends State<RulerPicker> {
- double lastOffset = 0;
- bool isPosFixed = false;
- late ScrollController scrollController;
- Map<int, ScaleLineStyle> _scaleLineStyleMap = {};
- int itemCount = 0;
- // 每个刻度间距
- final double _ruleScaleInterval = 15.w;
- @override
- void initState() {
- super.initState();
- itemCount = _calculateItemCount();
- for (var element in widget.scaleLineStyleList) {
- _scaleLineStyleMap[element.scale] = element;
- }
- double initValueOffset = getPositionByValue(widget.controller?.value ?? 0);
- scrollController = ScrollController(
- initialScrollOffset: initValueOffset > 0 ? initValueOffset : 0,
- );
- scrollController.addListener(_onValueChanged);
- widget.controller?.addListener(() {
- setPositionByValue(widget.controller?.value ?? 0);
- });
- }
- int _calculateItemCount() {
- int itemCount = 0;
- for (var element in widget.ranges) {
- itemCount += ((element.end - element.begin) / element.scale).truncate();
- }
- itemCount += 1;
- return itemCount;
- }
- void _onValueChanged() {
- int currentIndex = scrollController.offset ~/ _ruleScaleInterval.toInt();
- if (currentIndex < 0) currentIndex = 0;
- num currentValue = getRulerScaleValue(currentIndex);
- var lastConfig = widget.ranges.last;
- if (currentValue > lastConfig.end) currentValue = lastConfig.end;
- widget.onValueChanged(currentValue);
- }
- void fixOffset() {
- final double rawOffset = scrollController.offset;
- final double fixedOffset =
- (rawOffset / _ruleScaleInterval).round() * _ruleScaleInterval;
- scrollController.animateTo(
- fixedOffset,
- duration: const Duration(milliseconds: 100),
- curve: Curves.easeOut,
- );
- Future.delayed(const Duration(milliseconds: 120), () {
- _onValueChanged(); // 确保最终值是准确的
- });
- }
- num getRulerScaleValue(int index) {
- num rulerScaleValue = 0;
- RulerRange? currentConfig;
- for (RulerRange config in widget.ranges) {
- currentConfig = config;
- if (currentConfig == widget.ranges.last) {
- break;
- }
- var totalCount =
- ((config.end - config.begin) / config.scale).truncate();
- if (index <= totalCount) {
- break;
- } else {
- index -= totalCount;
- }
- }
- rulerScaleValue = index * currentConfig!.scale + currentConfig.begin;
- return rulerScaleValue;
- }
- double getPositionByValue(num value) {
- double offsetValue = 0;
- for (RulerRange config in widget.ranges) {
- if (config.begin <= value && config.end >= value) {
- offsetValue +=
- ((value - config.begin) / config.scale) * _ruleScaleInterval;
- break;
- } else if (value >= config.begin) {
- var totalCount =
- ((config.end - config.begin) / config.scale).truncate();
- offsetValue += totalCount * _ruleScaleInterval;
- }
- }
- return offsetValue;
- }
- void setPositionByValue(num value) {
- double offsetValue = getPositionByValue(value);
- scrollController.jumpTo(offsetValue);
- fixOffset();
- }
- Widget _buildRulerScaleLine(int index) {
- bool isMajorScale = index % 5 == 0;
- return Container(
- width: 8.w,
- height: isMajorScale ? 48.w : 24.w,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(40.w),
- color: const Color(0xffd0d1d6),
- ),
- );
- }
- Widget _buildRulerScale(BuildContext context, int index) {
- return Container(
- width: _ruleScaleInterval,
- child: Stack(
- clipBehavior: Clip.none,
- children: [
- Align(
- alignment: Alignment.topCenter,
- child: _buildRulerScaleLine(index),
- ),
- Positioned(
- bottom: 5,
- width: 100,
- left: -50 + _ruleScaleInterval / 2,
- child: index % 10 == 0
- ? Container(
- alignment: Alignment.center,
- child: Text(
- widget.onBuildRulerScaleText(
- index, getRulerScaleValue(index)),
- style: widget.rulerScaleTextStyle,
- ),
- )
- : const SizedBox(),
- ),
- ],
- ),
- );
- }
- Widget _buildMark() {
- Widget triangle() {
- return SizedBox(
- width: 15,
- height: 15,
- child: CustomPaint(
- painter: _TrianglePainter(),
- ),
- );
- }
- return SizedBox(
- width: _ruleScaleInterval * 2,
- height: 45,
- child: Stack(
- children: [
- Align(alignment: Alignment.topCenter, child: triangle()),
- Align(
- child: Container(
- width: 3,
- height: 34,
- color: const Color.fromARGB(255, 118, 165, 248),
- ),
- ),
- ],
- ),
- );
- }
- bool isRangesChanged(RulerPicker oldWidget) {
- if (oldWidget.ranges.length != widget.ranges.length) {
- return true;
- }
- for (int i = 0; i < widget.ranges.length; i++) {
- RulerRange oldRange = oldWidget.ranges[i];
- RulerRange range = widget.ranges[i];
- if (oldRange.begin != range.begin ||
- oldRange.end != range.end ||
- oldRange.scale != range.scale) {
- return true;
- }
- }
- return false;
- }
- @override
- void didUpdateWidget(RulerPicker oldWidget) {
- super.didUpdateWidget(oldWidget);
- if (mounted && isRangesChanged(oldWidget)) {
- Future.delayed(Duration.zero, () {
- setState(() {
- itemCount = _calculateItemCount();
- });
- _onValueChanged();
- });
- }
- }
- @override
- Widget build(BuildContext context) {
- return Container(
- width: widget.width,
- height: widget.height + widget.rulerMarginTop,
- child: Stack(
- children: [
- Align(
- alignment: Alignment.bottomCenter,
- child: Padding(padding: EdgeInsets.only(top: widget.rulerMarginTop),child: Listener(
- onPointerDown: (_) {
- FocusScope.of(context).unfocus();
- isPosFixed = false;
- },
- child: NotificationListener<ScrollNotification>(
- onNotification: (scrollNotification) {
- if (scrollNotification is ScrollStartNotification) {
- isPosFixed = false;
- } else if (scrollNotification is ScrollEndNotification) {
- if (!isPosFixed) {
- isPosFixed = true;
- fixOffset();
- }
- }
- return true;
- },
- child: ListView.builder(
- padding: EdgeInsets.symmetric(
- horizontal: (widget.width - _ruleScaleInterval) / 2,
- ),
- itemExtent: _ruleScaleInterval,
- itemCount: itemCount,
- controller: scrollController,
- scrollDirection: Axis.horizontal,
- itemBuilder: _buildRulerScale,
- ),
- ),
- ),)
- ),
- Align(
- alignment: Alignment.topCenter,
- child: widget.marker ?? _buildMark(),
- ),
- ],
- ),
- );
- }
- @override
- void dispose() {
- scrollController.dispose();
- super.dispose();
- }
- }
- class ScaleLineStyle {
- final int scale;
- final Color color;
- final double width;
- final double height;
- const ScaleLineStyle({
- this.scale = -1,
- required this.color,
- required this.width,
- required this.height,
- });
- }
- class RulerRange {
- final double scale;
- final int begin;
- final int end;
- const RulerRange({
- required this.begin,
- required this.end,
- this.scale = 1,
- });
- }
|