Flutter:实现多图上传选择的UI

效果图如下,只实现ui效果。

ui展示组件

dart 复制代码
import 'package:ayidaojia/common/index.dart';
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

/// 图片选择器组件(纯UI组件)
/// 支持多选、删除、自动换行
/// 所有业务逻辑由外部控制
class ImagePickerWidget extends StatelessWidget {
  /// 已选择的图片列表
  final List<String> images;
  
  /// 最小张数
  final int minCount;
  
  /// 最大张数
  final int maxCount;
  
  /// 图片尺寸
  final double imageSize;
  
  /// 图片圆角
  final double borderRadius;
  
  /// 每行显示的图片数量
  final int crossAxisCount;
  
  /// 图片间距
  final double spacing;
  
  /// 点击添加按钮回调(由外部处理选择和上传逻辑)
  final VoidCallback onAddTap;
  
  /// 删除图片回调
  final Function(int index) onDelete;
  
  /// 点击图片预览回调
  final Function(int index)? onPreview;
  
  /// 是否显示删除确认弹窗
  final bool showDeleteConfirm;

  const ImagePickerWidget({
    super.key,
    required this.images,
    required this.onAddTap,
    required this.onDelete,
    this.minCount = 0,
    this.maxCount = 9,
    this.imageSize = 200.0,
    this.borderRadius = 20.0,
    this.crossAxisCount = 4,
    this.spacing = 20.0,
    this.onPreview,
    this.showDeleteConfirm = true,
  });

  // 删除图片
  void _handleDelete(int index) {
    if (showDeleteConfirm) {
      // 显示确认弹窗
      showGeneralDialog(
      context: Get.context!,
      pageBuilder: (BuildContext buildContext, Animation<double> animation, Animation<double> secondaryAnimation) {
        return DialogWidget(
          title: '删除图片',  
          description:'确定删除图片吗?',
          onConfirm: () async {
            onDelete(index);
            Get.back();
          },
        );
      });
    } else {
      onDelete(index);
    }
  }

  // 构建图片项
  Widget _buildImageItem(int index) {
    return Stack(
      children: [
        // 图片
        ImgWidget(
          path: images[index],
          width: imageSize.w,
          height: imageSize.w,
          radius: borderRadius.w,
          fit: BoxFit.cover,
        ).onTap(() {
          if (onPreview != null) {
            onPreview!(index);
          }
        }),
        
        // 删除按钮
        Positioned(
          top: 4.w,
          right: 4.w,
          child: Container(
            width: 40.w,
            height: 40.w,
            decoration: BoxDecoration(
              color: Colors.black.withOpacity(0.6),
              shape: BoxShape.circle,
            ),
            child: Icon(
              Icons.close,
              size: 24.w,
              color: Colors.white,
            ),
          ).onTap(() => _handleDelete(index)),
        ),
      ],
    );
  }

  // 构建添加按钮
  Widget _buildAddButton(BuildContext context) {
    return Container(
      width: imageSize.w,
      height: imageSize.w,
      decoration: BoxDecoration(
        color: AppTheme.blockBgColor,
        borderRadius: BorderRadius.circular(borderRadius.w),
        border: Border.all(
          color: AppTheme.dividerColor,
          width: 2.w,
        ),
      ),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(
            Icons.add_photo_alternate_outlined,
            size: 60.w,
            color: AppTheme.color999,
          ),
          SizedBox(height: 10.w),
          TextWidget.body(
            '上传图片',
            size: 24.sp,
            color: AppTheme.color999,
          ),
          if (maxCount > 0)
            Padding(
              padding: EdgeInsets.only(top: 5.w),
              child: TextWidget.body(
                '${images.length}/$maxCount',
                size: 20.sp,
                color: AppTheme.color666,
              ),
            ),
        ],
      ),
    ).onTap(onAddTap);
  }

  @override
  Widget build(BuildContext context) {
    // 计算需要显示的所有项(图片 + 添加按钮)
    List<Widget> items = [];
    
    // 添加已选择的图片
    for (int i = 0; i < images.length; i++) {
      items.add(_buildImageItem(i));
    }
    
    // 如果未达到最大数量,显示添加按钮
    if (images.length < maxCount) {
      items.add(_buildAddButton(context));
    }

    return Wrap(
      spacing: spacing.w,
      runSpacing: spacing.w,
      children: items,
    );
  }
}

/// 简化版:单图片选择器(纯UI组件)
class SingleImagePickerWidget extends StatelessWidget {
  /// 图片 URL
  final String? imageUrl;
  
  /// 图片尺寸
  final double imageSize;
  
  /// 图片圆角
  final double borderRadius;
  
  /// 点击添加/更换图片回调(由外部处理选择和上传逻辑)
  final VoidCallback onTap;
  
