100 lines
3.1 KiB
Dart
100 lines
3.1 KiB
Dart
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<AvatarPicker> createState() => _AvatarPickerState();
|
|
}
|
|
|
|
class _AvatarPickerState extends State<AvatarPicker> {
|
|
File? _avatar;
|
|
|
|
Future<void> _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,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|