import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:image_editor_plus/image_editor_plus.dart'; import 'package:image_editor_plus/options.dart' as o; import 'RouteAnimation.dart'; class AvatarPicker extends StatefulWidget { final void Function(File file)? onImageSelected; const AvatarPicker({super.key, this.onImageSelected}); @override State createState() => _AvatarPickerState(); } class _AvatarPickerState extends State { File? _avatar; Future _pickAndCropImage() async { final picker = ImagePicker(); final picked = await picker.pickImage(source: ImageSource.gallery); if (picked == null) return; final bytes = await picked.readAsBytes(); final edited = await Navigator.push( context, RouteAnimation( ImageCropper( image: bytes, availableRatios: [o.AspectRatio(title: '1:1', ratio: 1)], ), Offset(0, -1), ), ); if (edited == null) return; final tempPath = '${Directory.systemTemp.path}/${DateTime.now().millisecondsSinceEpoch}.jpg'; final file = await File(tempPath).writeAsBytes(edited as Uint8List); setState(() => _avatar = file); widget.onImageSelected?.call(file); } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { // ✅ 取最小边作为正方形边长,防止拉伸成椭圆 final side = constraints.maxWidth < constraints.maxHeight ? constraints.maxWidth : constraints.maxHeight; return Center( child: GestureDetector( onTap: _pickAndCropImage, child: Stack( children: [ ClipOval( child: Container( width: side, height: side, color: Colors.grey.shade200, // 可选:加载前背景 child: _avatar != null ? FittedBox( fit: BoxFit.cover, clipBehavior: Clip.hardEdge, child: SizedBox( width: side, height: side, child: Image.file(_avatar!, fit: BoxFit.cover), ), ) : FittedBox( fit: BoxFit.cover, clipBehavior: Clip.hardEdge, child: SizedBox( width: side, height: side, child: Image.asset( "assets/icon/default_avatar.png", fit: BoxFit.cover, ), ), ), ), ) ], ), ), ); }, ); } }