• EasyExcel代码层面设置写出的Excel样式、以及拦截器策略的使用、自动列宽设置、EasyExcel默认设置详解


    一、概述

    虽然EasyExcel已经提供了一系列注解方式去设置样式。

    但是如果没有实体类,或者想要更精确的去设置导出文件的Excel样式的时候就需要在代码层面去控制样式了。

    二、使用已有拦截器自定义样式

    主要步骤:

    • 创建Excel对应的实体对象
    • 创建一个style策略 并注册
    • 写出Excel

    第一步是否需要创建Excel实体对象,得根据实际需求而定,如果导出字段不固定则使用无模型的方式即可

    不使用实体类时可以直接传入ListString>>类型的数据来作为表头和数据内容。

    2.1 定义一个Excel实体类

    @Getter
    @Setter
    @EqualsAndHashCode
    public class DemoData {
        @ExcelProperty("字符串标题")
        private String string;
        @ExcelProperty("日期标题")
        private Date date;
        @ExcelProperty("数字标题")
        private Double doubleData;
        /**
         * 忽略这个字段
         */
        @ExcelIgnore
        private String ignore;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.2 设置表头样式

    // 创建一个写出的单元格样式对象
    WriteCellStyle headWriteCellStyle = new WriteCellStyle();
    // 背景设置为红色
    headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
    
    // 创建写出Excel的字体对象
    WriteFont headWriteFont = new WriteFont(); 
    headWriteFont.setFontHeightInPoints((short)20);				// 设置字体大小为20
    headWriteFont.setItalic(BooleanEnum.TRUE.getBooleanValue());// 设置字体斜体
    headWriteCellStyle.setWriteFont(headWriteFont);				// 把字体对象设置到单元格样式对象中
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.3 设置内容样式

    // 创建一个写出的单元格样式对象
    WriteCellStyle headWriteCellStyle = new WriteCellStyle();
    // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND
    // 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
    contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
    // 设置内容背景色为绿色
    contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());
    
    // 边框设置
    contentWriteCellStyle.setBorderTop(BorderStyle.THIN);			 // 设置单元格上边框为细线
    contentWriteCellStyle.setBorderBottom(BorderStyle.THICK);		 // 设置单元格下边框为粗线
    contentWriteCellStyle.setBorderLeft(BorderStyle.MEDIUM);	     // 设置单元格左边框为中线
    contentWriteCellStyle.setBorderRight(BorderStyle.MEDIUM_DASHED); // 设置单元格右边框为中虚线
    
    // 创建写出Excel的字体对象
    WriteFont contentWriteFont = new WriteFont();
    contentWriteFont.setFontHeightInPoints((short)20);						  //设置字体大小
    contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); //设置文字居中
    contentWriteCellStyle.setWriteFont(contentWriteFont); 	 // 把字体对象设置到单元格样式对象中
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2.4 使用EasyExcel默认的拦截器策略自定义样式

    常见的策略有两种:

    • HorizontalCellStyleStrategy :每一行的样式都一样 或者隔行一样

      源码中它主要有两个构造函数

      // 构造函数一:接收一个WriteCellStyle对象和一个List集合
      // 第一个参数是表头部分单元格的样式
      // 第二个参数是内容部分的单元格样式
      public HorizontalCellStyleStrategy(WriteCellStyle headWriteCellStyle,
          								List<WriteCellStyle> contentWriteCellStyleList) {
          this.headWriteCellStyle = headWriteCellStyle;
          this.contentWriteCellStyleList = contentWriteCellStyleList;
      }
      
      // 构造函数二:接收两个WriteCellStyle对象
      // 第一个参数是表头部分的单元格的样式
      // 第二个参数是内容部分的单元格样式
      public HorizontalCellStyleStrategy(WriteCellStyle headWriteCellStyle, 
                                         			  WriteCellStyle contentWriteCellStyle) {
          this.headWriteCellStyle = headWriteCellStyle;
          if (contentWriteCellStyle != null) {
              this.contentWriteCellStyleList = 
                  	ListUtils.newArrayList(new WriteCellStyle[]{contentWriteCellStyle});
          }
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21

      WriteCellStyle其实只是一个单元格的对象。

      把它交由HorizontalCellStyleStrategy之后,就可以被渲染成一行的对象。然后每行都按这个策略执行。

      接收List参数时,会循环渲染集合中的样式对象。

    • AbstractVerticalCellStyleStrategy :每一列的样式都一样 需要自己回调每一页

      它是一个抽象类,需要自己定义类去继承它,然后重写里面对应的方法。

      这部分文档中的描述几乎没有,全靠自己摸索,可能官方更推荐第一种方式

    2.5 使用默认的拦截器HorizontalCellStyleStrategy自定义样式

    // 完整代码
    @Test
    public void handlerStyleWrite() {
        // 创建一个写出的单元格样式对象
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND
        // 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
        contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        // 设置内容背景色为绿色
        contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());
    
        // 边框设置
        contentWriteCellStyle.setBorderTop(BorderStyle.THIN);			// 设置单元格上边框为细线
        contentWriteCellStyle.setBorderBottom(BorderStyle.THICK);		// 设置单元格下边框为粗线
        contentWriteCellStyle.setBorderLeft(BorderStyle.MEDIUM);	    // 设置单元格左边框为中线
        contentWriteCellStyle.setBorderRight(BorderStyle.MEDIUM_DASHED);//设置单元格右边框为中虚线
    
        // 创建写出Excel的字体对象
        WriteFont contentWriteFont = new WriteFont();
        contentWriteFont.setFontHeightInPoints((short)20);						 //设置字体大小
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);//设置文字居中
        contentWriteCellStyle.setWriteFont(contentWriteFont); 	 // 把字体对象设置到单元格样式对象中
        
        // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
        HorizontalCellStyleStrategy horizontalCellStyleStrategy =
            new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
    
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        EasyExcel.write(fileName, DemoData.class)
            .registerWriteHandler(horizontalCellStyleStrategy)
            .sheet("horizontalCellStyleStrategy拦截器设置样式")
            .doWrite(data());
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    2.5 使用默认的拦截器AbstractVerticalCellStyleStrategy自定义样式

    1)创建一个类实现AbstractVerticalCellStyleStrategy

    public class CustomVerticalCellStyleStrategy extends AbstractVerticalCellStyleStrategy {
    
        // 重写定义表头样式的方法
        @Override
        protected WriteCellStyle headCellStyle(Head head) {
            WriteCellStyle writeCellStyle = new WriteCellStyle();
            writeCellStyle.setFillBackgroundColor(IndexedColors.RED.getIndex());
            WriteFont writeFont = new WriteFont();
            writeFont.setColor(IndexedColors.RED.getIndex());
            writeFont.setBold(false);
            writeFont.setFontHeightInPoints(Short.valueOf((short)15));
            writeCellStyle.setWriteFont(writeFont);
            return writeCellStyle;
        }
    
        // 重写定义内容部分样式的方法
        @Override
        protected WriteCellStyle contentCellStyle(Head head) {
            WriteCellStyle writeCellStyle = new WriteCellStyle();
            writeCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
            writeCellStyle.setFillBackgroundColor(IndexedColors.GREEN.getIndex());
            writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
            return writeCellStyle;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    这个方式在背景色设置方面存在问题,不知道是不是bug,这种方式还是慎用吧

    2)具体使用

    @Test
    public void handlerStyleWrite() {
    	// 创建拦截器对象
        CustomVerticalCellStyleStrategy customVerticalCellStyleStrategy 
            										= new CustomVerticalCellStyleStrategy();
    
        // 写出Excel
        EasyExcel.write(fileName, DemoData.class)
            .registerWriteHandler(customVerticalCellStyleStrategy)
            .sheet("horizontalCellStyleStrategy拦截器设置样式")
            .doWrite(data());
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    三、自定义拦截器设置Excel样式

    3.1 为什么不使用AbstractCellWriteHandler

    老版本中自定义拦截器主要是继承 Easyexcel 的抽象类AbstractCellWriteHandler 控制器。

    重写beforeCellCreate前置处理方法和afterCellDispose后置处理方法完成对应的方法达到控制单个单元格

    样式的效果。但是AbstractCellWriteHandler这个抽象类在3.x版本已经被弃用,所以现在不推荐使用它。

    3.2 实现CellWriteHandler接口

    前面的两种方式,都只能在代码层面批量设置样式,而不能设置导出Excel中的一部分单元格样式。

    实现这个接口后可以重写afterCellDispose方法来对单个单元格的样式进行设置。

    每个单元格处理完毕之后都会调用它。

    虽然文档中说不太推荐,可能是这里代码多了会影响性能,但是这是目前设置单个单元格最好的方式了。

    3.1 基础使用

    1)定义类实现CellWriteHandler接口,并重写afterCellDispose方法

    public class CustomCellWriteStrategy implements CellWriteHandler {
    
        // 在单元格处理之后执行
        @Override
        public void afterCellDispose(CellWriteHandlerContext context) {
            // 当前事件会在 数据设置到poi的cell里面才会回调
            // 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not true
            if (BooleanUtils.isNotTrue(context.getHead())) {
                // 获取第一个单元格对象
                // 只要不是头 一定会有数据 当然fill(填充)的情况 可能要context.getCellDataList() 
                // 这个需要看模板,因为一个单元格会有多个 WriteCellData
                WriteCellData<?> cellData = context.getFirstCellData();
                
                // cellData 可以获取样式/数据,也可以直接设置样式/数据,设置后会立即生效
                // 这里也需要用cellData去获取样式
                // 很重要的一个原因是 WriteCellStyle 和 dataFormatData绑定的 
                // 简单的说 比如你加了 DateTimeFormat,已经将writeCellStyle里面的dataFormatData改了 			  // 如果你自己new了一个WriteCellStyle,可能注解的样式就失效了
                // 然后getOrCreateStyle 用于返回一个样式,如果为空,则创建一个后返回
                // (总之记住用这个方法获取样式即可)
                WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();
                writeCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
                // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND
                // 要不然背景色不会生效
                writeCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
                
                // 获取当前单元格的数据,必要的时候可以根据数据来设置单元格的颜色
                // 比如当前列为状态列,数据为1是正常,背景色为绿色,反正不正常,背景色设置为红
                // 这种需求的实现将变得可能
                Object data = cellData.getStringValue();
                System.out.println("data: " + data);
                
                // 这样样式就设置好了 后面有个FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置			 到 cell里面去 所以可以不用管了
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    2)写出Excel时注册处理策略

    @Test
    public void handlerStyleWrite() {
    	// 创建处理器策略对象
        CustomCellWriteStrategy customCellWriteStrategy = new CustomCellWriteStrategy();
    
        // 写出Excel
        EasyExcel.write(fileName, DemoData.class)
            .registerWriteHandler(customCellWriteStrategy)
            .sheet("自定义单个单元格样式演示")
            .doWrite(data());
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    四、设置列宽

    有实体类的时候,可以使用注解去设置列宽,但是如果是那种无模型的,又该怎么去设置列宽呢。

    官方提供的LongestMatchColumnWidthStyleStrategy

    4.1 AbstractColumnWidthStyleStrategy

    1)基础写法

    定义一个类,去继承AbstractColumnWidthStyleStrategy这个抽象类,并且重写里面的setColumnWidth方法

    public class ExcelWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {
        
        @Override
        protected void setColumnWidth(WriteSheetHolder writeSheetHolder,
                                      List<WriteCellData<?>> cellDataList,
                                      Cell cell,
                                      Head head,
                                      Integer relativeRowIndex,
                                      Boolean isHead) {
            // 使用sheet对象 简单设置 index所对应的列的列宽
            Sheet sheet = writeSheetHolder.getSheet();
            sheet.setColumnWidth(cell.getColumnIndex(), 5000);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    每处理一个单元格都会调用一次setColumnWidth方法,这个方法有两种重载,重写哪一个都行。

    2)自适应列宽写法

    public class ExcelWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {
    
         // 单元格的最大宽度
        private static final int MAX_COLUMN_WIDTH = 50;                   
        // 缓存(第一个Map的键是sheet的index, 第二个Map的键是列的index, 值是数据长度)
        private  Map<Integer, Map<Integer, Integer>> CACHE = new HashMap(8);    
    
        // 重写设置列宽的方法
        @Override
        protected void setColumnWidth(WriteSheetHolder writeSheetHolder, 
                                      List<WriteCellData<?>> cellDataList, 
                                      Cell cell, 
                                      Head head, 
                                      Integer relativeRowIndex, 
                                      Boolean isHead) {
            boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
            // 当时表头或者单元格数据列表有数据时才进行处理
            if (needSetWidth) {
                Map<Integer, Integer> maxColumnWidthMap = 
                    								CACHE.get(writeSheetHolder.getSheetNo());
                
                if (maxColumnWidthMap == null) {
                    maxColumnWidthMap = new HashMap(16);
                    CACHE.put(writeSheetHolder.getSheetNo(), maxColumnWidthMap);
                }
                // 获取数据长度
                Integer columnWidth = this.dataLength(cellDataList, cell, isHead);
                if (columnWidth >= 0) {
                    if (columnWidth > MAX_COLUMN_WIDTH) {
                        columnWidth = MAX_COLUMN_WIDTH;
                    }
                    // 确保一个列的列宽以表头为主,如果表头已经设置了列宽,单元格将会跟随表头的列宽
                    Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
                    
                    if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
                        maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);
                        // 如果使用EasyExcel默认表头,那么使用columnWidth * 512
                        // 如果不使用EasyExcel默认表头,那么使用columnWidth * 256
                        // 如果是自己定义的字体大小,可以再去测试这个参数常量
                        writeSheetHolder
                            	.getSheet()
                            	.setColumnWidth(cell.getColumnIndex(), columnWidth * 512);
                    }
    
                }
            }
        }
    
        /**
         * 获取当前单元格的数据长度
         * @param cellDataList
         * @param cell
         * @param isHead
         * @return
         */
        private Integer dataLength(List<WriteCellData<?>> cellDataList, 
                                   Cell cell, 
                                   Boolean isHead) {
            if (isHead) {
                return cell.getStringCellValue().getBytes().length;
            } else {
                WriteCellData cellData = cellDataList.get(0);
                CellDataTypeEnum type = cellData.getType();
                if (type == null) {
                    return -1;
                } else {
                    switch(type) {
                        case STRING:
                            return cellData.getStringValue().getBytes().length;
                        case BOOLEAN:
                            return cellData.getBooleanValue().toString().getBytes().length;
                        case NUMBER:
                            return cellData.getNumberValue().toString().getBytes().length;
                        default:
                            return -1;
                    }
                }
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    可以根据自己的需求自行改造

    4.2 写出Excel时注册处理策略

    @Test
    public void handlerStyleWrite() {
    	// 创建处理器策略对象
        ExcelWidthStyleStrategy excelWidthStyleStrategy = new ExcelWidthStyleStrategy();
    
        // 写出Excel
        EasyExcel.write(fileName, DemoData.class)
            .registerWriteHandler(excelWidthStyleStrategy)
            .sheet("单个单元格列宽设置")
            .doWrite(data());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    五、EasyExcel其它默认设置

    5.1 表头自动合并

    EasyExcel.write(response.getOutputStream(), DemoData.class)
                .automaticMergeHead(false)		 // 自动合并表头
                .sheet("模板")
                .doWrite(demoData);
    
    • 1
    • 2
    • 3
    • 4

    automaticMergeHead设置为true,那么对于相邻表格中存在相同内容单元格,easyexcel会自动将其合并。

    它的默认值也是true

    如果不想使用表头自动合并,就设置为false即可。

    5.2 取消导出Excel的默认风格

    EasyExcel.write(response.getOutputStream(), DemoData.class)
                .useDefaultStyle(false)		 	// 取消导出Excel的默认风格
                .sheet("模板")
                .doWrite(demoData);
    
    • 1
    • 2
    • 3
    • 4

    easyexcel的默认风格是最明显的体现,对于表头会显示灰色背景,并且字体会加粗和放大。

    在这里插入图片描述

    如果不想使用这个默认风格,把useDefaultStyle设置为false即可。

    5.3 是否使用1904日期窗口

    EasyExcel.write(response.getOutputStream(), DemoData.class)
                .use1904windowing(true)		 	// 设置使用1904的时间格式
                .sheet("模板")
                .doWrite(demoData);
    
    • 1
    • 2
    • 3
    • 4

    EasyExcel中时间是存储1900年起的一个双精度浮点数。一般也是使用1900的时间格式就可以了。

    如果有业务想把开始日期改为1904,就可以设置use1904windowingtrue即可。

  • 相关阅读:
    【LIUNX】修改hostname方法
    Revive开发商加入VR开源标准OpenXR
    网络安全-零基础小白自学要点
    【牛客刷题-SQL大厂面试真题】NO4.出行场景(某滴打车)
    [ C++ ] 继承
    从Endnote导入Zotero(含PDF)
    DiskMirror-spring-boot-starter 技术|
    正则表达式
    【Redis】Redis内存模型
    期货十三篇 第九篇 心态篇
  • 原文地址:https://blog.csdn.net/qq_44749491/article/details/127917454