Flutter图片圆角制作器应用开发教程

项目概述

本教程将带你开发一个功能完整的Flutter图片圆角制作器应用。这款应用专为图片处理设计,提供多种圆角样式、实时预览、自定义设置等功能,让用户轻松制作出精美的圆角图片效果。
运行效果图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

应用特色

  • 多种圆角样式:支持四角圆角、上方圆角、下方圆角、左右圆角、对角圆角等6种样式
  • 实时预览功能:调节参数时实时显示效果,所见即所得
  • 灵活的圆角调节:支持0-100px的圆角大小调节,提供预设快捷值
  • 背景色设置:可为透明区域添加背景色,更好地预览效果
  • 作品管理系统:保存处理后的图片,支持重新编辑和管理
  • 个性化设置:主题颜色、网格布局、图片质量等可自定义
  • 统计分析功能:显示图片数量、平均圆角、使用天数等统计信息

技术栈

  • 框架:Flutter 3.x
  • 语言:Dart
  • UI组件:Material Design 3
  • 状态管理:StatefulWidget
  • 动画:AnimationController + Tween
  • 图片处理:CustomPainter + Canvas绘制
  • 数据存储:内存存储(可扩展为本地数据库)

项目结构设计

核心数据模型

1. 图片信息模型(ImageInfo)
class ImageInfo {
  final String id;              // 唯一标识
  final String name;            // 图片名称
  final Uint8List imageData;    // 图片数据
  final double width;           // 图片宽度
  final double height;          // 图片高度
  final DateTime createdAt;     // 创建时间
  double cornerRadius;          // 圆角大小
  bool isProcessed;            // 是否已处理

  ImageInfo({
    required this.id,
    required this.name,
    required this.imageData,
    required this.width,
    required this.height,
    required this.createdAt,
    this.cornerRadius = 0.0,
    this.isProcessed = false,
  });
}
2. 圆角样式枚举
enum CornerStyle {
  all,        // 四角圆角
  topOnly,    // 仅上方圆角
  bottomOnly, // 仅下方圆角
  leftOnly,   // 仅左侧圆角
  rightOnly,  // 仅右侧圆角
  diagonal,   // 对角圆角
}

页面架构

应用采用底部导航栏设计,包含四个主要页面:

  1. 图片页面:选择和管理原始图片
  2. 编辑页面:调节圆角参数和实时预览
  3. 作品页面:管理处理后的图片作品
  4. 设置页面:个性化设置和应用信息

详细实现步骤

第一步:项目初始化

创建新的Flutter项目:

flutter create image_rounder_app
cd image_rounder_app

第二步:主应用结构

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '图片圆角制作器',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const ImageRounderHomePage(),
    );
  }
}

第三步:数据初始化

创建示例图片数据:

void _initializeSampleImages() {
  _images = [
    _createSampleImage('风景照片', Colors.blue, Colors.green),
    _createSampleImage('人物照片', Colors.pink, Colors.purple),
    _createSampleImage('建筑照片', Colors.orange, Colors.red),
    _createSampleImage('动物照片', Colors.teal, Colors.cyan),
    _createSampleImage('花朵照片', Colors.yellow, Colors.orange),
    _createSampleImage('食物照片', Colors.brown, Colors.amber),
  ];
}

ImageInfo _createSampleImage(String name, Color color1, Color color2) {
  final width = 300.0;
  final height = 200.0;
  
  // 创建渐变图片数据(模拟)
  final imageData = _generateGradientImageData(
    width.toInt(), 
    height.toInt(), 
    color1, 
    color2
  );

  return ImageInfo(
    id: DateTime.now().millisecondsSinceEpoch.toString() + 
         _random.nextInt(1000).toString(),
    name: name,
    imageData: imageData,
    width: width,
    height: height,
    createdAt: DateTime.now().subtract(Duration(days: _random.nextInt(30))),
  );
}

第四步:图片选择页面

