前言:在项目开发中,我们有时候会遇到自定义表头样式的需求。EasyExcel官方文档中关于表头样式的说明有两种(以设置表头颜色为例):
- 方式1:通过使用注解来设置颜色(优点:使用非常方便,缺点:对于复杂表头就无能为力了,例如无法单独设置父表头和子表头的颜色)
- 方式2:通过使用HorizontalCellStyleStrategy 策略的方式来设置表头颜色(优点:通过此策略可以对表头设置更多的样式,缺点:但此策略的应用对象是所有表头,这意味着无法单独对单个的表头进行个性化设置)。
- 因此,本文想提供一种可行的且更加全面的表头样式设置方式,即自定义策略
1. 源码分析
通过类图我们不难发现,HorizontalCellStyleStrategy 类继承了AbstractCellStyleStrategy抽象类,而此抽象类中有三个抽象方法,其中与标题头部样式有关的是setHeadCellStyls这个抽象方法,那么这个方法又是何时调用的呢?我们进一步到AbstractCellStyleStrategy类中查看。
可以看到,在afterCellDispose方法中有对setHeadCellStyls的调用,当传入的是表头时,就会执行此方法来设置表头样式,否则就是对单元格的内容进行样式设置。
现在,我们再来看看HorizontalCellStyleStrategy 类是如何实现具体实现这些抽象方法的:
- HorizontalCellStyleStrategy 类中的属性及构造方法:
可以看到,HorizontalCellStyleStrategy 类定义了两种类型的属性,方框1中的属性是属于EasyExcel的样式对象,方框2中的属性是属于POI的,从这里我们不难发现,HorizontalCellStyleStrategy 构造方法中传入的EasyExcel的样式对象,最终都要转换成POI的对象。那么在哪里进行转换呢?请看下面这段代码。
- 抽象方法ininCellStyle的实现
上图中的initCellStyle方法,就是此策略进行对象转换的地方。通过StyleUtil工具类将EasyExcel对象转换成POI对象。值得注意的是,在代码调试的时我发现此方法只会执行一次,即在内容填充前进行样式的初始化,之后就不会执行了。 当然也可以在其父类AbstractCellStyleStrategy中发现此方法的调用时机。如下图所示:
- 抽象方法setHeadCellStyle的实现
头部的具体样式都在这里进行设置,那么此方法又是何时调用的呢?
通过调用可以看到,当所有对Cell的操作都执行完之后,就会调用此方法,如果Cell是头,则执行头部样式方法,如果是内容则执行内容样式方法。至此,HorizontalCellStyleStrategy 类就分析完了。我们要想自定义策略就可以把HorizontalCellStyleStrategy 类当做参考对象。
2. 自定义样式策略的实现
- 基本思路:实现一个可以自定义表头样式的对象、实现一个可以处理此样式集合的策略、写Excel
- 复杂表头样式对象【用于存储当表头的自定义样式信息】:
/**
* 复杂表头样式信息,包含需要自定义的表头坐标及样式
*
* @Author: nxf
* @Date: 2021/1/17 20:32
*/
public class ComplexHeadStyles {
/**
* 表头横坐标 - 行
* */
private Integer x;
/**
* 表头纵坐标 - 列
* */
private Integer y;
/**
* 内置颜色
* */
private Short indexColor;
public ComplexHeadStyles(Integer x, Integer y, Short indexColor){
this.x=x;
this.y=y;
this.indexColor=indexColor;
}
private void setCroods(Integer x,Integer y){
this.x=x;
this.y=y;
}
public Integer getX() {
return x;
}
public void setX(Integer x) {
this.x = x;
}
public Integer getY() {
return y;
}
public void setY(Integer y) {
this.y = y;
}
public Short getIndexColor() {
return indexColor;
}
public void setIndexColor(Short indexColor) {
this.indexColor = indexColor;
}
}
- 自定义样式策略:
import com.alibaba.excel.util.StyleUtil;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.AbstractCellStyleStrategy;
import com.study.poi.utils.styles.ComplexHeadStyles;
import org.apache.poi.ss.usermodel.*;
import java.util.concurrent.ArrayBlockingQueue;
/**
* 自定义样式拦截器-复杂表头样式的使用
*
* @Author: nxf
* @Date: 2021/1/17 14:31
*/
public class HeadStyleWriteHandler extends AbstractCellStyleStrategy {
/**
* 复杂表头自定义样式队列,先进先出,方便存储
* */
private ArrayBlockingQueue headStylesQueue;
/**
* WorkBoot
* */
private Workbook workbook;
/**
* 构造方法,创建对象时传入需要定制的表头信息队列
*
*/
public HeadStyleWriteHandler(ArrayBlockingQueue headStylesQueue){
this.headStylesQueue=headStylesQueue;
}
@Override
protected void initCellStyle(Workbook workbook) {
// 初始化信息时,保存Workbook对象,转换时需要使用
this.workbook=workbook;
}
@Override
protected void setHeadCellStyle(Cell cell, Head head, Integer relativeRowIndex) {
WriteCellStyle writeCellStyle=new WriteCellStyle();
if(headStylesQueue !=null && ! headStylesQueue.isEmpty()){
ComplexHeadStyles complexHeadStyle=headStylesQueue.peek();
// 取出队列中的自定义表头信息,与当前坐标比较,判断是否相符
if(cell.getColumnIndex() == complexHeadStyle.getY() && relativeRowIndex.equals(complexHeadStyle.getX())){
// 设置自定义的表头样式
writeCellStyle.setFillForegroundColor(complexHeadStyle.getIndexColor());
// 样式出队
headStylesQueue.poll();
}
}
// WriteCellStyle转换为CellStyle
CellStyle headCellStyle = StyleUtil.buildHeadCellStyle(workbook, writeCellStyle);
// 设置表头样式
cell.setCellStyle(headCellStyle);
}
@Override
protected void setContentCellStyle(Cell cell, Head head, Integer relativeRowIndex) {
}
}
- Excel写对象类
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.study.poi.utils.SexConverterForStudentInfo;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 学生信息导出-复杂表头-特殊表头样式【单独设置表头样式】(Excel类)
*
* @Author: nxf
* @Date: 2021/1/12 23:07
*/
@Data
@HeadRowHeight(20)
@ColumnWidth(25)
@ContentRowHeight(20)
public class StudyPoiComplexHeadStyleWriteExportDTO {
/**
* 学号ID,主键ID
* note 复杂表头使用这样的方式即可
* note 复杂表头无法使用注解【@HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 14)】来达到满意的效果,其设定的样式会把学生基本信息和学号都设置为同样的颜色,无法单独设置,需另行处理
*/
@ExcelProperty({"学生基本信息", "学号"})
private Long studentId;
/**
* 学生姓名
*/
@ExcelProperty({"学生基本信息", "姓名"})
private String studentName;
/**
* 出生日期
*/
@ExcelProperty(value = "出生日期")
private String studentBirthday;
/**
* 性别
*/
@ExcelProperty(value = "性别")
private String studentSex;
/**
* 年级
*/
@ExcelProperty(value = "年级")
private Integer studentGrade;
/**
* 班级
*/
@ExcelProperty(value = "班级")
private Integer studentClass;
}
- 写Excel
/**
* 复杂表头-自定义表头样式导出-学生信息表
*
* @param response = 浏览器响应对象
* @return: void
* @Author: nxf
* @Date: 2021/1/17 14:59
*/
@PostMapping("/complexHeadStyleExportStudentInfo")
public void complexHeadStyleExportStudentInfo(HttpServletResponse response) throws IOException{
try {
// 查询导出的学生信息表数据
List studyPioStudents=studentInfoExportService.searchAllStudentInfo();
// 字符编码
String encode="utf-8";
// 文件名
String fileName=URLEncoder.encode("复杂表头-自定义表头样式导出",encode).replaceAll("\\+","%20");
// response三部曲 1.设置响应文件类型 2.设置响应编码 3.设置响应文件拓展名
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding(encode);
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
// 设置表头样式队列【先进先出】
ArrayBlockingQueue complexHeadStylesArrayBlockingQueue=new ArrayBlockingQueue<>(4);
/**
* (0,0)和(0,1)位置的单元格设置背景色为红色;(1,0)设置为绿色;(1,1)设置为蓝色
* 写Excel是一行一行写的,因此入队顺序是这样
*/
complexHeadStylesArrayBlockingQueue.add(new ComplexHeadStyles(0,0,IndexedColors.RED1.getIndex()));
complexHeadStylesArrayBlockingQueue.add(new ComplexHeadStyles(0,1,IndexedColors.RED1.getIndex()));
complexHeadStylesArrayBlockingQueue.add(new ComplexHeadStyles(1,0,IndexedColors.LIGHT_GREEN.getIndex()));
complexHeadStylesArrayBlockingQueue.add(new ComplexHeadStyles(1,1,IndexedColors.SKY_BLUE.getIndex()));
// 自定义表头策略
HeadStyleWriteHandler headStyleWriteHandler=new HeadStyleWriteHandler(complexHeadStylesArrayBlockingQueue);
// 写Excel
EasyExcelFactory.write(response.getOutputStream(), StudyPoiComplexHeadStyleWriteExportDTO.class)
.registerWriteHandler(headStyleWriteHandler)
.autoCloseStream(true)
.sheet("自定义学生信息表头颜色")
.doWrite(studyPioStudents);
}catch (Exception e){
errorReturn(response);
}
}