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

一、概述

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

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

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

主要步骤:

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

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

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

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;
}

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);				// 把字体对象设置到单元格样式对象中

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); 	 // 把字体对象设置到单元格样式对象中

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

常见的策略有两种:

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

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

    // 构造函数一:接收一个WriteCellStyle对象和一个List<WriteCellStyle>集合
    // 第一个参数是表头部分单元格的样式
    // 第二个参数是内容部分的单元格样式
    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});
        }
    
    }
    

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

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

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

  • 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());

}

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;
    }
}

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

2)具体使用

@Test
public void handlerStyleWrite() {
	// 创建拦截器对象
    CustomVerticalCellStyleStrategy customVerticalCellStyleStrategy 
        										= new CustomVerticalCellStyleStrategy();

    // 写出Excel
    EasyExcel.write(fileName, DemoData.class)
        .registerWriteHandler(customVerticalCellStyleStrategy)
        .sheet("horizontalCellStyleStrategy拦截器设置样式")
        .doWrite(data());

}

三、自定义拦截器设置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里面去 所以可以不用管了
        }
    }
}

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

@Test
public void handlerStyleWrite() {
	// 创建处理器策略对象
    CustomCellWriteStrategy customCellWriteStrategy = new CustomCellWriteStrategy();

    // 写出Excel
    EasyExcel.write(fileName, DemoData.class)
        .registerWriteHandler(customCellWriteStrategy)
        .sheet("自定义单个单元格样式演示")
        .doWrite(data());

}

四、设置列宽

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

官方提供的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);
    }
}

每处理一个单元格都会调用一次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;
                }
            }
        }
    }

}

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

4.2 写出Excel时注册处理策略

@Test
public void handlerStyleWrite() {
	// 创建处理器策略对象
    ExcelWidthStyleStrategy excelWidthStyleStrategy = new ExcelWidthStyleStrategy();

    // 写出Excel
    EasyExcel.write(fileName, DemoData.class)
        .registerWriteHandler(excelWidthStyleStrategy)
        .sheet("单个单元格列宽设置")
        .doWrite(data());
}

五、EasyExcel其它默认设置

5.1 表头自动合并

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

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

它的默认值也是true

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

5.2 取消导出Excel的默认风格

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

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

在这里插入图片描述

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

5.3 是否使用1904日期窗口

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

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

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

  • 20
    点赞
  • 86
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值