图片网格显示
Widget _buildImagesPage() {
  return Padding(
    padding: const EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Text(
              '选择图片',
              style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                    fontWeight: FontWeight.bold,
                  ),
            ),
            const Spacer(),
            ElevatedButton.icon(
              onPressed: _pickImage,
              icon: const Icon(Icons.add_photo_alternate),
              label: const Text('添加图片'),
            ),
          ],
        ),
        const SizedBox(height: 16),
        
        Expanded(
          child: GridView.builder(
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2,
              childAspectRatio: 1.2,
              crossAxisSpacing: 16,
              mainAxisSpacing: 16,
            ),
            itemCount: _images.length,
            itemBuilder: (context, index) {
              final image = _images[index];
              return _buildImageCard(image);
            },
          ),
        ),
      ],
    ),
  );
}
图片卡片组件
Widget _buildImageCard(ImageInfo image) {
  return Card(
    elevation: 4,
    child: InkWell(
      onTap: () => _selectImage(image),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 图片预览
          Expanded(
            child: Container(
              width: double.infinity,
              decoration: BoxDecoration(
                borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
                gradient: LinearGradient(
                  colors: [
                    _getImageColors(image)[0],
                    _getImageColors(image)[1],
                  ],
                ),
              ),
              child: const Center(
                child: Icon(Icons.image, size: 40, color: Colors.white),
              ),
            ),
          ),

          // 图片信息
          Padding(
            padding: const EdgeInsets.all(12),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  image.name,
                  style: const TextStyle(
                    fontSize: 14,
                    fontWeight: FontWeight.bold,
                  ),
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                ),
                const SizedBox(height: 4),
                Text(
                  '${image.width.toInt()}×${image.height.toInt()}',
                  style: TextStyle(
                    fontSize: 12,
                    color: Colors.grey.shade600,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    ),
  );
}

第五步:编辑页面实现

实时预览组件
Widget _buildEditorPage() {
  if (_currentImage == null) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.edit_outlined, size: 80, color: Colors.grey.shade400),
          const SizedBox(height: 16),
          Text('请先选择一张图片', 
               style: TextStyle(fontSize: 18, color: Colors.grey.shade600)),
        ],
      ),
    );
  }

  return SingleChildScrollView(
    padding: const EdgeInsets.all(16),
    child: Column(
      children: [
        // 图片预览区域
        Card(
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              children: [
                Text('预览效果', 
                     style: Theme.of(context).textTheme.titleLarge?.copyWith(
                           fontWeight: FontWeight.bold)),
                const SizedBox(height: 16),
                Center(
                  child: AnimatedBuilder(
                    animation: _previewAnimation,
                    builder: (context, child) {
                      return Transform.scale(
                        scale: 0.8 + (_previewAnimation.value * 0.2),
                        child: Container(
                          width: 250,
                          height: 200,
                          decoration: BoxDecoration(
                            borderRadius: _getBorderRadius(),
                            gradient: LinearGradient(
                              colors: _getImageColors(_currentImage!),
                            ),
                            boxShadow: [
                              BoxShadow(
                                color: Colors.black.withValues(alpha: 0.2),
                                blurRadius: 10,
                                spreadRadius: 2,
                              ),
                            ],
                          ),
                          child: const Center(
                            child: Icon(Icons.image, size: 50, color: Colors.white),
                          ),
                        ),
                      );
                    },
                  ),
                ),
              ],
            ),
          ),
        ),
        
        // 圆角设置控件...
      ],
    ),
  );
}
圆角样式控制
BorderRadius _getBorderRadius() {
  switch (_cornerStyle) {
    case CornerStyle.all:
      return BorderRadius.circular(_cornerRadius);
    case CornerStyle.topOnly:
      return BorderRadius.vertical(top: Radius.circular(_cornerRadius));
    case CornerStyle.bottomOnly:
      return BorderRadius.vertical(bottom: Radius.circular(_cornerRadius));
    case CornerStyle.leftOnly:
      return BorderRadius.horizontal(left: Radius.circular(_cornerRadius));
    case CornerStyle.rightOnly:
      return BorderRadius.horizontal(right: Radius.circular(_cornerRadius));
    case CornerStyle.diagonal:
      return BorderRadius.only(
        topLeft: Radius.circular(_cornerRadius),
        bottomRight: Radius.circular(_cornerRadius),
      );
  }
}
圆角参数调节
// 圆角大小滑块
Row(
  children: [
    const Text('圆角大小:'),
    Expanded(
      child: Slider(
        value: _cornerRadius,
        min: 0,
        max: 100,
        divisions: 100,
        label: '${_cornerRadius.toInt()}px',
        onChanged: (value) {
          setState(() {
            _cornerRadius = value;
          });
          _previewAnimationController.forward(from: 0);
        },
      ),
    ),
    Text('${_cornerRadius.toInt()}px'),
  ],
),

