mirror of
https://github.com/gskinnerTeam/flutter-wonderous-app.git
synced 2025-08-06 18:24:29 +08:00
Merge pull request #208 from gskinnerTeam/accessability-trackpadfix
Accessibility - Trackpad Fix
This commit is contained in:
@ -1,4 +1,6 @@
|
|||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
|
import 'package:wonders/ui/common/controls/trackpad_listener.dart';
|
||||||
|
|
||||||
class EightWaySwipeDetector extends StatefulWidget {
|
class EightWaySwipeDetector extends StatefulWidget {
|
||||||
const EightWaySwipeDetector({super.key, required this.child, this.threshold = 50, required this.onSwipe});
|
const EightWaySwipeDetector({super.key, required this.child, this.threshold = 50, required this.onSwipe});
|
||||||
@ -40,12 +42,16 @@ class _EightWaySwipeDetectorState extends State<EightWaySwipeDetector> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSwipeStart(d) {
|
void _trackpadSwipe(Offset delta) {
|
||||||
_isSwiping = true;
|
widget.onSwipe?.call(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleSwipeStart(DragStartDetails d) {
|
||||||
|
_isSwiping = d.kind != null;
|
||||||
_startPos = _endPos = d.localPosition;
|
_startPos = _endPos = d.localPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSwipeUpdate(d) {
|
void _handleSwipeUpdate(DragUpdateDetails d) {
|
||||||
_endPos = d.localPosition;
|
_endPos = d.localPosition;
|
||||||
_maybeTriggerSwipe();
|
_maybeTriggerSwipe();
|
||||||
}
|
}
|
||||||
@ -57,12 +63,24 @@ class _EightWaySwipeDetectorState extends State<EightWaySwipeDetector> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return TrackpadListener(
|
||||||
|
scrollSensitivity: 70,
|
||||||
|
onScroll: _trackpadSwipe,
|
||||||
|
child: GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onPanStart: _handleSwipeStart,
|
onPanStart: _handleSwipeStart,
|
||||||
onPanUpdate: _handleSwipeUpdate,
|
onPanUpdate: _handleSwipeUpdate,
|
||||||
onPanCancel: _resetSwipe,
|
onPanCancel: _resetSwipe,
|
||||||
onPanEnd: _handleSwipeEnd,
|
onPanEnd: _handleSwipeEnd,
|
||||||
child: widget.child);
|
supportedDevices: const {
|
||||||
|
// Purposely omitting PointerDeviceKind.trackpad.
|
||||||
|
PointerDeviceKind.mouse,
|
||||||
|
PointerDeviceKind.stylus,
|
||||||
|
PointerDeviceKind.touch,
|
||||||
|
PointerDeviceKind.unknown,
|
||||||
|
},
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
69
lib/ui/common/controls/trackpad_listener.dart
Normal file
69
lib/ui/common/controls/trackpad_listener.dart
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class TrackpadListener extends StatefulWidget {
|
||||||
|
final Widget? child;
|
||||||
|
final double scrollSensitivity;
|
||||||
|
final ValueChanged<Offset>? onScroll;
|
||||||
|
|
||||||
|
const TrackpadListener({
|
||||||
|
super.key,
|
||||||
|
this.child,
|
||||||
|
this.scrollSensitivity = 100,
|
||||||
|
this.onScroll,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TrackpadListener> createState() => _TrackpadListenerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TrackpadListenerState extends State<TrackpadListener> {
|
||||||
|
Offset _scrollOffset = Offset.zero;
|
||||||
|
|
||||||
|
void _handleTrackpadEvent(PointerSignalEvent event) {
|
||||||
|
// Directly process the event here.
|
||||||
|
if (event is PointerScrollEvent && event.kind == PointerDeviceKind.trackpad) {
|
||||||
|
Offset newScroll = _scrollOffset + event.scrollDelta;
|
||||||
|
newScroll = Offset(
|
||||||
|
newScroll.dx.clamp(-widget.scrollSensitivity, widget.scrollSensitivity),
|
||||||
|
newScroll.dy.clamp(-widget.scrollSensitivity, widget.scrollSensitivity),
|
||||||
|
);
|
||||||
|
_scrollOffset = newScroll;
|
||||||
|
_update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _update() {
|
||||||
|
Offset directionScroll = Offset.zero;
|
||||||
|
double sensitivity = widget.scrollSensitivity;
|
||||||
|
if (_scrollOffset.dy >= sensitivity) {
|
||||||
|
// Scroll down
|
||||||
|
_scrollOffset += Offset(0.0, -sensitivity);
|
||||||
|
directionScroll += Offset(0.0, -1.0);
|
||||||
|
} else if (_scrollOffset.dy <= -sensitivity) {
|
||||||
|
// Scroll up
|
||||||
|
_scrollOffset += Offset(0.0, sensitivity);
|
||||||
|
directionScroll += Offset(0.0, 1.0);
|
||||||
|
}
|
||||||
|
if (_scrollOffset.dx >= sensitivity) {
|
||||||
|
// Scroll left
|
||||||
|
_scrollOffset += Offset(-sensitivity, 0.0);
|
||||||
|
directionScroll += Offset(-1.0, 0.0);
|
||||||
|
} else if (_scrollOffset.dx <= -sensitivity) {
|
||||||
|
// Scroll right
|
||||||
|
_scrollOffset += Offset(sensitivity, 0.0);
|
||||||
|
directionScroll += Offset(1.0, 0.0);
|
||||||
|
}
|
||||||
|
if (directionScroll != Offset.zero) {
|
||||||
|
widget.onScroll?.call(directionScroll);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Listener(
|
||||||
|
onPointerSignal: _handleTrackpadEvent,
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ import 'package:wonders/ui/common/app_icons.dart';
|
|||||||
import 'package:wonders/ui/common/blend_mask.dart';
|
import 'package:wonders/ui/common/blend_mask.dart';
|
||||||
import 'package:wonders/ui/common/centered_box.dart';
|
import 'package:wonders/ui/common/centered_box.dart';
|
||||||
import 'package:wonders/ui/common/compass_divider.dart';
|
import 'package:wonders/ui/common/compass_divider.dart';
|
||||||
|
import 'package:wonders/ui/common/controls/trackpad_listener.dart';
|
||||||
import 'package:wonders/ui/common/curved_clippers.dart';
|
import 'package:wonders/ui/common/curved_clippers.dart';
|
||||||
import 'package:wonders/ui/common/fullscreen_keyboard_list_scroller.dart';
|
import 'package:wonders/ui/common/fullscreen_keyboard_list_scroller.dart';
|
||||||
import 'package:wonders/ui/common/google_maps_marker.dart';
|
import 'package:wonders/ui/common/google_maps_marker.dart';
|
||||||
@ -66,6 +67,13 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
|
|||||||
_scrollPos.value = _scroller.position.pixels;
|
_scrollPos.value = _scroller.position.pixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleTrackpadScroll(Offset direction) {
|
||||||
|
// Trackpad swipe up. Return to home if at the top.
|
||||||
|
if (_scroller.position.pixels == 0 && direction.dy > 0) {
|
||||||
|
_handleBackPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _handleBackPressed() => context.go(ScreenPaths.home);
|
void _handleBackPressed() => context.go(ScreenPaths.home);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -82,115 +90,119 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
|
|||||||
controller: _scroller,
|
controller: _scroller,
|
||||||
child: ColoredBox(
|
child: ColoredBox(
|
||||||
color: $styles.colors.offWhite,
|
color: $styles.colors.offWhite,
|
||||||
child: Stack(
|
child: TrackpadListener(
|
||||||
children: [
|
onScroll: _handleTrackpadScroll,
|
||||||
/// Background
|
scrollSensitivity: 120,
|
||||||
Positioned.fill(
|
child: Stack(
|
||||||
child: ColoredBox(color: widget.data.type.bgColor),
|
children: [
|
||||||
),
|
/// Background
|
||||||
|
Positioned.fill(
|
||||||
/// Top Illustration - Sits underneath the scrolling content, fades out as it scrolls
|
child: ColoredBox(color: widget.data.type.bgColor),
|
||||||
SizedBox(
|
|
||||||
height: illustrationHeight,
|
|
||||||
child: ValueListenableBuilder<double>(
|
|
||||||
valueListenable: _scrollPos,
|
|
||||||
builder: (_, value, child) {
|
|
||||||
// get some value between 0 and 1, based on the amt scrolled
|
|
||||||
double opacity = (1 - value / 700).clamp(0, 1);
|
|
||||||
return Opacity(opacity: opacity, child: child);
|
|
||||||
},
|
|
||||||
// This is due to a bug: https://github.com/flutter/flutter/issues/101872
|
|
||||||
child: RepaintBoundary(
|
|
||||||
child: _TopIllustration(
|
|
||||||
widget.data.type,
|
|
||||||
// Polish: Inject the content padding into the illustration as an offset, so it can center itself relative to the content
|
|
||||||
// this allows the background to extend underneath the vertical side nav when it has rounded corners.
|
|
||||||
fgOffset: Offset(widget.contentPadding.left / 2, 0),
|
|
||||||
)),
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
/// Scrolling content - Includes an invisible gap at the top, and then scrolls over the illustration
|
/// Top Illustration - Sits underneath the scrolling content, fades out as it scrolls
|
||||||
TopCenter(
|
SizedBox(
|
||||||
child: Padding(
|
height: illustrationHeight,
|
||||||
padding: widget.contentPadding,
|
child: ValueListenableBuilder<double>(
|
||||||
child: SizedBox(
|
valueListenable: _scrollPos,
|
||||||
child: FocusTraversalGroup(
|
builder: (_, value, child) {
|
||||||
child: FullscreenKeyboardListScroller(
|
// get some value between 0 and 1, based on the amt scrolled
|
||||||
scrollController: _scroller,
|
double opacity = (1 - value / 700).clamp(0, 1);
|
||||||
child: CustomScrollView(
|
return Opacity(opacity: opacity, child: child);
|
||||||
controller: _scroller,
|
},
|
||||||
scrollBehavior: ScrollConfiguration.of(context).copyWith(),
|
// This is due to a bug: https://github.com/flutter/flutter/issues/101872
|
||||||
key: PageStorageKey('editorial'),
|
child: RepaintBoundary(
|
||||||
slivers: [
|
child: _TopIllustration(
|
||||||
/// Invisible padding at the top of the list, so the illustration shows through the btm
|
widget.data.type,
|
||||||
SliverToBoxAdapter(
|
// Polish: Inject the content padding into the illustration as an offset, so it can center itself relative to the content
|
||||||
child: SizedBox(height: illustrationHeight),
|
// this allows the background to extend underneath the vertical side nav when it has rounded corners.
|
||||||
),
|
fgOffset: Offset(widget.contentPadding.left / 2, 0),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
/// Text content, animates itself to hide behind the app bar as it scrolls up
|
/// Scrolling content - Includes an invisible gap at the top, and then scrolls over the illustration
|
||||||
SliverToBoxAdapter(
|
TopCenter(
|
||||||
child: ValueListenableBuilder<double>(
|
child: Padding(
|
||||||
valueListenable: _scrollPos,
|
padding: widget.contentPadding,
|
||||||
builder: (_, value, child) {
|
child: SizedBox(
|
||||||
double offsetAmt = max(0, value * .3);
|
child: FocusTraversalGroup(
|
||||||
double opacity = (1 - offsetAmt / 150).clamp(0, 1);
|
child: FullscreenKeyboardListScroller(
|
||||||
return Transform.translate(
|
scrollController: _scroller,
|
||||||
offset: Offset(0, offsetAmt),
|
child: CustomScrollView(
|
||||||
child: Opacity(opacity: opacity, child: child),
|
controller: _scroller,
|
||||||
);
|
scrollBehavior: ScrollConfiguration.of(context).copyWith(),
|
||||||
},
|
key: PageStorageKey('editorial'),
|
||||||
child: _TitleText(widget.data, scroller: _scroller),
|
slivers: [
|
||||||
|
/// Invisible padding at the top of the list, so the illustration shows through the btm
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: SizedBox(height: illustrationHeight),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
/// Collapsing App bar, pins to the top of the list
|
/// Text content, animates itself to hide behind the app bar as it scrolls up
|
||||||
SliverAppBar(
|
SliverToBoxAdapter(
|
||||||
pinned: true,
|
child: ValueListenableBuilder<double>(
|
||||||
collapsedHeight: minAppBarHeight,
|
valueListenable: _scrollPos,
|
||||||
toolbarHeight: minAppBarHeight,
|
builder: (_, value, child) {
|
||||||
expandedHeight: maxAppBarHeight,
|
double offsetAmt = max(0, value * .3);
|
||||||
backgroundColor: Colors.transparent,
|
double opacity = (1 - offsetAmt / 150).clamp(0, 1);
|
||||||
elevation: 0,
|
return Transform.translate(
|
||||||
leading: SizedBox.shrink(),
|
offset: Offset(0, offsetAmt),
|
||||||
flexibleSpace: SizedBox.expand(
|
child: Opacity(opacity: opacity, child: child),
|
||||||
child: _AppBar(
|
);
|
||||||
widget.data.type,
|
},
|
||||||
scrollPos: _scrollPos,
|
child: _TitleText(widget.data, scroller: _scroller),
|
||||||
sectionIndex: _sectionIndex,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
/// Editorial content (text and images)
|
/// Collapsing App bar, pins to the top of the list
|
||||||
_ScrollingContent(widget.data, scrollPos: _scrollPos, sectionNotifier: _sectionIndex),
|
SliverAppBar(
|
||||||
],
|
pinned: true,
|
||||||
|
collapsedHeight: minAppBarHeight,
|
||||||
|
toolbarHeight: minAppBarHeight,
|
||||||
|
expandedHeight: maxAppBarHeight,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
leading: SizedBox.shrink(),
|
||||||
|
flexibleSpace: SizedBox.expand(
|
||||||
|
child: _AppBar(
|
||||||
|
widget.data.type,
|
||||||
|
scrollPos: _scrollPos,
|
||||||
|
sectionIndex: _sectionIndex,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
/// Editorial content (text and images)
|
||||||
|
_ScrollingContent(widget.data, scrollPos: _scrollPos, sectionNotifier: _sectionIndex),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
/// Home Btn
|
/// Home Btn
|
||||||
AnimatedBuilder(
|
AnimatedBuilder(
|
||||||
animation: _scroller,
|
animation: _scroller,
|
||||||
builder: (_, child) {
|
builder: (_, child) {
|
||||||
return AnimatedOpacity(
|
return AnimatedOpacity(
|
||||||
opacity: _scrollPos.value > 0 ? 0 : 1,
|
opacity: _scrollPos.value > 0 ? 0 : 1,
|
||||||
duration: $styles.times.med,
|
duration: $styles.times.med,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: backBtnAlign,
|
alignment: backBtnAlign,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all($styles.insets.sm),
|
padding: EdgeInsets.all($styles.insets.sm),
|
||||||
child: BackBtn(icon: AppIcons.north, onPressed: _handleBackPressed),
|
child: BackBtn(icon: AppIcons.north, onPressed: _handleBackPressed),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -4,6 +4,7 @@ import 'package:wonders/logic/data/wonder_data.dart';
|
|||||||
import 'package:wonders/ui/common/app_icons.dart';
|
import 'package:wonders/ui/common/app_icons.dart';
|
||||||
import 'package:wonders/ui/common/controls/app_header.dart';
|
import 'package:wonders/ui/common/controls/app_header.dart';
|
||||||
import 'package:wonders/ui/common/controls/app_page_indicator.dart';
|
import 'package:wonders/ui/common/controls/app_page_indicator.dart';
|
||||||
|
import 'package:wonders/ui/common/controls/trackpad_listener.dart';
|
||||||
import 'package:wonders/ui/common/gradient_container.dart';
|
import 'package:wonders/ui/common/gradient_container.dart';
|
||||||
import 'package:wonders/ui/common/ignore_pointer.dart';
|
import 'package:wonders/ui/common/ignore_pointer.dart';
|
||||||
import 'package:wonders/ui/common/previous_next_navigation.dart';
|
import 'package:wonders/ui/common/previous_next_navigation.dart';
|
||||||
@ -133,6 +134,12 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleTrackpadScroll(Offset direction) {
|
||||||
|
if (direction.dy < 0) {
|
||||||
|
_showDetailsPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_fadeInOnNextBuild == true) {
|
if (_fadeInOnNextBuild == true) {
|
||||||
@ -142,25 +149,29 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
|
|
||||||
return _swipeController.wrapGestureDetector(Container(
|
return _swipeController.wrapGestureDetector(Container(
|
||||||
color: $styles.colors.black,
|
color: $styles.colors.black,
|
||||||
child: PreviousNextNavigation(
|
child: TrackpadListener(
|
||||||
listenToMouseWheel: false,
|
scrollSensitivity: 60,
|
||||||
onPreviousPressed: () => _handlePrevNext(-1),
|
onScroll: _handleTrackpadScroll,
|
||||||
onNextPressed: () => _handlePrevNext(1),
|
child: PreviousNextNavigation(
|
||||||
child: Stack(
|
listenToMouseWheel: false,
|
||||||
children: [
|
onPreviousPressed: () => _handlePrevNext(-1),
|
||||||
/// Background
|
onNextPressed: () => _handlePrevNext(1),
|
||||||
..._buildBgAndClouds(),
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
/// Background
|
||||||
|
..._buildBgAndClouds(),
|
||||||
|
|
||||||
/// Wonders Illustrations (main content)
|
/// Wonders Illustrations (main content)
|
||||||
_buildMgPageView(),
|
_buildMgPageView(),
|
||||||
|
|
||||||
/// Foreground illustrations and gradients
|
/// Foreground illustrations and gradients
|
||||||
_buildFgAndGradients(),
|
_buildFgAndGradients(),
|
||||||
|
|
||||||
/// Controls that float on top of the various illustrations
|
/// Controls that float on top of the various illustrations
|
||||||
_buildFloatingUi(),
|
_buildFloatingUi(),
|
||||||
],
|
],
|
||||||
).maybeAnimate().fadeIn(),
|
).maybeAnimate().fadeIn(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -173,62 +173,63 @@ class _PhotoGalleryState extends State<PhotoGallery> {
|
|||||||
return FullscreenKeyboardListener(
|
return FullscreenKeyboardListener(
|
||||||
onKeyDown: _handleKeyDown,
|
onKeyDown: _handleKeyDown,
|
||||||
child: ValueListenableBuilder<List<String>>(
|
child: ValueListenableBuilder<List<String>>(
|
||||||
valueListenable: _photoIds,
|
valueListenable: _photoIds,
|
||||||
builder: (_, value, __) {
|
builder: (_, value, __) {
|
||||||
if (value.isEmpty) {
|
if (value.isEmpty) {
|
||||||
return Center(child: AppLoadingIndicator());
|
return Center(child: AppLoadingIndicator());
|
||||||
}
|
}
|
||||||
Size imgSize = context.isLandscape
|
Size imgSize = context.isLandscape
|
||||||
? Size(context.widthPx * .5, context.heightPx * .66)
|
? Size(context.widthPx * .5, context.heightPx * .66)
|
||||||
: Size(context.widthPx * .66, context.heightPx * .5);
|
: Size(context.widthPx * .66, context.heightPx * .5);
|
||||||
imgSize = (widget.imageSize ?? imgSize) * _scale;
|
imgSize = (widget.imageSize ?? imgSize) * _scale;
|
||||||
// Get transform offset for the current _index
|
// Get transform offset for the current _index
|
||||||
final padding = $styles.insets.md;
|
final padding = $styles.insets.md;
|
||||||
var gridOffset = _calculateCurrentOffset(padding, imgSize);
|
var gridOffset = _calculateCurrentOffset(padding, imgSize);
|
||||||
gridOffset += Offset(0, -context.mq.padding.top / 2);
|
gridOffset += Offset(0, -context.mq.padding.top / 2);
|
||||||
final offsetTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration;
|
final offsetTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration;
|
||||||
final cutoutTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration * .5;
|
final cutoutTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration * .5;
|
||||||
return _AnimatedCutoutOverlay(
|
return _AnimatedCutoutOverlay(
|
||||||
animationKey: ValueKey(_index),
|
animationKey: ValueKey(_index),
|
||||||
cutoutSize: imgSize,
|
cutoutSize: imgSize,
|
||||||
swipeDir: _lastSwipeDir,
|
swipeDir: _lastSwipeDir,
|
||||||
duration: cutoutTweenDuration,
|
duration: cutoutTweenDuration,
|
||||||
opacity: _scale == 1 ? .7 : .5,
|
opacity: _scale == 1 ? .7 : .5,
|
||||||
enabled: useClipPathWorkAroundForWeb == false,
|
enabled: useClipPathWorkAroundForWeb == false,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
// Place content in overflow box, to allow it to flow outside the parent
|
// Place content in overflow box, to allow it to flow outside the parent
|
||||||
child: OverflowBox(
|
child: OverflowBox(
|
||||||
maxWidth: _gridSize * imgSize.width + padding * (_gridSize - 1),
|
maxWidth: _gridSize * imgSize.width + padding * (_gridSize - 1),
|
||||||
maxHeight: _gridSize * imgSize.height + padding * (_gridSize - 1),
|
maxHeight: _gridSize * imgSize.height + padding * (_gridSize - 1),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
// Detect swipes in order to change index
|
// Detect swipes in order to change index
|
||||||
child: EightWaySwipeDetector(
|
child: EightWaySwipeDetector(
|
||||||
onSwipe: _handleSwipe,
|
onSwipe: _handleSwipe,
|
||||||
threshold: 30,
|
threshold: 30,
|
||||||
// A tween animation builder moves from image to image based on current offset
|
// A tween animation builder moves from image to image based on current offset
|
||||||
child: TweenAnimationBuilder<Offset>(
|
child: TweenAnimationBuilder<Offset>(
|
||||||
tween: Tween(begin: gridOffset, end: gridOffset),
|
tween: Tween(begin: gridOffset, end: gridOffset),
|
||||||
duration: offsetTweenDuration,
|
duration: offsetTweenDuration,
|
||||||
curve: Curves.easeOut,
|
curve: Curves.easeOut,
|
||||||
builder: (_, value, child) => Transform.translate(offset: value, child: child),
|
builder: (_, value, child) => Transform.translate(offset: value, child: child),
|
||||||
child: FocusTraversalGroup(
|
child: FocusTraversalGroup(
|
||||||
//policy: OrderedTraversalPolicy(),
|
//policy: OrderedTraversalPolicy(),
|
||||||
child: GridView.count(
|
child: GridView.count(
|
||||||
physics: NeverScrollableScrollPhysics(),
|
physics: NeverScrollableScrollPhysics(),
|
||||||
crossAxisCount: _gridSize,
|
crossAxisCount: _gridSize,
|
||||||
childAspectRatio: imgSize.aspectRatio,
|
childAspectRatio: imgSize.aspectRatio,
|
||||||
mainAxisSpacing: padding,
|
mainAxisSpacing: padding,
|
||||||
crossAxisSpacing: padding,
|
crossAxisSpacing: padding,
|
||||||
children: List.generate(_imgCount, (i) => _buildImage(i, swipeDuration, imgSize)),
|
children: List.generate(_imgCount, (i) => _buildImage(i, swipeDuration, imgSize)),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}),
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user