  /// 提示文字
  final String hintText;

  const SingleImagePickerWidget({
    super.key,
    this.imageUrl,
    required this.onTap,
    this.imageSize = 200.0,
    this.borderRadius = 20.0,
    this.hintText = '上传图片',
  });

  @override
  Widget build(BuildContext context) {
    if (imageUrl != null && imageUrl!.isNotEmpty) {
      // 显示已选择的图片
      return Stack(
        children: [
          ImgWidget(
            path: imageUrl!,
            width: imageSize.w,
            height: imageSize.w,
            radius: borderRadius.w,
            fit: BoxFit.cover,
          ),
          // 更换按钮
          Positioned(
            top: -10.w,
            right: -10.w,
            child: Container(
              width: 40.w,
              height: 40.w,
              decoration: BoxDecoration(
                color: Colors.black.withOpacity(0.6),
                shape: BoxShape.circle,
              ),
              child: Icon(
                Icons.edit,
                size: 24.w,
                color: Colors.white,
              ),
            ).onTap(onTap),
          ),
        ],
      );
    } else {
      // 显示添加按钮
      return Container(
        width: imageSize.w,
        height: imageSize.w,
        decoration: BoxDecoration(
          color: AppTheme.blockBgColor,
          borderRadius: BorderRadius.circular(borderRadius.w),
          border: Border.all(
            color: AppTheme.dividerColor,
            width: 2.w,
          ),
        ),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(
              Icons.add_photo_alternate_outlined,
              size: 60.w,
              color: AppTheme.color999,
            ),
            SizedBox(height: 10.w),
            TextWidget.body(
              hintText,
              size: 24.sp,
              color: AppTheme.color999,
            ),
          ],
        ),
      ).onTap(onTap);
    }
  }
}

页面中使用

dart 复制代码
// 上传图片组件
ImagePickerWidget(
  images: controller.images,
  minCount: 0,
  maxCount: controller.maxImages,
  imageSize: 150,
  borderRadius: 16,
  crossAxisCount: 4,
  spacing: 20,
  onAddTap: controller.pickImages, // 点击添加按钮触发选择
  onDelete: controller.deleteImage,
  showDeleteConfirm: true, // 显示删除确认
  onPreview: (index) {
    Get.to(() => ImagePreview(
      imageList: controller.images,
      initialIndex: index,
    ));
  },
),



import 'dart:io';
import 'package:ayidaojia/common/index.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

class EvaluateController extends GetxController {
  EvaluateController();
  
  // 评价图片列表
  List<String> images = [];
  
  // 最大图片数量
  final int maxImages = 5;

  // 删除图片(UI回调)
  void deleteImage(int index) {
    if (index >= 0 && index < images.length) {
      images.removeAt(index);
      update(["evaluate"]);
    }
  }

  // 选择图片(需要自行处理上传逻辑)
  void pickImages() {
    int remainingCount = maxImages - images.length;
    if (remainingCount <= 0) {
      Loading.toast('最多只能上传$maxImages张图片'.tr);
      return;
    }

    WechatImagePicker.showImagePicker(
      context: Get.context!,
      maxAssets: 1, // 单张选择
      onSingleResult: (File? selectedFile) async {
        try {
          if (selectedFile == null) return Loading.toast('图片选择失败'.tr);
          images.add(selectedFile.path);
          update(["evaluate"]);
        } catch (e) {
          Loading.toast('图片处理失败'.tr);
        } finally {
          Loading.dismiss();
        }
      },
    );
  }

}
相关推荐
Flutter OH4 小时前
Flutter OH 外接纹理问题定位指南
flutter
未来猫咪花9 小时前
Everything is ViewModel:让状态管理回到对象世界
android·flutter·ios
xy34539 小时前
Axure9.0 中继器遮罩的核心应用场景(精准适配列表交互)
前端·ui·html·原型·产品设计·axure9.0
Flutter OH10 小时前
Flutter OH 日志抓取与过滤指南
flutter
Flutter OH10 小时前
Flutter OH 内存与 GPU 问题定位指南
flutter
Flutter OH11 小时前
Flutter OH 多设备适配问题定位指南
flutter
律宏阔12 小时前
Flutter Hooks 与 flutter_map_animations 冲突:第二次地图动画报错的解决方案
flutter
skr爱码士15 小时前
15_国际化和本地化:tr()、ts 文件、QM 文件、多语言切换
c++·qt·ui·客户端
Flutter OH15 小时前
Flutter OH 卡死冻屏问题定位指南
flutter·命令模式
2501_9160074716 小时前
Flutter与游戏App加固避坑指南:如何为混合开发与高交互应用选对安全方案?
安全·flutter·游戏·ios·小程序·uni-app·iphone