解决easyexcel填充图片后合并单元格没有占满的问题

该文章提供了一个自定义的`ImageModifyHandler`类,用于处理EasyExcel在合并单元格时图片保持比例的问题。通过获取合并区域信息并调整图片的相对列和行索引,确保图片不会被拉伸。代码示例展示了如何在写入Excel时注册这个处理器来正确显示图片。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

解决easyexcel合并单元格图片没有拉伸的问题
没有处理之前
处理之后

package com.demo.server.excel.handle;

import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.ImageData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.util.StyleUtil;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFPicture;
import org.apache.poi.xssf.usermodel.XSSFShape;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.xmlbeans.XmlCursor;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Objects;

/**
 * 图像细胞编写处理程序
 *
 * @author admin
 * @date 2023/01/13
 */
public class ImageModifyHandler implements CellWriteHandler {

    @Override
    public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, WriteCellData<?> cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        // 此处不处理表头,不处理不包含图像的
        boolean noImageValue = Objects.isNull(cellData) || CollectionUtils.isEmpty(cellData.getImageDataList());
        if (Objects.equals(Boolean.TRUE, isHead) || noImageValue) {
            return;
        }
        cellData.setType(CellDataTypeEnum.EMPTY);
    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        if (CollectionUtils.isEmpty(cellDataList) || Objects.equals(Boolean.TRUE, isHead)) {
            return;
        }
        // cellDataList 是list的原因是 填充的情况下 可能会多个写到一个单元格 但是如果普通写入只有一个
        WriteCellData<?> writeCellData = cellDataList.get(0);
        //  在afterCellDataConverted方法里面已经将CellDataType设置为EMPTY 此处不能用cellData.getType()来判断是否图片类型
        if (Objects.isNull(writeCellData) || Objects.isNull(writeCellData.getImageDataList()) || writeCellData.getImageDataList().get(0) == null) {
            return;
        }
        ImageData imageData = writeCellData.getImageDataList().get(0);
        setImageValue(imageData, cell);

    }

    private void setImageValue(ImageData imageData, Cell cell) {
        //获取当前单元格所在的sheet
        Sheet sheet = cell.getRow().getSheet();
        //获取当前sheet页中的所有合并单元格信息
        List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
        //获取当前单元格的开始列号
        int firstColumn = (short) cell.getColumnIndex();
        //获取当前单元格的开始行号
        int firstRow = cell.getRow().getRowNum();
        //获取当前单元格的结束列号
        int lastColumn = (short) (cell.getColumnIndex());
        //获取当前单元格的结束行号
        int lastRow = cell.getRow().getRowNum();
        for (CellRangeAddress mergedRegion : mergedRegions) {
            //判断当前单元格是否包含合并行或和并列 当前单元格的所有行号和列号都包含在合并域内 则认为当前单元格存在合并行或和并列
            if (cell.getColumnIndex() >= mergedRegion.getFirstColumn() && cell.getColumnIndex() <= mergedRegion.getLastColumn() && cell.getRow().getRowNum() >= mergedRegion.getFirstRow() && cell.getRow().getRowNum() <= mergedRegion.getLastRow()) {
                //获取合并域的开始行号
                firstRow = mergedRegion.getFirstRow();
                //获取合并域的结束行号
                lastRow = mergedRegion.getLastRow();
                //获取合并域的开始列号
                firstColumn = mergedRegion.getFirstColumn();
                //获取合并域的结束列号
                lastColumn = mergedRegion.getLastColumn();
                break;
            }
        }
        imageData.setRelativeLastColumnIndex(lastColumn - 1);
        imageData.setRelativeLastRowIndex(lastRow - 1);

        int index = sheet.getWorkbook().addPicture(imageData.getImage(), HSSFWorkbook.PICTURE_TYPE_PNG);
        Drawing<?> drawing = sheet.getDrawingPatriarch();
        if (drawing == null) {
            drawing = sheet.createDrawingPatriarch();
        }
        CreationHelper helper = sheet.getWorkbook().getCreationHelper();
        ClientAnchor anchor = helper.createClientAnchor();
        // 图片边距:让图片不会填满整个单元格,与四周有一定边距
        anchor.setDx1(StyleUtil.getCoordinate(2));
        anchor.setDx2(-StyleUtil.getCoordinate(2));
        anchor.setDy1(StyleUtil.getCoordinate(2));
        anchor.setDy2(-StyleUtil.getCoordinate(2));
        // 图片行列
        anchor.setCol1(cell.getColumnIndex());
        anchor.setCol2(cell.getColumnIndex() + (lastColumn - firstColumn + 1));
        anchor.setRow1(cell.getRowIndex());
        anchor.setRow2(cell.getRowIndex() + (lastRow - firstRow + 1));
        anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE);
        drawing.createPicture(anchor, index);
    }
}

