主要解决系统拖条不便于自定义样式,和排版奇怪的问题
import 'dart:math';
import 'package:flutter/material.dart';
class CustomSlider extends StatelessWidget {
final double width;
final double value;
final void Function(double value, bool isEnd) valueChanged;
final Widget Function(BuildContext context, Size size)? activeBuilder;
final Widget Function(BuildContext context, Size size)? inactiveBuilder;
final Widget Function(BuildContext context)? sliderBarBuilder;
const CustomSlider({
required this.value,
required this.valueChanged,
super.key,
this.width = 10,
this.sliderBarBuilder,
this.activeBuilder,
this.inactiveBuilder,
});
@override
Widget build(BuildContext context) {
late Widget sliderBar;
if (sliderBarBuilder != null) {
sliderBar = sliderBarBuilder!(context);
} else {
sliderBar = Container(
width: 24,
height: 24,
decoration: const ShapeDecoration(
color: Colors.white,
shape: OvalBorder(
side: BorderSide(width: 1, color: Color(0xFF954CFF)),
),
shadows: [
BoxShadow(
color: Color(0x66AAA6AA),
blurRadius: 2,
offset: Offset(1, 1),
spreadRadius: 0,
)
],
),
);
}
return LayoutBuilder(builder: (BuildContext context, constraints) {
double maxWidth = constraints.maxWidth;
double sliderWidth = maxWidth;
double sliderHeight = width;
late Widget inactive;
if (inactiveBuilder != null) {
inactive = inactiveBuilder!(context, Size(sliderWidth, sliderHeight));
} else {
inactive = Container(
width: sliderWidth,
height: sliderHeight,
decoration: ShapeDecoration(
color: const Color(0xFFF3F2F4),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
),
),
);
}
double activeWidth = sliderWidth * value;
double activeHeight = sliderHeight;
late Widget active;
if (activeBuilder != null) {
active = activeBuilder!(context, Size(activeWidth, activeHeight));
} else {
active = Container(
width: activeWidth,
height: activeHeight,
decoration: ShapeDecoration(
color: const Color(0xFF954CFF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
),
),
);
}
return GestureDetector(
child: Stack(
alignment: AlignmentDirectional.centerStart,
children: [
inactive,
active,
Align(
alignment: FractionalOffset(value, 0.5),
child: sliderBar,
),
],
),
onTapDown: (details) {
updateDx(details.localPosition, maxWidth);
},
onTapUp: (details) {
updateDx(details.localPosition, maxWidth, isEnd: true);
},
onPanUpdate: (details) {
updateDx(details.localPosition, maxWidth);
},
);
});
}
void updateDx(Offset value, double maxX, {bool isEnd = false}) {
double dx = value.dx;
dx = max(0, dx);
dx = min(maxX, dx);
valueChanged(dx / maxX, isEnd);
}
}
旧版的一些方法,没有彻底解决自定义的一些需求,但是使用画笔绘制性能会好些,对于简单的需求可以参考以下实现。
enum SliderDirection {
vertical,
horizontal
}
class SliderBar extends StatelessWidget {
final Size size
final Widget child
SliderBar({this.child, this.size = const Size(24, 24)})
@override
Widget build(BuildContext context) {
return SizedBox(
width: size.width,
height: size.height,
child: child ?? thumb(),
)
}
Widget thumb() {
return Container(width: size.width, height: size.height,).backgroundColor(color: Colors.white).cornerRadius(radius: min(size.width, size.height))
}
}
// ignore: must_be_immutable
class CustomSlider extends StatelessWidget {
//UI
//不论竖着还是横着,width是短边,height是长边,短边默认值20,长边撑满
final double width
final double height
final Color activeTrackColor
final SliderBar sliderBar
final double value
final Function(double, bool) valueChanged
final SliderDirection direction
final Decoration background
CustomSlider(this.value, this.valueChanged, {
this.width = 24,
this.height = double.infinity,
this.direction = SliderDirection.horizontal,
this.background,
this.activeTrackColor,
this.sliderBar
})
double dx = 0
double maxX = 0
bool get isVertical => direction == SliderDirection.vertical
@override
Widget build(BuildContext context) {
Decoration decoration = this.background ?? BoxDecoration(
color: Colors.grey[300]
)
return GestureDetector(
child: Stack(
alignment: AlignmentDirectional.center,
children: [
Container(
height: isVertical ? height : width,
width: isVertical ? width : height,
child: CustomPaint(
painter: SliderPainter(
(double maxDx) {
maxX = maxDx;
return value * maxDx;
},
vertical: isVertical,
activeTrackColor: activeTrackColor,
),
),
decoration: decoration
),
Align(child: sliderBar, alignment: FractionalOffset(isVertical ? 0.5 : value,isVertical ? 1 - value : 0.5)),
],
).size(width: isVertical ? max(sliderBar.size.width, width) : height, height: isVertical ? height : null),
onTapDown: (details) {
updateDx(getPoint(context, details.globalPosition))
},
onTapUp: (details) {
setValue(true)
},
onPanUpdate: (details){
updateDx(getPoint(context, details.globalPosition))
},
onPanEnd: (details){
setValue(true)
},
)
}
Offset getPoint(BuildContext context, Offset globalPosition) {
RenderBox renderBox = context.findRenderObject()
return renderBox.globalToLocal(globalPosition)
}
void updateDx(Offset value) {
dx = isVertical ? value.dy : value.dx
dx = dx < 0 ? 0 : dx
dx = dx > maxX ? maxX : dx
setValue(false)
}
void setValue(bool isEnd) {
valueChanged(isVertical ? ((maxX - dx) / maxX) : dx/maxX, isEnd)
}
}
class SliderPainter extends CustomPainter {
final Color activeTrackColor
final double Function(double maxDx) getDx
final bool vertical
SliderPainter(this.getDx, {
this.activeTrackColor,
this.vertical = false,
})
/// 初始化画笔
var lineP = Paint()
..strokeCap = StrokeCap.butt
var thumbP = Paint()
..strokeCap = StrokeCap.round
@override
void paint(Canvas canvas, Size size) {
double width = vertical ? size.width : size.height
double height = vertical ? size.height : size.width
lineP.strokeWidth = width
lineP.color = this.activeTrackColor ?? Colors.blue
double dx = getDx(height)
Offset endPoint = Offset.zero
double centerW = width / 2
/// 通过canvas画线
if (vertical == true) {
endPoint = Offset(centerW, height - dx)
canvas.drawLine(Offset(centerW, height), endPoint , lineP)
} else {
endPoint = Offset(dx, centerW)
canvas.drawLine(Offset(0, centerW), endPoint , lineP)
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true
}
}