// 预设圆角值
Wrap(
  spacing: 8,
  children: _presetRadius.map((radius) {
    return FilterChip(
      label: Text('${radius.toInt()}px'),
      selected: _cornerRadius == radius,
      onSelected: (selected) {
        if (selected) {
          setState(() {
            _cornerRadius = radius;
          });
          _previewAnimationController.forward(from: 0);
        }
      },
    );
  }).toList(),
),

// 圆角样式选择
Wrap(
  spacing: 8,
  runSpacing: 8,
  children: CornerStyle.values.map((style) {
    return FilterChip(
      label: Text(_getCornerStyleName(style)),
      selected: _cornerStyle == style,
      onSelected: (selected) {
        if (selected) {
          setState(() {
            _cornerStyle = style;
          });
          _previewAnimationController.forward(from: 0);
        }
      },
    );
  }).toList(),
),

第六步:背景色设置

// 背景设置
SwitchListTile(
  title: const Text('显示背景色'),
  subtitle: const Text('为透明区域添加背景色'),
  value: _showBackground,
  onChanged: (value) {
    setState(() {
      _showBackground = value;
    });
  },
),

if (_showBackground) ...[
  const Text('背景颜色:'),
  const SizedBox(height: 8),
  Wrap(
    spacing: 8,
    children: [
      Colors.transparent,
      Colors.white,
      Colors.black,
      Colors.red,
      Colors.green,
      Colors.blue,
      Colors.yellow,
      Colors.purple,
    ].map((color) {
      return GestureDetector(
        onTap: () {
          setState(() {
            _backgroundColor = color;
          });
        },
        child: Container(
          width: 40,
          height: 40,
          decoration: BoxDecoration(
            color: color == Colors.transparent 
                ? Colors.grey.shade200 
                : color,
            shape: BoxShape.circle,
            border: Border.all(
              color: _backgroundColor == color
                  ? Colors.blue
                  : Colors.grey.shade300,
              width: _backgroundColor == color ? 3 : 1,
            ),
          ),
          child: color == Colors.transparent
              ? const Icon(Icons.clear, color: Colors.grey)
              : null,
        ),
      );
    }).toList(),
  ),
],

第七步:作品管理功能

保存处理后的图片
void _saveProcessedImage() {
  if (_currentImage == null) return;

  // 创建处理后的图片信息
  final processedImage = ImageInfo(
    id: DateTime.now().millisecondsSinceEpoch.toString(),
    name: '${_currentImage!.name}_圆角',
    imageData: _currentImage!.imageData,
    width: _currentImage!.width,
    height: _currentImage!.height,
    createdAt: DateTime.now(),
    cornerRadius: _cornerRadius,
    isProcessed: true,
  );

  setState(() {
    _processedImages.add(processedImage);
  });

  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: const Text('图片已保存到作品集'),
      action: SnackBarAction(
        label: '查看',
        onPressed: () {
          setState(() {
            _selectedIndex = 2; // 切换到作品页面
          });
        },
      ),
    ),
  );
}
作品展示页面
Widget _buildGalleryPage() {
  return Padding(
    padding: const EdgeInsets.all(16),
    child: Column(
      children: [
        Row(
          children: [
            Text('我的作品', 
                 style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                       fontWeight: FontWeight.bold)),
            const Spacer(),
            if (_processedImages.isNotEmpty)
              TextButton.icon(
                onPressed: _clearAllProcessedImages,
                icon: const Icon(Icons.clear_all),
                label: const Text('清空'),
              ),
          ],
        ),
        const SizedBox(height: 16),

        Expanded(
          child: GridView.builder(
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2,
              childAspectRatio: 0.8,
              crossAxisSpacing: 16,
              mainAxisSpacing: 16,
            ),
            itemCount: _processedImages.length,
            itemBuilder: (context, index) {
              final image = _processedImages[index];
              return _buildProcessedImageCard(image);
            },
          ),
        ),
      ],
    ),
  );
}

第八步:动画效果实现

