import 'dart:ui'; import 'package:flutter/material.dart'; class GlassDock extends StatefulWidget { const GlassDock({ super.key, required this.selectedIndex, required this.onSelected, }); final int selectedIndex; final ValueChanged onSelected; @override State createState() => _GlassDockState(); } class _GlassDockState extends State { static const _width = 192.0, _travel = 88.0; double? _drag; double get _position => _drag ?? widget.selectedIndex.toDouble(); void _select(int index) { setState(() => _drag = null); widget.onSelected(index); } @override Widget build(BuildContext context) => Container( width: _width, height: 70, decoration: BoxDecoration( borderRadius: BorderRadius.circular(40), boxShadow: [ BoxShadow( color: const Color(0xFF10212E).withValues(alpha: 0.22), blurRadius: 28, offset: const Offset(0, 10), ), BoxShadow( color: Colors.white.withValues(alpha: 0.35), blurRadius: 1, spreadRadius: 1, ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(40), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 24, sigmaY: 24), child: DecoratedBox( decoration: BoxDecoration( borderRadius: BorderRadius.circular(40), gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ const Color(0xFFB9C0CA).withValues(alpha: 0.58), const Color(0xFF56616D).withValues(alpha: 0.34), const Color(0xFFA1ADB8).withValues(alpha: 0.42), ], ), border: Border.all( color: Colors.white.withValues(alpha: 0.55), width: 1.2, ), ), child: Stack( children: [ Positioned( left: 18, right: 18, top: 1, child: Container( height: 1.5, decoration: BoxDecoration( borderRadius: BorderRadius.circular(30), gradient: LinearGradient( colors: [ Colors.white.withValues(alpha: 0), Colors.white.withValues(alpha: 0.8), Colors.white.withValues(alpha: 0.08), ], ), ), ), ), AnimatedPositioned( duration: Duration(milliseconds: _drag == null ? 260 : 0), curve: Curves.easeOutCubic, left: 7 + _position * _travel, top: _drag == null ? 7 : 5, width: _drag == null ? 90 : 94, height: _drag == null ? 56 : 60, child: IgnorePointer( child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(36), gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ const Color(0xFF24323F).withValues(alpha: 0.64), const Color(0xFF101E2B).withValues(alpha: 0.8), const Color(0xFF475866).withValues(alpha: 0.5), ], ), border: Border.all( color: Colors.white.withValues(alpha: 0.3), width: 0.9, ), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.18), blurRadius: 7, offset: const Offset(0, 3), ), BoxShadow( color: Colors.white.withValues(alpha: 0.12), blurRadius: 3, spreadRadius: 0.5, ), ], ), ), ), ), Positioned( left: 8, right: 8, top: 7, bottom: 7, child: Row( children: [ _item(0, Icons.calendar_month_rounded, '课表'), _item(1, Icons.person_rounded, '我的'), ], ), ), Positioned( left: 16, right: 16, bottom: 1, child: Container( height: 1.3, decoration: BoxDecoration( gradient: LinearGradient( colors: [ Colors.white.withValues(alpha: 0.05), Colors.white.withValues(alpha: 0.5), Colors.white.withValues(alpha: 0.05), ], ), ), ), ), ], ), ), ), ), ); Widget _item(int index, IconData icon, String label) => Expanded( child: Semantics( label: label, button: true, selected: index == widget.selectedIndex, excludeSemantics: true, onTap: () => _select(index), child: GestureDetector( key: index == widget.selectedIndex ? const Key('dock-cursor') : null, behavior: HitTestBehavior.opaque, onTap: () => _select(index), // Only the selected lens can start a drag. The pages have no swipe handlers. onHorizontalDragStart: index == widget.selectedIndex ? (_) => setState(() => _drag = widget.selectedIndex.toDouble()) : null, onHorizontalDragUpdate: index == widget.selectedIndex ? (details) => setState( () => _drag = (_position + details.delta.dx / _travel).clamp( 0, 1, ), ) : null, onHorizontalDragEnd: index == widget.selectedIndex ? (_) => _select(_position >= 0.5 ? 1 : 0) : null, onHorizontalDragCancel: index == widget.selectedIndex ? () => setState(() => _drag = null) : null, child: Center( child: Icon( icon, size: 27, color: index == (_position >= 0.5 ? 1 : 0) ? const Color(0xFF458BFF) : Colors.white.withValues(alpha: 0.85), ), ), ), ), ); }