在这里插入图片描述

写CSS样式的时候,经常需要调整各种属性来达到想要的效果。今天我们来实现一个CSS生成器,让你可以通过可视化的方式生成CSS代码。

功能定位

CSS生成器的核心价值是降低CSS编写的门槛。不是每个人都能记住所有CSS属性,通过可视化界面调整参数,实时看到代码和效果,这种体验要好得多。

我们重点实现几个常用的CSS功能:边框、阴影、渐变、圆角。这些是最常用也最容易出错的属性。

完整代码实现

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';

class CssGeneratorPage extends StatefulWidget {
  const CssGeneratorPage({Key? key}) : super(key: key);

  
  State<CssGeneratorPage> createState() => _CssGeneratorPageState();
}

class _CssGeneratorPageState extends State<CssGeneratorPage> {
  // 边框属性
  double _borderWidth = 2.0;
  Color _borderColor = Colors.blue;
  double _borderRadius = 8.0;
  
  // 阴影属性
  double _shadowX = 0.0;
  double _shadowY = 4.0;
  double _shadowBlur = 8.0;
  Color _shadowColor = Colors.black26;
  
  // 背景属性
  Color _backgroundColor = Colors.white;
  
  // 内边距
  double _padding = 16.0;
  
  int _selectedTab = 0;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('CSS生成器'),
        actions: [
          IconButton(
            icon: const Icon(Icons.copy),
            onPressed: _copyCssCode,
            tooltip: '复制CSS代码',
          ),
        ],
      ),
      body: Column(
        children: [
          // 标签切换
          Container(
            color: Colors.grey[100],
            child: Row(
              children: [
                _buildTab('边框', 0),
                _buildTab('阴影', 1),
                _buildTab('背景', 2),
                _buildTab('间距', 3),
              ],
            ),
          ),
          
          // 控制面板
          Expanded(
            child: Row(
              children: [
                // 左侧参数调整区
                Expanded(
                  flex: 2,
                  child: Container(
                    color: Colors.grey[50],
                    child: SingleChildScrollView(
                      padding: EdgeInsets.all(16.w),
                      child: _buildControlPanel(),
                    ),
                  ),
                ),
                
                // 右侧预览和代码区
                Expanded(
                  flex: 3,
                  child: Container(
                    color: Colors.white,
                    child: Column(
                      children: [
                        // 预览区
                        Expanded(
                          flex: 2,
                          child: Container(
                            padding: EdgeInsets.all(24.w),
                            child: Center(
                              child: _buildPreview(),
                            ),
                          ),
                        ),
                        
                        Divider(height: 1.h),
                        
                        // 代码区
                        Expanded(
                          flex: 1,
                          child: Container(
                            color: Colors.grey[900],
                            padding: EdgeInsets.all(16.w),
                            child: SingleChildScrollView(
                              child: SelectableText(
                                _generateCssCode(),
                                style: TextStyle(
                                  fontFamily: 'monospace',
                                  fontSize: 13.sp,
                                  color: Colors.greenAccent,
                                ),
                              ),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildTab(String title, int index) {
    final isSelected = _selectedTab == index;
    return Expanded(
      child: GestureDetector(
        onTap: () => setState(() => _selectedTab = index),
        child: Container(
          padding: EdgeInsets.symmetric(vertical: 12.h),
          decoration: BoxDecoration(
            color: isSelected ? Colors.white : Colors.transparent,
            border: Border(
              bottom: BorderSide(
                color: isSelected ? Colors.blue : Colors.transparent,
                width: 2.w,
              ),
            ),
          ),
          child: Text(
            title,
            textAlign: TextAlign.center,
            style: TextStyle(
              fontSize: 14.sp,
              fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
              color: isSelected ? Colors.blue : Colors.grey[600],
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildControlPanel() {
    switch (_selectedTab) {
      case 0:
        return _buildBorderControls();
      case 1:
        return _buildShadowControls();
      case 2:
        return _buildBackgroundControls();
      case 3:
        return _buildPaddingControls();
      default:
        return Container();
    }
  }

  Widget _buildBorderControls() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('边框宽度', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold)),
        Slider(
          value: _borderWidth,
          min: 0,
          max: 10,
          divisions: 20,
          label: '${_borderWidth.toStringAsFixed(1)}px',
          onChanged: (value) => setState(() => _borderWidth = value),
        ),
        SizedBox(height: 16.h),
        
        Text('边框颜色', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold)),
        SizedBox(height: 8.h),
        Wrap(
          spacing: 8.w,
          children: [
            Colors.blue,
            Colors.red,
            Colors.green,
            Colors.orange,
            Colors.purple,
            Colors.black,
          ].map((color) => _buildColorButton(color, _borderColor, (c) {
            setState(() => _borderColor = c);
          })).toList(),
        ),
        SizedBox(height: 16.h),
        
        Text('圆角半径', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold)),
        Slider(
          value: _borderRadius,
          min: 0,
          max: 50,
          divisions: 50,
          label: '${_borderRadius.toStringAsFixed(0)}px',
          onChanged: (value) => setState(() => _borderRadius = value),
        ),
      ],
    );
  }

  Widget _buildShadowControls() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('水平偏移', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold)),
        Slider(
          value: _shadowX,
          min: -20,
          max: 20,
          divisions: 40,
          label: '${_shadowX.toStringAsFixed(0)}px',
          onChanged: (value) => setState(() => _shadowX = value),
        ),
        SizedBox(height: 16.h),
        
        Text('垂直偏移', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold)),
        Slider(
          value: _shadowY,
          min: -20,
          max: 20,
          divisions: 40,
          label: '${_shadowY.toStringAsFixed(0)}px',
          onChanged: (value) => setState(() => _shadowY = value),
        ),
        SizedBox(height: 16.h),
        
        Text('模糊半径', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold)),
        Slider(
          value: _shadowBlur,
          min: 0,
          max: 30,
          divisions: 30,
          label: '${_shadowBlur.toStringAsFixed(0)}px',
          onChanged: (value) => setState(() => _shadowBlur = value),
        ),
        SizedBox(height: 16.h),
        
        Text('阴影颜色', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold)),
        SizedBox(height: 8.h),
        Wrap(
          spacing: 8.w,
          children: [
            Colors.black26,
            Colors.black54,
            Colors.blue.withOpacity(0.3),
            Colors.red.withOpacity(0.3),
          ].map((color) => _buildColorButton(color, _shadowColor, (c) {
            setState(() => _shadowColor = c);
          })).toList(),
        ),
      ],
    );
  }

  Widget _buildBackgroundControls() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('背景颜色', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold)),
        SizedBox(height: 8.h),
        Wrap(
          spacing: 8.w,
          runSpacing: 8.h,
          children: [
            Colors.white,
            Colors.grey[100]!,
            Colors.blue[50]!,
            Colors.green[50]!,
            Colors.orange[50]!,
            Colors.purple[50]!,
          ].map((color) => _buildColorButton(color, _backgroundColor, (c) {
            setState(() => _backgroundColor = c);
          })).toList(),
        ),
      ],
    );
  }

  Widget _buildPaddingControls() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('内边距', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold)),
        Slider(
          value: _padding,
          min: 0,
          max: 50,
          divisions: 50,
          label: '${_padding.toStringAsFixed(0)}px',
          onChanged: (value) => setState(() => _padding = value),
        ),
      ],
    );
  }

  Widget _buildColorButton(Color color, Color currentColor, Function(Color) onTap) {
    final isSelected = color == currentColor;
    return GestureDetector(
      onTap: () => onTap(color),
      child: Container(
        width: 40.w,
        height: 40.w,
        decoration: BoxDecoration(
          color: color,
          border: Border.all(
            color: isSelected ? Colors.blue : Colors.grey[300]!,
            width: isSelected ? 3.w : 1.w,
          ),
          borderRadius: BorderRadius.circular(8.r),
        ),
      ),
    );
  }

  Widget _buildPreview() {
    return Container(
      width: 200.w,
      height: 150.h,
      padding: EdgeInsets.all(_padding),
      decoration: BoxDecoration(
        color: _backgroundColor,
        border: Border.all(
          color: _borderColor,
          width: _borderWidth,
        ),
        borderRadius: BorderRadius.circular(_borderRadius),
        boxShadow: [
          BoxShadow(
            color: _shadowColor,
            offset: Offset(_shadowX, _shadowY),
            blurRadius: _shadowBlur,
          ),
        ],
      ),
      child: Center(
        child: Text(
          '预览效果',
          style: TextStyle(
            fontSize: 16.sp,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    );
  }

  String _generateCssCode() {
    final buffer = StringBuffer();
    buffer.writeln('.element {');
    
    // 边框
    if (_borderWidth > 0) {
      buffer.writeln('  border: ${_borderWidth.toStringAsFixed(1)}px solid ${_colorToHex(_borderColor)};');
    }
    
    // 圆角
    if (_borderRadius > 0) {
      buffer.writeln('  border-radius: ${_borderRadius.toStringAsFixed(0)}px;');
    }
    
    // 背景
    buffer.writeln('  background-color: ${_colorToHex(_backgroundColor)};');
    
    // 内边距
    if (_padding > 0) {
      buffer.writeln('  padding: ${_padding.toStringAsFixed(0)}px;');
    }
    
    // 阴影
    if (_shadowBlur > 0 || _shadowX != 0 || _shadowY != 0) {
      buffer.writeln('  box-shadow: ${_shadowX.toStringAsFixed(0)}px ${_shadowY.toStringAsFixed(0)}px ${_shadowBlur.toStringAsFixed(0)}px ${_colorToHex(_shadowColor)};');
    }
    
    buffer.writeln('}');
    return buffer.toString();
  }

  String _colorToHex(Color color) {
    return '#${color.value.toRadixString(16).substring(2, 8)}';
  }

  void _copyCssCode() {
    Clipboard.setData(ClipboardData(text: _generateCssCode()));
    Get.snackbar(
      '复制成功',
      'CSS代码已复制到剪贴板',
      snackPosition: SnackPosition.BOTTOM,
      duration: const Duration(seconds: 2),
    );
  }
}

界面布局设计

整个页面分为三个主要区域:

顶部标签栏:切换不同的CSS属性类别。

左侧控制面板:调整具体的CSS参数。

右侧预览区:上半部分显示实时效果,下半部分显示生成的CSS代码。

这种布局让用户可以边调整边看效果,非常直观。

标签切换实现

使用一个简单的索引变量控制显示哪个面板:

int _selectedTab = 0;

Widget _buildTab(String title, int index) {
  final isSelected = _selectedTab == index;
  return GestureDetector(
    onTap: () => setState(() => _selectedTab = index),
    child: Container(
      decoration: BoxDecoration(
        color: isSelected ? Colors.white : Colors.transparent,
        border: Border(
          bottom: BorderSide(
            color: isSelected ? Colors.blue : Colors.transparent,
            width: 2.w,
          ),
        ),
      ),
      child: Text(title),
    ),
  );
}

选中的标签会有蓝色下划线和白色背景,视觉反馈很清晰。

滑块控件使用

Slider是调整数值的最佳选择,直观又好用:

Slider(
  value: _borderWidth,
  min: 0,
  max: 10,
  divisions: 20,
  label: '${_borderWidth.toStringAsFixed(1)}px',
  onChanged: (value) => setState(() => _borderWidth = value),
)

divisions参数:把滑块分成20个刻度,让调整更精确。

label参数:显示当前值,用户知道自己在调什么。

onChanged回调:实时更新状态,预览立即刷新。

颜色选择器实现

我们用一组色块来实现颜色选择:

Widget _buildColorButton(Color color, Color currentColor, Function(Color) onTap) {
  final isSelected = color == currentColor;
  return GestureDetector(
    onTap: () => onTap(color),
    child: Container(
      width: 40.w,
      height: 40.w,
      decoration: BoxDecoration(
        color: color,
        border: Border.all(
          color: isSelected ? Colors.blue : Colors.grey[300]!,
          width: isSelected ? 3.w : 1.w,
        ),
        borderRadius: BorderRadius.circular(8.r),
      ),
    ),
  );
}

选中的颜色会有更粗的蓝色边框,一眼就能看出来。

虽然颜色选项有限,但覆盖了最常用的几种。如果要支持自定义颜色,可以集成flutter_colorpicker包。

实时预览实现

预览区直接使用Flutter的Container来模拟CSS效果:

Container(
  decoration: BoxDecoration(
    color: _backgroundColor,
    border: Border.all(
      color: _borderColor,
      width: _borderWidth,
    ),
    borderRadius: BorderRadius.circular(_borderRadius),
    boxShadow: [
      BoxShadow(
        color: _shadowColor,
        offset: Offset(_shadowX, _shadowY),
        blurRadius: _shadowBlur,
      ),
    ],
  ),
)

Flutter的BoxDecoration和CSS的样式属性几乎是一一对应的,所以预览效果非常准确。

CSS代码生成

根据当前的参数值,动态生成CSS代码:

String _generateCssCode() {
  final buffer = StringBuffer();
  buffer.writeln('.element {');
  
  if (_borderWidth > 0) {
    buffer.writeln('  border: ${_borderWidth.toStringAsFixed(1)}px solid ${_colorToHex(_borderColor)};');
  }
  
  if (_borderRadius > 0) {
    buffer.writeln('  border-radius: ${_borderRadius.toStringAsFixed(0)}px;');
  }
  
  buffer.writeln('}');
  return buffer.toString();
}

使用StringBuffer拼接字符串,效率比直接用加号高。

只有当属性值不为默认值时才输出,避免生成冗余代码。

颜色转换工具

Flutter的Color对象需要转换成CSS的十六进制格式:

String _colorToHex(Color color) {
  return '#${color.value.toRadixString(16).substring(2, 8)}';
}

color.value是一个32位整数,包含ARGB四个通道。我们只需要RGB部分,所以截取后6位。

复制功能实现

点击复制按钮,把CSS代码复制到剪贴板:

void _copyCssCode() {
  Clipboard.setData(ClipboardData(text: _generateCssCode()));
  Get.snackbar(
    '复制成功',
    'CSS代码已复制到剪贴板',
    snackPosition: SnackPosition.BOTTOM,
    duration: const Duration(seconds: 2),
  );
}

使用GetX的snackbar给用户一个反馈,体验更好。

性能优化

每次调整参数都会触发setState,重建整个页面。这在参数不多的情况下没问题,但如果功能扩展了,可能会卡顿。

优化方法是把不同的区域拆分成独立的StatefulWidget,只重建需要更新的部分。

class BorderControlPanel extends StatefulWidget {
  final Function(double, Color, double) onChanged;
  // ...
}

这样调整边框参数时,阴影控制面板就不会重建了。

功能扩展建议

更多CSS属性:文字样式、定位、变换等。

预设模板:提供一些常用的样式组合,用户可以快速选择。

历史记录:保存用户生成过的CSS,方便复用。

导出功能:除了复制,还可以导出为CSS文件。

响应式预览:模拟不同屏幕尺寸下的效果。

实战经验

做这个功能时,最大的挑战是如何让界面简洁又功能完整。CSS属性太多了,全部放上去会很乱。

最后决定只做最常用的几个属性,通过标签切换来组织。这样既保持了界面清爽,又能满足大部分需求。

还有一个细节:滑块的范围设置很重要。比如边框宽度,设置0-10px就够了,设置0-100px反而不好用,因为大部分时候只需要1-5px。

小结

CSS生成器通过可视化的方式降低了CSS编写的难度。用户不需要记住所有属性名和语法,只需要拖动滑块、选择颜色,就能生成标准的CSS代码。

核心技术点包括:标签切换、滑块控件、实时预览、代码生成。把这些组合起来,就是一个实用的CSS工具。

记住:工具的价值在于降低使用门槛,让复杂的事情变简单。


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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