预览动画设置
void _setupAnimations() {
  _previewAnimationController = AnimationController(
    duration: const Duration(milliseconds: 500),
    vsync: this,
  );

  _previewAnimation = Tween<double>(
    begin: 0.0,
    end: 1.0,
  ).animate(CurvedAnimation(
    parent: _previewAnimationController,
    curve: Curves.easeInOut,
  ));
}
动画触发
// 在参数改变时触发动画
onChanged: (value) {
  setState(() {
    _cornerRadius = value;
  });
  _previewAnimationController.forward(from: 0);
},

第九步:设置页面

Widget _buildSettingsPage() {
  return Padding(
    padding: const EdgeInsets.all(16),
    child: Column(
      children: [
        // 图片设置
        Card(
          child: Column(
            children: [
              ListTile(
                leading: const Icon(Icons.image_aspect_ratio, color: Colors.blue),
                title: const Text('默认图片尺寸'),
                subtitle: const Text('设置新图片的默认尺寸'),
                trailing: const Icon(Icons.chevron_right),
                onTap: _showImageSizeSettings,
              ),
              ListTile(
                leading: const Icon(Icons.rounded_corner, color: Colors.green),
                title: const Text('默认圆角大小'),
                subtitle: const Text('设置新图片的默认圆角大小'),
                onTap: _showDefaultRadiusSettings,
              ),
            ],
          ),
        ),

        // 界面设置
        Card(
          child: Column(
            children: [
              ListTile(
                leading: const Icon(Icons.palette, color: Colors.purple),
                title: const Text('主题颜色'),
                subtitle: const Text('选择应用主题颜色'),
                onTap: _showThemeSettings,
              ),
              ListTile(
                leading: const Icon(Icons.animation, color: Colors.teal),
                title: const Text('动画效果'),
                subtitle: const Text('开启或关闭界面动画'),
                trailing: Switch(
                  value: true,
                  onChanged: (value) {
                    // 实现动画开关
                  },
                ),
              ),
            ],
          ),
        ),
      ],
    ),
  );
}

第十步:图片详情页面

class ImageDetailPage extends StatelessWidget {
  final ImageInfo image;
  final VoidCallback onDelete;
  final VoidCallback onEdit;

  const ImageDetailPage({
    super.key,
    required this.image,
    required this.onDelete,
    required this.onEdit,
  });

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(image.name),
        actions: [
          IconButton(
            icon: const Icon(Icons.edit),
            onPressed: onEdit,
          ),
          IconButton(
            icon: const Icon(Icons.share),
            onPressed: () => _shareImage(context),
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            // 图片预览
            Center(
              child: Container(
                width: 300,
                height: 200,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(image.cornerRadius),
                  gradient: LinearGradient(
                    colors: _getImageColors(image),
                  ),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withValues(alpha: 0.2),
                      blurRadius: 10,
                      spreadRadius: 2,
                    ),
                  ],
                ),
                child: const Center(
                  child: Icon(Icons.image, size: 60, color: Colors.white),
                ),
              ),
            ),

            // 图片信息
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text('图片信息', 
                         style: Theme.of(context).textTheme.titleLarge?.copyWith(
                               fontWeight: FontWeight.bold)),
                    const SizedBox(height: 16),
                    _buildInfoRow('名称', image.name),
                    _buildInfoRow('尺寸', '${image.width.toInt()}×${image.height.toInt()}'),
                    _buildInfoRow('圆角大小', '${image.cornerRadius.toInt()}px'),
                    _buildInfoRow('创建时间', _formatDateTime(image.createdAt)),
                    _buildInfoRow('状态', image.isProcessed ? '已处理' : '原始图片'),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

核心功能详解

1. 图片数据生成

由于这是一个演示应用,我们使用渐变色来模拟图片数据:

Uint8List _generateGradientImageData(int width, int height, Color color1, Color color2) {
  // 简单的渐变数据生成(RGBA格式)
  final data = Uint8List(width * height * 4);
  
  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      final index = (y * width + x) * 4;
      final ratio = x / width;
      
      final r = (color1.red * (1 - ratio) + color2.red * ratio).round();
      final g = (color1.green * (1 - ratio) + color2.green * ratio).round();
      final b = (color1.blue * (1 - ratio) + color2.blue * ratio).round();
      
      data[index] = r;     // R
      data[index + 1] = g; // G
      data[index + 2] = b; // B
      data[index + 3] = 255; // A
    }
  }
  
  return data;
}

