didvan-app/lib/views/widgets/carousel_3d.dart

215 lines
7.2 KiB
Dart

import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class Carousel3D extends StatefulWidget {
final List<Widget> items;
final double height;
final Duration autoPlayDuration;
final Function(int)? onItemChanged;
final bool showControls;
const Carousel3D({
super.key,
required this.items,
this.height = 220,
this.autoPlayDuration = const Duration(seconds: 4),
this.onItemChanged,
this.showControls = true,
});
@override
State<Carousel3D> createState() => _Carousel3DState();
}
class _Carousel3DState extends State<Carousel3D> with SingleTickerProviderStateMixin {
late final AnimationController _controller;
Timer? _autoPlayTimer;
int _currentIndex = 0;
int _targetIndex = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 150),
);
_startAutoPlay();
}
void _startAutoPlay() {
_autoPlayTimer?.cancel();
_autoPlayTimer = Timer.periodic(widget.autoPlayDuration, (timer) {
if (mounted) {
_goToIndex((_currentIndex + 1) % widget.items.length);
}
});
}
void _goToIndex(int index) {
if (index == _currentIndex) return;
setState(() {
_targetIndex = index;
});
_controller.forward(from: 0).then((_) {
setState(() {
_currentIndex = _targetIndex;
});
widget.onItemChanged?.call(_currentIndex);
_controller.reset();
});
_startAutoPlay();
}
void _nextPage() {
_goToIndex((_currentIndex + 1) % widget.items.length);
}
void _previousPage() {
_goToIndex((_currentIndex - 1 + widget.items.length) % widget.items.length);
}
@override
void dispose() {
_autoPlayTimer?.cancel();
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return Column(
children: [
SizedBox(
height: widget.height,
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Stack(
alignment: Alignment.center,
children: List.generate(widget.items.length, (index) {
final double currentRelativePos = (index - _currentIndex).toDouble();
final double targetRelativePos = (index - _targetIndex).toDouble();
final double relativePos = Tween<double>(
begin: currentRelativePos,
end: targetRelativePos,
).transform(CurvedAnimation(parent: _controller, curve: Curves.easeInOutCubic).value);
final bool isGrayscale = relativePos != 0;
final double absRelativePos = relativePos.abs();
final double scale = (1 - (absRelativePos * 0.15)).clamp(0.0, 1.0);
final double translateX = relativePos * -30.0;
final double translateZ = absRelativePos * -100.0;
final double opacity = (1 - (absRelativePos * 0.3)).clamp(0.0, 1.0);
Matrix4 transform = Matrix4.identity()
..setEntry(3, 2, 0.001)
..translate(translateX, 0.0, translateZ)
..scale(scale);
return Transform(
transform: transform,
alignment: Alignment.center,
child: Opacity(
opacity: opacity,
child: GestureDetector(
onTap: () {
if (relativePos != 0) {
_goToIndex(index);
}
},
child: Container(
width: screenWidth * 0.7,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(isGrayscale ? 0.1 : 0.25),
spreadRadius: 2,
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: ColorFiltered(
colorFilter: isGrayscale
? const ColorFilter.matrix([
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0, 0, 0, 1, 0,
])
: const ColorFilter.mode(
Colors.transparent,
BlendMode.multiply,
),
child: widget.items[index],
),
),
),
),
),
);
}).toList()
..sort((a, b) {
final transformA = a.transform;
final transformB = b.transform;
return transformA.getTranslation().z.compareTo(transformB.getTranslation().z);
}),
);
},
),
),
if (widget.showControls) ...[
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: SvgPicture.asset('lib/assets/icons/Arrow Right.svg'),
iconSize: 40,
onPressed: _previousPage,
),
const SizedBox(width: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(widget.items.length, (index) {
return AnimatedContainer(
duration: const Duration(milliseconds: 150),
margin: const EdgeInsets.symmetric(horizontal: 3),
width: _currentIndex == index ? 9.0 : 7.0,
height: _currentIndex == index ? 9.0 : 7.0,
decoration: BoxDecoration(
color: _currentIndex == index
? const Color.fromARGB(255, 0, 126, 167)
: Colors.grey.withOpacity(0.5),
shape: BoxShape.circle,
),
);
}),
),
const SizedBox(width: 20),
IconButton(
icon: SvgPicture.asset('lib/assets/icons/Click Area.svg'),
iconSize: 40,
onPressed: _nextPage,
),
],
),
],
],
);
}
}