flutter_aliplayer/example/lib/widget/aliyun_slider.dart

594 lines
17 KiB
Dart
Raw Permalink Normal View History

2023-12-06 11:23:26 +08:00
import 'dart:math' as math;
import 'dart:ui' show lerpDouble;
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class AliyunSlider extends StatefulWidget {
const AliyunSlider({
Key key,
@required this.value,
@required this.onChanged,
this.bufferValue,
this.onChangeStart,
this.onChangeEnd,
this.onTapDot,
this.min = 0.0,
this.max = 1.0,
this.divisions,
this.activeColor,
this.trackColor,
this.bufferColor,
this.dotList,
this.thumbColor = CupertinoColors.white,
})
: assert(value != null),
assert(min != null),
assert(max != null),
assert(value >= min && value <= max),
assert(divisions == null || divisions > 0),
assert(thumbColor != null),
super(key: key);
final double value;
final double bufferValue;
final ValueChanged<double> onChanged;
final ValueChanged<double> onChangeStart;
final ValueChanged<double> onChangeEnd;
final ValueChanged<double> onTapDot;
final double min;
final double max;
final int divisions;
final Color activeColor;
final Color trackColor;
final Color bufferColor;
final Color thumbColor;
final List<int> dotList;
@override
_AliyunSliderState createState() => _AliyunSliderState();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DoubleProperty('value', value));
properties.add(DoubleProperty('min', min));
properties.add(DoubleProperty('max', max));
}
}
class _AliyunSliderState extends State<AliyunSlider>
with TickerProviderStateMixin {
void _handleChanged(double value) {
assert(widget.onChanged != null);
final double lerpValue = lerpDouble(widget.min, widget.max, value);
if (lerpValue != widget.value) {
widget.onChanged(lerpValue);
}
}
void _handleDragStart(double value) {
assert(widget.onChangeStart != null);
widget.onChangeStart(lerpDouble(widget.min, widget.max, value));
}
void _handleDragEnd(double value) {
assert(widget.onChangeEnd != null);
widget.onChangeEnd(lerpDouble(widget.min, widget.max, value));
}
@override
Widget build(BuildContext context) {
return _AliyunSliderRenderObjectWidget(
value: (widget.value - widget.min) / (widget.max - widget.min),
max: widget.max - widget.min,
bufferValue:
(widget.bufferValue - widget.min) / (widget.max - widget.min),
divisions: widget.divisions,
activeColor: CupertinoDynamicColor.resolve(
widget.activeColor ?? CupertinoTheme
.of(context)
.primaryColor,
context,
),
trackColor: CupertinoDynamicColor.resolve(
widget.trackColor ?? CupertinoColors.systemFill,
context,
),
bufferColor: CupertinoDynamicColor.resolve(
widget.bufferColor ?? CupertinoTheme
.of(context)
.primaryColor,
context,
),
thumbColor: widget.thumbColor,
onChanged: widget.onChanged != null ? _handleChanged : null,
onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
onTapDot: widget.onTapDot,
vsync: this,
dotList: widget.dotList,
);
}
}
class _AliyunSliderRenderObjectWidget extends LeafRenderObjectWidget {
const _AliyunSliderRenderObjectWidget({
Key key,
this.value,
this.max,
this.bufferValue,
this.divisions,
this.activeColor,
this.trackColor,
this.bufferColor,
this.thumbColor,
this.onChanged,
this.onChangeStart,
this.onChangeEnd,
this.onTapDot,
this.vsync,
this.dotList,
}) : super(key: key);
final double value;
final double max;
final double bufferValue;
final int divisions;
final Color activeColor;
final Color trackColor;
final Color bufferColor;
final Color thumbColor;
final ValueChanged<double> onChanged;
final ValueChanged<double> onChangeStart;
final ValueChanged<double> onChangeEnd;
final ValueChanged<double> onTapDot;
final TickerProvider vsync;
final List<int> dotList;
@override
_RenderAliyunSlider createRenderObject(BuildContext context) {
return _RenderAliyunSlider(
value: value,
max: max,
bufferValue: bufferValue,
divisions: divisions,
activeColor: activeColor,
bufferColor: bufferColor,
thumbColor: CupertinoDynamicColor.resolve(thumbColor, context),
// trackColor:
// CupertinoDynamicColor.resolve(CupertinoColors.systemFill, context),
trackColor: trackColor,
onChanged: onChanged,
onChangeStart: onChangeStart,
onChangeEnd: onChangeEnd,
onTapDot: onTapDot,
vsync: vsync,
dotList: dotList,
textDirection: Directionality.of(context),
);
}
@override
void updateRenderObject(BuildContext context,
_RenderAliyunSlider renderObject) {
renderObject
..value = value
..max = max
..dotList = dotList
..bufferValue = bufferValue
..divisions = divisions
..activeColor = activeColor
..thumbColor = CupertinoDynamicColor.resolve(thumbColor, context)
..trackColor = trackColor
..bufferColor = bufferColor
// CupertinoDynamicColor.resolve(CupertinoColors.systemFill, context)
..onChanged = onChanged
..onChangeStart = onChangeStart
..onChangeEnd = onChangeEnd
..onTapDot = onTapDot
..textDirection = Directionality.of(context);
}
}
const double _kPadding = 8.0;
const double _kSliderHeight = 2.0 * (CupertinoThumbPainter.radius + _kPadding);
const double _kSliderWidth = 176.0; // Matches Material Design slider.
const Duration _kDiscreteTransitionDuration = Duration(milliseconds: 500);
// Matches iOS implementation of material slider.
const double _kAdjustmentUnit = 0.1;
class _RenderAliyunSlider extends RenderConstrainedBox {
_RenderAliyunSlider({
@required double value,
double bufferValue,
int divisions,
Color activeColor,
Color thumbColor,
Color trackColor,
Color bufferColor,
ValueChanged<double> onChanged,
this.onChangeStart,
this.onChangeEnd,
this.dotList,
this.onTapDot,
this.max,
TickerProvider vsync,
@required TextDirection textDirection,
})
: assert(value != null && value >= 0.0 && value <= 1.0),
assert(textDirection != null),
_value = value,
_bufferValue = bufferValue,
_divisions = divisions,
_activeColor = activeColor,
_thumbColor = thumbColor,
_trackColor = trackColor,
_bufferColor = bufferColor,
_onChanged = onChanged,
_textDirection = textDirection,
super(
additionalConstraints: const BoxConstraints.tightFor(
width: _kSliderWidth, height: _kSliderHeight)) {
_tapGesture = TapGestureRecognizer()
..onTap = _handleTap;
_drag = HorizontalDragGestureRecognizer()
..onStart = _handleDragStart
..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd;
_position = AnimationController(
value: value,
duration: _kDiscreteTransitionDuration,
vsync: vsync,
)
..addListener(markNeedsPaint);
}
double get value => _value;
double _value;
double max;
List<int> dotList;
double tapCurrentValue;
set value(double newValue) {
assert(newValue != null && newValue >= 0.0 && newValue <= 1.0);
if (newValue == _value) return;
_value = newValue;
if (divisions != null)
_position.animateTo(newValue, curve: Curves.fastOutSlowIn);
else
_position.value = newValue;
markNeedsSemanticsUpdate();
}
double get bufferValue => _bufferValue;
double _bufferValue;
set bufferValue(double value) {
if (value == _bufferValue) return;
_bufferValue = value;
markNeedsPaint();
}
int get divisions => _divisions;
int _divisions;
set divisions(int value) {
if (value == _divisions) return;
_divisions = value;
markNeedsPaint();
}
Color get activeColor => _activeColor;
Color _activeColor;
set activeColor(Color value) {
if (value == _activeColor) return;
_activeColor = value;
markNeedsPaint();
}
Color get thumbColor => _thumbColor;
Color _thumbColor;
set thumbColor(Color value) {
if (value == _thumbColor) return;
_thumbColor = value;
markNeedsPaint();
}
Color get trackColor => _trackColor;
Color _trackColor;
set trackColor(Color value) {
if (value == _trackColor) return;
_trackColor = value;
markNeedsPaint();
}
Color get bufferColor => _bufferColor;
Color _bufferColor;
set bufferColor(Color value) {
if (value == _bufferColor) return;
_bufferColor = value;
markNeedsPaint();
}
ValueChanged<double> get onChanged => _onChanged;
ValueChanged<double> _onChanged;
set onChanged(ValueChanged<double> value) {
if (value == _onChanged) return;
final bool wasInteractive = isInteractive;
_onChanged = value;
if (wasInteractive != isInteractive) markNeedsSemanticsUpdate();
}
ValueChanged<double> onChangeStart;
ValueChanged<double> onChangeEnd;
ValueChanged<double> onTapDot;
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
assert(value != null);
if (_textDirection == value) return;
_textDirection = value;
markNeedsPaint();
}
AnimationController _position;
HorizontalDragGestureRecognizer _drag;
TapGestureRecognizer _tapGesture;
double _currentDragValue = 0.0;
double get _discretizedCurrentDragValue {
double dragValue = _currentDragValue.clamp(0.0, 1.0) as double;
if (divisions != null)
dragValue = (dragValue * divisions).round() / divisions;
return dragValue;
}
double get _trackLeft => _kPadding;
double get _trackRight => size.width - _kPadding;
double get _thumbCenter {
double visualPosition;
switch (textDirection) {
case TextDirection.rtl:
visualPosition = 1.0 - _value;
break;
case TextDirection.ltr:
visualPosition = _value;
break;
}
return lerpDouble(_trackLeft + CupertinoThumbPainter.radius,
_trackRight - CupertinoThumbPainter.radius, visualPosition);
}
double _dotCenter(double percent) {
return lerpDouble(_trackLeft + CupertinoThumbPainter.radius,
_trackRight - CupertinoThumbPainter.radius / 3, percent);
}
double get _bufferRight {
double visualPosition;
switch (textDirection) {
case TextDirection.rtl:
visualPosition = 1.0 - _bufferValue - _value;
break;
case TextDirection.ltr:
visualPosition = _bufferValue - _value;
break;
}
return lerpDouble(_trackLeft + CupertinoThumbPainter.radius,
_trackRight - CupertinoThumbPainter.radius, visualPosition);
}
bool get isInteractive => onChanged != null;
void _handleTap() {
if (onTapDot != null) {
onTapDot.call(tapCurrentValue);
}
}
void _handleDragStart(DragStartDetails details) =>
_startInteraction(details.globalPosition);
void _handleDragUpdate(DragUpdateDetails details) {
if (isInteractive) {
final double extent = math.max(_kPadding,
size.width - 2.0 * (_kPadding + CupertinoThumbPainter.radius));
final double valueDelta = details.primaryDelta / extent;
switch (textDirection) {
case TextDirection.rtl:
_currentDragValue -= valueDelta;
break;
case TextDirection.ltr:
_currentDragValue += valueDelta;
break;
}
onChanged(_discretizedCurrentDragValue);
}
}
void _handleDragEnd(DragEndDetails details) => _endInteraction();
void _startInteraction(Offset globalPosition) {
if (isInteractive) {
if (onChangeStart != null) {
onChangeStart(_discretizedCurrentDragValue);
}
_currentDragValue = _value;
onChanged(_discretizedCurrentDragValue);
}
}
void _endInteraction() {
if (onChangeEnd != null) {
onChangeEnd(_discretizedCurrentDragValue);
}
_currentDragValue = 0.0;
}
@override
bool hitTestSelf(Offset position) {
if ((position.dx - _thumbCenter).abs() <
CupertinoThumbPainter.radius + _kPadding) {
return true;
} else {
for (var value in dotList) {
if (((position.dx - _dotCenter(value / max)).abs() <
CupertinoThumbPainter.radius / 3 + _kPadding)) {
tapCurrentValue = value.toDouble();
return true;
}
}
}
return false;
}
@override
void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
assert(debugHandleEvent(event, entry));
if (event is PointerDownEvent && isInteractive) {
_tapGesture.addPointer(event);
_drag.addPointer(event);
}
}
@override
void paint(PaintingContext context, Offset offset) {
double visualPosition;
Color leftColor;
Color rightColor;
switch (textDirection) {
case TextDirection.rtl:
visualPosition = 1.0 - _position.value;
leftColor = _activeColor;
rightColor = trackColor;
break;
case TextDirection.ltr:
visualPosition = _position.value;
leftColor = trackColor;
rightColor = _activeColor;
break;
}
final double trackCenter = offset.dy + size.height / 2.0;
final double trackLeft = offset.dx + _trackLeft;
final double trackTop = trackCenter - 1.0;
final double trackBottom = trackCenter + 1.0;
final double trackRight = offset.dx + _trackRight;
final double trackActive = offset.dx + _thumbCenter;
final double buffingRight = trackActive + _bufferRight;
final Canvas canvas = context.canvas;
///未播放进度
if (visualPosition < 1.0) {
final Paint paint = Paint()
..color = leftColor;
canvas.drawRRect(
RRect.fromLTRBXY(
trackActive, trackTop, trackRight, trackBottom, 1.0, 1.0),
paint);
}
///buffering进度
if (visualPosition > 0.0 && bufferValue >= 0) {
final Paint paint = Paint()
..color = _bufferColor;
//buffre end draw
if (bufferValue >= 1.0) {
canvas.drawRRect(
RRect.fromLTRBXY(
trackActive, trackTop, trackRight, trackBottom, 1.0, 1.0),
paint);
} else {
canvas.drawRRect(
RRect.fromLTRBXY(
trackActive, trackTop, buffingRight, trackBottom, 1.0, 1.0),
paint);
}
///播放进度
if (visualPosition > 0.0) {
final Paint paint = Paint()
..color = rightColor;
canvas.drawRRect(
RRect.fromLTRBXY(
trackLeft, trackTop, trackActive, trackBottom, 1.0, 1.0),
paint);
}
}
///打点
if (dotList != null) {
for (var value in dotList) {
var dotCenter = _dotCenter(value / max) + offset.dx;
final Offset thumbCenter = Offset(dotCenter, trackCenter);
CupertinoThumbPainter(color: Colors.red).paint(
canvas,
Rect.fromCircle(
center: thumbCenter, radius: CupertinoThumbPainter.radius / 3));
}
}
///thumb
if (visualPosition >= 1.0) {
final Offset thumbCenter = Offset(trackRight, trackCenter);
CupertinoThumbPainter(color: thumbColor).paint(
canvas,
Rect.fromCircle(
center: thumbCenter, radius: CupertinoThumbPainter.radius));
} else {
final Offset thumbCenter = Offset(trackActive, trackCenter);
CupertinoThumbPainter(color: thumbColor).paint(
canvas,
Rect.fromCircle(
center: thumbCenter, radius: CupertinoThumbPainter.radius));
}
}
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.isSemanticBoundary = isInteractive;
if (isInteractive) {
config.textDirection = textDirection;
config.onIncrease = _increaseAction;
config.onDecrease = _decreaseAction;
config.value = '${(value * 100).round()}%';
config.increasedValue =
'${((value + _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%';
config.decreasedValue =
'${((value - _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%';
}
}
double get _semanticActionUnit =>
divisions != null ? 1.0 / divisions : _kAdjustmentUnit;
void _increaseAction() {
if (isInteractive)
onChanged((value + _semanticActionUnit).clamp(0.0, 1.0) as double);
}
void _decreaseAction() {
if (isInteractive)
onChanged((value - _semanticActionUnit).clamp(0.0, 1.0) as double);
}
}