EasyExcel--自定义表头(标题)样式策略

前言:在项目开发中,我们有时候会遇到自定义表头样式的需求。EasyExcel官方文档中关于表头样式的说明有两种(以设置表头颜色为例):

  • 方式1:通过使用注解来设置颜色(优点:使用非常方便,缺点:对于复杂表头就无能为力了,例如无法单独设置父表头和子表头的颜色)
  • 方式2:通过使用HorizontalCellStyleStrategy 策略的方式来设置表头颜色(优点:通过此策略可以对表头设置更多的样式,缺点:但此策略的应用对象是所有表头,这意味着无法单独对单个的表头进行个性化设置)。
  • 因此,本文想提供一种可行的且更加全面的表头样式设置方式,即自定义策略
1. 源码分析
HorizontalCellStyleStrategy 策略类图.png

通过类图我们不难发现,HorizontalCellStyleStrategy 类继承了AbstractCellStyleStrategy抽象类,而此抽象类中有三个抽象方法,其中与标题头部样式有关的是setHeadCellStyls这个抽象方法,那么这个方法又是何时调用的呢?我们进一步到AbstractCellStyleStrategy类中查看。

AbstractCellStyleStrategy的afterCellDispose方法.png

可以看到,在afterCellDispose方法中有对setHeadCellStyls的调用,当传入的是表头时,就会执行此方法来设置表头样式,否则就是对单元格的内容进行样式设置。

现在,我们再来看看HorizontalCellStyleStrategy 类是如何实现具体实现这些抽象方法的:

  • HorizontalCellStyleStrategy 类中的属性及构造方法
HorizontalCellStyleStrategy 类属性.png

HorizontalCellStyleStrategy 类构造方法.png

可以看到,HorizontalCellStyleStrategy 类定义了两种类型的属性方框1中的属性是属于EasyExcel的样式对象,方框2中的属性是属于POI的,从这里我们不难发现,HorizontalCellStyleStrategy 构造方法中传入的EasyExcel的样式对象,最终都要转换成POI的对象。那么在哪里进行转换呢?请看下面这段代码。

  • 抽象方法ininCellStyle的实现
HorizontalCellStyleStrategy 类中的实现.png

上图中的initCellStyle方法,就是此策略进行对象转换的地方。通过StyleUtil工具类将EasyExcel对象转换成POI对象。值得注意的是,在代码调试的时我发现此方法只会执行一次,即在内容填充前进行样式的初始化,之后就不会执行了。 当然也可以在其父类AbstractCellStyleStrategy中发现此方法的调用时机。如下图所示:

AbstractCellStyleStrategy类中initCellStyle方法的调用时机.png

  • 抽象方法setHeadCellStyle的实现
HorizontalCellStyleStrategy 类中的实现.png

头部的具体样式都在这里进行设置,那么此方法又是何时调用的呢?

AbstractCellStyleStrategy类中setHeadCellStyle方法的调用时机.png

通过调用可以看到,当所有对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<ComplexHeadStyles> headStylesQueue;
    /**
    *   WorkBoot
    * */
    private Workbook workbook;

    /**
    *   构造方法,创建对象时传入需要定制的表头信息队列
    *
    */
    public HeadStyleWriteHandler(ArrayBlockingQueue<ComplexHeadStyles> 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<StudyPioStudentDTO> 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<ComplexHeadStyles> 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);
        }
    }


3. 导出效果:
最终效果.png
通过以上方式,我们就能够单独定义表头的样式啦!!!
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容