Initial commit
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
mixin BannerCarouselMixin<T extends StatefulWidget>
|
||||
on State<T>, WidgetsBindingObserver {
|
||||
final PageController pageController = PageController();
|
||||
Timer? _autoPlayTimer;
|
||||
Timer? _resumeTimer;
|
||||
int currentIndex = 0;
|
||||
bool hovering = false;
|
||||
bool _manualPaused = false;
|
||||
bool _appPaused = false;
|
||||
bool _autoPageChangeInProgress = false;
|
||||
double? lastBannerWidth;
|
||||
bool _tickerVisible = true;
|
||||
|
||||
static const autoInterval = Duration(seconds: 5);
|
||||
static const _manualPauseDuration = Duration(seconds: 8);
|
||||
static const slideDuration = Duration(milliseconds: 450);
|
||||
static const slideCurve = Curves.easeInOutCubic;
|
||||
|
||||
int get itemCount;
|
||||
bool get isActive;
|
||||
void emitActiveItem();
|
||||
void precacheAdjacentSlides();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
emitActiveItem();
|
||||
syncAutoPlay();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_autoPlayTimer?.cancel();
|
||||
_resumeTimer?.cancel();
|
||||
pageController.dispose();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final visible = TickerMode.of(context);
|
||||
if (visible == _tickerVisible) return;
|
||||
_tickerVisible = visible;
|
||||
if (visible) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted || !_tickerVisible) return;
|
||||
emitActiveItem();
|
||||
precacheAdjacentSlides();
|
||||
});
|
||||
syncAutoPlay();
|
||||
} else {
|
||||
stopAutoPlay();
|
||||
_resumeTimer?.cancel();
|
||||
_manualPaused = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
final paused =
|
||||
state == AppLifecycleState.paused ||
|
||||
state == AppLifecycleState.inactive ||
|
||||
state == AppLifecycleState.hidden;
|
||||
if (paused == _appPaused) return;
|
||||
_appPaused = paused;
|
||||
paused ? stopAutoPlay() : syncAutoPlay();
|
||||
}
|
||||
|
||||
bool get shouldAutoPlay =>
|
||||
mounted &&
|
||||
isActive &&
|
||||
_tickerVisible &&
|
||||
!_appPaused &&
|
||||
!hovering &&
|
||||
!_manualPaused &&
|
||||
itemCount > 1;
|
||||
|
||||
void syncAutoPlay() {
|
||||
if (!shouldAutoPlay) {
|
||||
stopAutoPlay();
|
||||
return;
|
||||
}
|
||||
_autoPlayTimer ??= Timer.periodic(
|
||||
autoInterval,
|
||||
(_) => nextPage(manual: false),
|
||||
);
|
||||
}
|
||||
|
||||
void stopAutoPlay() {
|
||||
_autoPlayTimer?.cancel();
|
||||
_autoPlayTimer = null;
|
||||
}
|
||||
|
||||
|
||||
void setHovering(bool value) {
|
||||
hovering = value;
|
||||
value ? stopAutoPlay() : syncAutoPlay();
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
void pauseForManual() {
|
||||
if (!_manualPaused) setState(() => _manualPaused = true);
|
||||
stopAutoPlay();
|
||||
_resumeTimer?.cancel();
|
||||
_resumeTimer = Timer(_manualPauseDuration, () {
|
||||
if (!mounted) return;
|
||||
setState(() => _manualPaused = false);
|
||||
syncAutoPlay();
|
||||
});
|
||||
}
|
||||
|
||||
void onPageChanged(int index) {
|
||||
setState(() => currentIndex = index);
|
||||
emitActiveItem();
|
||||
precacheAdjacentSlides();
|
||||
if (_autoPageChangeInProgress) {
|
||||
_autoPageChangeInProgress = false;
|
||||
return;
|
||||
}
|
||||
pauseForManual();
|
||||
}
|
||||
|
||||
void goToPage(int index) {
|
||||
if (index < 0 || index >= itemCount) return;
|
||||
if (!pageController.hasClients) return;
|
||||
pageController.animateToPage(
|
||||
index,
|
||||
duration: slideDuration,
|
||||
curve: slideCurve,
|
||||
);
|
||||
pauseForManual();
|
||||
}
|
||||
|
||||
void nextPage({bool manual = true}) {
|
||||
if (itemCount <= 1) return;
|
||||
if (!pageController.hasClients) return;
|
||||
final nextIndex = (currentIndex + 1) % itemCount;
|
||||
if (!manual) _autoPageChangeInProgress = true;
|
||||
pageController.animateToPage(
|
||||
nextIndex,
|
||||
duration: slideDuration,
|
||||
curve: slideCurve,
|
||||
);
|
||||
if (manual) pauseForManual();
|
||||
}
|
||||
|
||||
void prevPage() {
|
||||
if (itemCount <= 1) return;
|
||||
if (!pageController.hasClients) return;
|
||||
final previousIndex = (currentIndex - 1 + itemCount) % itemCount;
|
||||
pageController.animateToPage(
|
||||
previousIndex,
|
||||
duration: slideDuration,
|
||||
curve: slideCurve,
|
||||
);
|
||||
pauseForManual();
|
||||
}
|
||||
|
||||
int? bannerCacheWidth() {
|
||||
final width = lastBannerWidth;
|
||||
if (width == null || !width.isFinite || width <= 0) return null;
|
||||
return (width * MediaQuery.devicePixelRatioOf(context)).ceil();
|
||||
}
|
||||
|
||||
|
||||
void resetCarouselItems() {
|
||||
stopAutoPlay();
|
||||
_resumeTimer?.cancel();
|
||||
_manualPaused = false;
|
||||
if (itemCount > 0 && pageController.hasClients) {
|
||||
pageController.jumpToPage(0);
|
||||
}
|
||||
currentIndex = 0;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
emitActiveItem();
|
||||
precacheAdjacentSlides();
|
||||
syncAutoPlay();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void activateCarousel() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
emitActiveItem();
|
||||
precacheAdjacentSlides();
|
||||
syncAutoPlay();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void deactivateCarousel() {
|
||||
stopAutoPlay();
|
||||
_resumeTimer?.cancel();
|
||||
_manualPaused = false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user