tencent_cloud_chat_uikit_fl.../lib/ui/widgets/gestured_image.dart

476 lines
15 KiB
Dart

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:extended_image/extended_image.dart';
Map<Object?, GestureDetails?> _gestureDetailsCache =
<Object?, GestureDetails?>{};
///clear the gesture details
void clearGestureDetailsCache() {
_gestureDetailsCache.clear();
}
/// scale idea from https://github.com/flutter/flutter/blob/master/examples/layers/widgets/gestures.dart
/// zoom image
class GesturedImage extends ExtendedImageGesture {
const GesturedImage(ExtendedImageState extendedImageState,
{ImageBuilderForGesture? imageBuilder,
CanScaleImage? canScaleImage,
Key? key})
: super(extendedImageState,
imageBuilder: imageBuilder, canScaleImage: canScaleImage, key: key);
@override
GesturedImageState createState() => GesturedImageState();
}
class GesturedImageState extends ExtendedImageGestureState {
///details for gesture
GestureDetails? _gestureDetails;
late Offset _normalizedOffset;
double? _startingScale;
late Offset _startingOffset;
Offset? _pointerDownPosition;
late GestureAnimation _gestureAnimation;
GestureConfig? _gestureConfig;
ExtendedImageGesturePageViewState? _pageViewState;
@override
ExtendedImageSlidePageState? get extendedImageSlidePageState =>
widget.extendedImageState.slidePageState;
@override
GestureDetails? get gestureDetails => _gestureDetails;
@override
set gestureDetails(GestureDetails? value) {
if (mounted) {
setState(() {
_gestureDetails = value;
_gestureConfig?.gestureDetailsIsChanged?.call(_gestureDetails);
});
}
}
@override
GestureConfig? get imageGestureConfig => _gestureConfig;
@override
Offset? get pointerDownPosition => _pointerDownPosition;
@override
Widget build(BuildContext context) {
if (_gestureConfig!.cacheGesture) {
_gestureDetailsCache[widget.extendedImageState.imageStreamKey] =
_gestureDetails;
}
Widget image = ExtendedRawImage(
image: widget.extendedImageState.extendedImageInfo?.image,
width: widget.extendedImageState.imageWidget.width,
height: widget.extendedImageState.imageWidget.height,
scale: widget.extendedImageState.extendedImageInfo?.scale ?? 1.0,
color: widget.extendedImageState.imageWidget.color,
colorBlendMode: widget.extendedImageState.imageWidget.colorBlendMode,
fit: widget.extendedImageState.imageWidget.fit,
alignment: widget.extendedImageState.imageWidget.alignment,
repeat: widget.extendedImageState.imageWidget.repeat,
centerSlice: widget.extendedImageState.imageWidget.centerSlice,
matchTextDirection:
widget.extendedImageState.imageWidget.matchTextDirection,
invertColors: widget.extendedImageState.invertColors,
filterQuality: widget.extendedImageState.imageWidget.filterQuality,
beforePaintImage: widget.extendedImageState.imageWidget.beforePaintImage,
afterPaintImage: widget.extendedImageState.imageWidget.afterPaintImage,
gestureDetails: _gestureDetails,
);
if (extendedImageSlidePageState != null) {
image = widget.extendedImageState.imageWidget.heroBuilderForSlidingPage
?.call(image) ??
image;
if (extendedImageSlidePageState!.widget.slideType ==
SlideType.onlyImage) {
image = Transform.translate(
offset: extendedImageSlidePageState!.offset,
child: Transform.scale(
scale: extendedImageSlidePageState!.scale,
child: image,
),
);
}
}
image = widget.imageBuilder?.call(image) ?? image;
image = GestureDetector(
onScaleStart: handleScaleStart,
onScaleUpdate: handleScaleUpdate,
onScaleEnd: handleScaleEnd,
onDoubleTap: _handleDoubleTap,
child: image,
behavior: _gestureConfig?.hitTestBehavior,
);
image = Listener(
child: image,
onPointerDown: _handlePointerDown,
onPointerSignal: _handlePointerSignal,
behavior: _gestureConfig!.hitTestBehavior,
);
return image;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_pageViewState = null;
if (_gestureConfig!.inPageView) {
_pageViewState =
context.findAncestorStateOfType<ExtendedImageGesturePageViewState>();
_pageViewState?.extendedImageGestureState = this;
}
}
@override
void didUpdateWidget(GesturedImage oldWidget) {
super.didUpdateWidget(oldWidget);
_initGestureConfig();
_pageViewState = null;
if (_gestureConfig!.inPageView) {
_pageViewState =
context.findAncestorStateOfType<ExtendedImageGesturePageViewState>();
_pageViewState?.extendedImageGestureState = this;
}
}
@override
void dispose() {
_gestureAnimation.stop();
_gestureAnimation.dispose();
_pageViewState?.extendedImageGestureStates.remove(this);
super.dispose();
}
@override
void handleDoubleTap({double? scale, Offset? doubleTapPosition}) {
doubleTapPosition ??= _pointerDownPosition;
scale ??= _gestureConfig!.initialScale;
//scale = scale.clamp(_gestureConfig.minScale, _gestureConfig.maxScale);
handleScaleStart(ScaleStartDetails(focalPoint: doubleTapPosition!));
handleScaleUpdate(ScaleUpdateDetails(
focalPoint: doubleTapPosition,
scale: scale / _startingScale!,
focalPointDelta: Offset.zero,
));
if (scale < _gestureConfig!.minScale || scale > _gestureConfig!.maxScale) {
handleScaleEnd(ScaleEndDetails());
}
}
@override
void initState() {
super.initState();
_initGestureConfig();
}
@override
void reset() {
_gestureConfig = widget
.extendedImageState.imageWidget.initGestureConfigHandler
?.call(widget.extendedImageState) ??
GestureConfig();
gestureDetails = GestureDetails(
totalScale: _gestureConfig!.initialScale,
offset: Offset.zero,
initialAlignment: _gestureConfig!.initialAlignment,
);
}
@override
void slide() {
if (mounted) {
setState(() {
_gestureDetails!.slidePageOffset = extendedImageSlidePageState?.offset;
});
}
}
void _handleDoubleTap() {
if (widget.extendedImageState.imageWidget.onDoubleTap != null) {
widget.extendedImageState.imageWidget.onDoubleTap!(this);
return;
}
if (!mounted) {
return;
}
gestureDetails = GestureDetails(
offset: Offset.zero,
totalScale: _gestureConfig!.initialScale,
);
}
void _handlePointerDown(PointerDownEvent pointerDownEvent) {
_pointerDownPosition = pointerDownEvent.position;
_gestureAnimation.stop();
_pageViewState?.extendedImageGestureState = this;
}
void _handlePointerSignal(PointerSignalEvent event) {
if (event is PointerScrollEvent && event.kind == PointerDeviceKind.mouse) {
handleScaleStart(ScaleStartDetails(focalPoint: event.position));
final double dy = event.scrollDelta.dy;
final double dx = event.scrollDelta.dx;
handleScaleUpdate(ScaleUpdateDetails(
focalPoint: event.position,
scale: 1.0 +
_reverseIf((dy.abs() > dx.abs() ? dy : dx) *
_gestureConfig!.speed /
1000.0),
focalPointDelta: Offset.zero));
handleScaleEnd(ScaleEndDetails());
}
}
@override
void handleScaleEnd(ScaleEndDetails details) {
if (extendedImageSlidePageState != null &&
extendedImageSlidePageState!.isSliding) {
extendedImageSlidePageState!.endSlide(details);
return;
}
if (_pageViewState != null && _pageViewState!.isDraging) {
_pageViewState!.onDragEnd(DragEndDetails(
velocity: details.velocity,
primaryVelocity:
_pageViewState!.widget.scrollDirection == Axis.horizontal
? details.velocity.pixelsPerSecond.dx
: details.velocity.pixelsPerSecond.dy,
));
return;
}
//animate back to maxScale if gesture exceeded the maxScale specified
if (_gestureDetails!.totalScale!.greaterThan(_gestureConfig!.maxScale)) {
final double velocity =
(_gestureDetails!.totalScale! - _gestureConfig!.maxScale) /
_gestureConfig!.maxScale;
_gestureAnimation.animationScale(
_gestureDetails!.totalScale, _gestureConfig!.maxScale, velocity);
return;
}
//animate back to minScale if gesture fell smaller than the minScale specified
if (_gestureDetails!.totalScale!.lessThan(_gestureConfig!.minScale)) {
final double velocity =
(_gestureConfig!.minScale - _gestureDetails!.totalScale!) /
_gestureConfig!.minScale;
_gestureAnimation.animationScale(
_gestureDetails!.totalScale, _gestureConfig!.minScale, velocity);
return;
}
if (_gestureDetails!.actionType == ActionType.pan) {
// get magnitude from gesture velocity
final double magnitude = details.velocity.pixelsPerSecond.distance;
// do a significant magnitude
if (magnitude.greaterThanOrEqualTo(minMagnitude)) {
final Offset direction = details.velocity.pixelsPerSecond /
magnitude *
_gestureConfig!.inertialSpeed;
_gestureAnimation.animationOffset(
_gestureDetails!.offset, _gestureDetails!.offset! + direction);
}
}
}
@override
void handleScaleStart(ScaleStartDetails details) {
_gestureAnimation.stop();
_normalizedOffset = (details.focalPoint - _gestureDetails!.offset!) /
_gestureDetails!.totalScale!;
_startingScale = _gestureDetails!.totalScale;
_startingOffset = details.focalPoint;
}
@override
void handleScaleUpdate(ScaleUpdateDetails details) {
// 取消原组件对totalScale的判断。这样scale大于1也能执行slidePage的slide方法
if (extendedImageSlidePageState != null &&
_gestureDetails!.userOffset &&
_gestureDetails!.actionType == ActionType.pan) {
final Offset totalDelta = details.focalPointDelta;
bool updateGesture = false;
if (!extendedImageSlidePageState!.isSliding) {
if (totalDelta.dx != 0 &&
totalDelta.dx.abs().greaterThan(totalDelta.dy.abs())) {
if (_gestureDetails!.computeHorizontalBoundary) {
if (totalDelta.dx > 0) {
updateGesture = _gestureDetails!.boundary.left;
} else {
updateGesture = _gestureDetails!.boundary.right;
}
} else {
updateGesture = true;
}
}
if (totalDelta.dy != 0 &&
totalDelta.dy.abs().greaterThan(totalDelta.dx.abs())) {
if (_gestureDetails!.computeVerticalBoundary) {
if (totalDelta.dy < 0) {
updateGesture = _gestureDetails!.boundary.bottom;
} else {
updateGesture = _gestureDetails!.boundary.top;
}
} else {
updateGesture = true;
}
}
} else {
updateGesture = true;
}
if (details.focalPointDelta.distance.greaterThan(minGesturePageDelta) &&
updateGesture) {
extendedImageSlidePageState!.slide(
details.focalPointDelta,
extendedImageGestureState: this,
);
}
}
if (extendedImageSlidePageState != null &&
extendedImageSlidePageState!.isSliding) {
return;
}
// totalScale > 1 and page view is starting to move
if (_pageViewState != null) {
final ExtendedImageGesturePageViewState pageViewState = _pageViewState!;
final Axis axis = pageViewState.widget.scrollDirection;
final bool movePage = _pageViewState!.isDraging ||
(details.pointerCount == 1 &&
details.scale == 1 &&
_gestureDetails!.movePage(details.focalPointDelta, axis));
if (movePage) {
if (!pageViewState.isDraging) {
pageViewState
.onDragDown(DragDownDetails(globalPosition: details.focalPoint));
pageViewState.onDragStart(
DragStartDetails(globalPosition: details.focalPoint));
//assert(!pageViewState.isDraging);
}
Offset delta = details.focalPointDelta;
delta =
axis == Axis.horizontal ? Offset(delta.dx, 0) : Offset(0, delta.dy);
pageViewState.onDragUpdate(DragUpdateDetails(
globalPosition: details.focalPoint,
delta: delta,
primaryDelta: axis == Axis.horizontal ? delta.dx : delta.dy,
));
return;
}
}
final double? scale = widget.canScaleImage(_gestureDetails)
? clampScale(
_startingScale! * details.scale * _gestureConfig!.speed,
_gestureConfig!.animationMinScale,
_gestureConfig!.animationMaxScale)
: _gestureDetails!.totalScale;
//Round the scale to three points after comma to prevent shaking
//scale = roundAfter(scale, 3);
//no more zoom
if (details.scale != 1.0 &&
((_gestureDetails!.totalScale!
.equalTo(_gestureConfig!.animationMinScale) &&
scale!.lessThanOrEqualTo(_gestureDetails!.totalScale!)) ||
(_gestureDetails!.totalScale!
.equalTo(_gestureConfig!.animationMaxScale) &&
scale!.greaterThanOrEqualTo(_gestureDetails!.totalScale!)))) {
return;
}
final Offset offset = (details.scale == 1.0
? details.focalPoint * _gestureConfig!.speed
: _startingOffset) -
_normalizedOffset * scale!;
if (mounted &&
(offset != _gestureDetails!.offset ||
scale != _gestureDetails!.totalScale)) {
gestureDetails = GestureDetails(
offset: offset,
totalScale: scale,
gestureDetails: _gestureDetails,
actionType: details.scale != 1.0 ? ActionType.zoom : ActionType.pan);
}
}
void _initGestureConfig() {
final double? initialScale = _gestureConfig?.initialScale;
final InitialAlignment? initialAlignment = _gestureConfig?.initialAlignment;
_gestureConfig = widget
.extendedImageState.imageWidget.initGestureConfigHandler
?.call(widget.extendedImageState) ??
GestureConfig();
if (_gestureDetails == null ||
initialScale != _gestureConfig!.initialScale ||
initialAlignment != _gestureConfig!.initialAlignment) {
_gestureDetails = GestureDetails(
totalScale: _gestureConfig!.initialScale,
offset: Offset.zero,
initialAlignment: _gestureConfig!.initialAlignment,
);
}
if (_gestureConfig!.cacheGesture) {
final GestureDetails? cache =
_gestureDetailsCache[widget.extendedImageState.imageStreamKey];
if (cache != null) {
_gestureDetails = cache;
}
}
_gestureDetails ??= GestureDetails(
totalScale: _gestureConfig!.initialScale,
offset: Offset.zero,
);
_gestureAnimation = GestureAnimation(this, offsetCallBack: (Offset value) {
gestureDetails = GestureDetails(
offset: value,
totalScale: _gestureDetails!.totalScale,
gestureDetails: _gestureDetails);
}, scaleCallBack: (double scale) {
gestureDetails = GestureDetails(
offset: _gestureDetails!.offset,
totalScale: scale,
gestureDetails: _gestureDetails,
actionType: ActionType.zoom,
userOffset: false);
});
}
double _reverseIf(double scaleDetal) {
if (_gestureConfig?.reverseMousePointerScrollDirection ?? false) {
return -scaleDetal;
} else {
return scaleDetal;
}
}
}