使用方式

 excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(inputStream).build();
            WriteSheet writeSheet = EasyExcel.writerSheet()
                   .registerWriteHandler(new ImageModifyHandler()).build();

不过这种方式存在一点小缺陷就是大图后还隐藏一张小图
第二种方式暂时没有出现问题

package com.demo.server.excel.handle;

import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.ImageData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Objects;

/**
 * 修改图像处理程序
 *
 * @author admin
 * @date 2023/01/16
 */
public class ImageModifyHandler implements CellWriteHandler {
    /**
     * 后单元格数据转换
     *
     * @param writeSheetHolder 写单夹
     * @param writeTableHolder 写表夹
     * @param cellData         单元格数据
     * @param cell             细胞
     * @param head             头
     * @param relativeRowIndex 相对行索引
     * @param isHead           是头
     */
    @Override
    public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                                       WriteCellData<?> cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        boolean noImageValue = Objects.isNull(cellData) || CollectionUtils.isEmpty(cellData.getImageDataList());
        if (Objects.equals(Boolean.TRUE, isHead) || noImageValue) {
            return;
        }
        Sheet sheet = cell.getSheet();
        int mergeColumNum = getMergeColumNum(cell, sheet);
        int mergeRowNum = getMergeRowNum(cell, sheet);
        ImageData imageData = cellData.getImageDataList().get(0);
        imageData.setRelativeLastRowIndex(mergeRowNum - 1);
        imageData.setRelativeLastColumnIndex(mergeColumNum - 1);
        CellWriteHandler.super.afterCellDataConverted(writeSheetHolder, writeTableHolder, cellData, cell, head, relativeRowIndex, isHead);
    }


    /**
     * 得到合并行num
     *
     * @param cell  细胞
     * @param sheet 表
     * @return int
     */
    public static int getMergeRowNum(Cell cell, Sheet sheet) {
        int mergeSize = 1;
        List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
        for (CellRangeAddress cellRangeAddress : mergedRegions) {
            if (cellRangeAddress.isInRange(cell)) {
                //获取合并的行数
                mergeSize = cellRangeAddress.getLastRow() - cellRangeAddress.getFirstRow() + 1;
                break;
            }
        }
        return mergeSize;
    }

    /**
     * 得到合并列num
     *
     * @param cell  细胞
     * @param sheet 表
     * @return int
     */
    public static int getMergeColumNum(Cell cell, Sheet sheet) {
        int mergeSize = 1;
        List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
        for (CellRangeAddress cellRangeAddress : mergedRegions) {
            if (cellRangeAddress.isInRange(cell)) {
                //获取合并的列数
                mergeSize = cellRangeAddress.getLastColumn() - cellRangeAddress.getFirstColumn() + 1;
                break;
            }
        }
        return mergeSize;
    }

}

如有更好的方式,还望各位大佬多多指教!

