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();
        }
      },
    );
  }

}
相关推荐
倔强_build1 小时前
flutter app 状态栏
flutter
Howie Zphile1 小时前
NEXTJS/REACT有哪些主流的UI可选
前端·react.js·ui
晚霞的不甘2 小时前
深度解析:Flutter 与 OpenHarmony 融合架构下的跨平台渲染机制与系统级集成
flutter·架构
kirk_wang2 小时前
Flutter图片库CachedNetworkImage鸿蒙适配:从原理到实践
flutter·移动开发·跨平台·arkts·鸿蒙
松☆2 小时前
Flutter 与 OpenHarmony 数据持久化协同方案:从 Shared Preferences 到分布式数据管理
分布式·flutter
松☆2 小时前
OpenHarmony + Flutter 离线能力构建指南:打造无网可用的高可靠政务/工业应用
flutter·政务
松☆2 小时前
OpenHarmony + Flutter 多语言与国际化(i18n)深度适配指南:一套代码支持中英俄等 10+ 语种
android·javascript·flutter
晚霞的不甘2 小时前
Flutter 与开源鸿蒙(OpenHarmony)性能调优与生产部署实战:从启动加速到线上监控的全链路优化
flutter·开源·harmonyos
AskHarries3 小时前
Flutter + Supabase 接入 Google 登录
flutter