2. 圆角样式系统

通过枚举和BorderRadius实现多种圆角样式:

String _getCornerStyleName(CornerStyle style) {
  switch (style) {
    case CornerStyle.all:
      return '四角圆角';
    case CornerStyle.topOnly:
      return '上方圆角';
    case CornerStyle.bottomOnly:
      return '下方圆角';
    case CornerStyle.leftOnly:
      return '左侧圆角';
    case CornerStyle.rightOnly:
      return '右侧圆角';
    case CornerStyle.diagonal:
      return '对角圆角';
  }
}

3. 实时预览机制

通过setState和动画控制器实现实时预览:

onChanged: (value) {
  setState(() {
    _cornerRadius = value;
  });
  _previewAnimationController.forward(from: 0);
},

4. 数据管理

使用List存储图片数据,支持增删改查操作:

// 添加图片
void _pickImage() {
  final newImage = _createSampleImage(
    '新图片${_images.length + 1}',
    Colors.primaries[_random.nextInt(Colors.primaries.length)],
    Colors.primaries[_random.nextInt(Colors.primaries.length)],
  );
  
  setState(() {
    _images.add(newImage);
  });
}

// 删除图片
void _deleteProcessedImage(ImageInfo image) {
  setState(() {
    _processedImages.remove(image);
  });
}

性能优化

1. 列表优化

使用GridView.builder实现虚拟滚动:

GridView.builder(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    childAspectRatio: 1.2,
    crossAxisSpacing: 16,
    mainAxisSpacing: 16,
  ),
  itemCount: _images.length,
  itemBuilder: (context, index) {
    final image = _images[index];
    return _buildImageCard(image);
  },
)

2. 动画优化

合理使用动画控制器,避免过度动画:


void dispose() {
  _previewAnimationController.dispose();
  super.dispose();
}

3. 内存管理

及时释放不需要的资源:

// 清空作品时释放内存
void _clearAllProcessedImages() {
  setState(() {
    _processedImages.clear();
  });
}

扩展功能

1. 真实图片处理

可以集成image插件实现真实图片处理:

dependencies:
  flutter:
    sdk: flutter
  image: ^4.0.17
  image_picker: ^1.0.4

2. 文件保存

使用path_provider保存处理后的图片:

dependencies:
  path_provider: ^2.1.1

3. 分享功能

集成share_plus插件实现图片分享:

dependencies:
  share_plus: ^7.2.1

测试策略

1. 单元测试

测试核心业务逻辑:

test('should create image with correct properties', () {
  final image = ImageInfo(
    id: '1',
    name: 'Test Image',
    imageData: Uint8List(0),
    width: 300,
    height: 200,
    createdAt: DateTime.now(),
  );
  
  expect(image.cornerRadius, 0.0);
  expect(image.isProcessed, false);
});

2. Widget测试

测试UI组件:

testWidgets('should display image card', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());
  expect(find.text('风景照片'), findsOneWidget);
});

3. 集成测试

测试完整用户流程:

testWidgets('should edit image corner radius', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());
  
  // 选择图片
  await tester.tap(find.text('风景照片'));
  await tester.pumpAndSettle();
  
  // 调节圆角
  await tester.drag(find.byType(Slider), const Offset(100, 0));
  await tester.pumpAndSettle();
  
  // 验证预览更新
  expect(find.byType(Container), findsWidgets);
});

部署发布

1. Android打包

flutter build apk --release

2. iOS打包

flutter build ios --release

3. 应用商店发布

准备应用图标、截图和描述,提交到各大应用商店。

总结

本教程详细介绍了Flutter图片圆角制作器应用的完整开发过程,涵盖了:

  • 数据模型设计:合理的图片信息和圆角样式设计
  • UI界面开发:Material Design 3风格的现代化界面
  • 功能实现:多种圆角样式、实时预览、作品管理
  • 动画效果:提升用户体验的预览动画
  • 性能优化:列表优化、动画优化、内存管理
  • 扩展功能:真实图片处理、文件保存、分享功能
  • 测试策略:单元测试、Widget测试、集成测试

这款应用不仅功能完整,而且代码结构清晰,易于维护和扩展。通过本教程的学习,你可以掌握Flutter图片处理应用开发的核心技能,为后续开发更复杂的图像处理应用打下坚实基础。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