<think>好的,我现在需要解决用户的问题:如何使用EasyExcel根据模板填充导出,并合并相同单元。用户提到了示例教程,所以我需要提供一个清晰的步骤说明,并且结合代码示例。首先,我得回忆一下EasyExcel的相关文档和之前的使用经验。 首先,EasyExcel的模板填充功能允许用户通过预定义的模板来生成Excel文件,这样可以保持式的一致性。合并单元通常需要自定义单元合并策略,这应该是通过实现CellWriteHandler接口来完成的。用户需要将相同内容的单元合并,所以应该在填充数据的时候应用这个策略。 接下来,我需要考虑如何编写模板。用户需要创建一个包含占位符的Excel模板,比如使用{{name}}这样的变量。然后在代码中,通过填充配置将数据填入这些占位符。同时,合并策略需要在填充时被注册,这样EasyExcel在写入数据时会自动处理合并。 然后,代码部分需要包括填充配置的设置,合并策略的实现,以及如何读取模板并导出文件。合并策略的逻辑应该是,在每次处理单元内容后,检查当前单元的值是否与前一行相同,如果是,则合并这些单元。这里需要注意的是,合并的行数需要动态计算,可能需要维护一个状态来记录连续的相同值。 另外,用户可能希望合并的列是动态指定的,所以合并策略应该可以配置需要合并的列索引。例如,在示例中合并第二列,所以需要传入列索引参数。 测试的时候,需要准备测试数据和模板文件,运行代码后检查导出的Excel文件是否正确合并了单元。同时,要处理可能的异常情况,比如空数据或者不同列的情况。 可能遇到的问题包括模板路径不正确、数据式不匹配、合并策略未正确应用等。需要确保模板文件存在,并且占位符与代码中的键对应。此外,合并策略的实现需要正确处理每一行的数据,避免出现合并错误或性能问题。 最后,整理这些思路,分步骤说明,并提供完整的代码示例,确保用户能够按照步骤操作。同时,生成的相关问题需要与EasyExcel的使用和合并单元相关,帮助用户进一步了解扩展功能。</think>以下是如何使用EasyExcel根据模板填充数据并合并相同单元的详细步骤及示例: ### 一、操作流程 1. **准备模板文件** 创建带有占位符的Excel模板,如:`template.xlsx`,其中使用`{{.}}`表示动态填充区域。模板需标注需要合并的列,例如: ``` | 姓名(A列) | 部门(B列) | | {{name}} | {{dept}} | ``` 2. **定义合并策略** 继承`CellWriteHandler`接口实现自动合并逻辑,核心判断连续相同值: ```java public class CustomMergeStrategy implements CellWriteHandler { private final int mergeColumnIndex; private int totalRows = 0; public CustomMergeStrategy(int mergeColumnIndex) { this.mergeColumnIndex = mergeColumnIndex; } @Override public void afterCellDispose(WriteSheetHolder sheetHolder, WriteTableHolder tableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { if (cell.getRowIndex() > 0 && cell.getColumnIndex() == mergeColumnIndex) { String currentVal = cell.getStringCellValue(); String prevVal = cell.getSheet().getRow(cell.getRowIndex()-1).getCell(mergeColumnIndex).getStringCellValue(); if (currentVal.equals(prevVal)) { totalRows++; } else { if (totalRows > 0) { Sheet sheet = cell.getSheet(); CellRangeAddress region = new CellRangeAddress(cell.getRowIndex()-totalRows-1, cell.getRowIndex()-1, mergeColumnIndex, mergeColumnIndex); sheet.addMergedRegion(region); } totalRows = 0; } } } } ``` 3. **数据填充与导出** 通过`ExcelWriter`绑定模板并应用策略: ```java public class ExportDemo { public static void main(String[] args) { List<Employee> dataList = Arrays.asList( new Employee("张三", "技术部"), new Employee("李四", "技术部"), new Employee("王五", "市场部") ); String templatePath = "template.xlsx"; String outputPath = "result.xlsx"; ExcelWriter excelWriter = EasyExcel.write(outputPath) .withTemplate(templatePath) .registerWriteHandler(new CustomMergeStrategy(1)) // 合并第二列(B列) .build(); WriteSheet writeSheet = EasyExcel.writerSheet().build(); excelWriter.fill(dataList, writeSheet); excelWriter.finish(); } } ``` ### 二、关键配置说明 | 组件 | 作用 | |--------------------|----------------------------------------------------------------------| | `@ExcelProperty` | 实体类字段与模板占位符的映射 | | `CellWriteHandler` | 控制单元写入时的合并行为[^1] | | `ExcelWriter` | 核心写入器,支持模板填充和策略叠加 | ### 三、生成效果 导出的Excel会自动将`部门`列中连续相同的值合并,如技术部的两行会合并为一个单元
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值