Điều chỉnh độ rộng cột Excel theo số lượng ảnh trong ô với EasyExcel

Khi xuất tệp Excel bằng EasyExcel, nếu một ô chứa nội dung ảnh và có nhiều ảnh trong ô đó, chúng ta cần điều chỉnh độ rộng của ô sao cho phù hợp với số lượng ảnh.

Sau quá trình nghiên cứu và thử nghiệm, kết quả xuất tệp Excel như sau:

1. Cấu hình phiên bản EasyExcel

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.2</version>
</dependency>

2. Công cụ chuyển đổi URL ảnh sang dữ liệu ảnh Excel

Tạo một lớp tiện ích để xử lý việc chuyển đổi danh sách các URL ảnh thành định dạng `ImageData` mà EasyExcel có thể sử dụng.

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ImageData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.util.IoUtils;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * Công cụ chuyển đổi URL ảnh sang dữ liệu ảnh Excel.
 */
public class ImageUrlConverter implements Converter<List<String>> {

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        // Hỗ trợ kiểu dữ liệu chuỗi cho đầu vào
        return CellDataTypeEnum.STRING;
    }

    @Override
    public WriteCellData<?> convertToExcelData(List<String> urlList, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        List<ImageData> imageDataList = new ArrayList<>();
        for (String imageUrlString : urlList) {
            try {
                URL imageUrl = new URL(imageUrlString);
                // Đọc dữ liệu ảnh từ URL
                byte[] imageDataBytes = IoUtils.toByteArray(imageUrl.openConnection().getInputStream());
                ImageData imageData = new ImageData();
                imageData.setImage(imageDataBytes);
                imageDataList.add(imageData);
            } catch (Exception e) {
                // Xử lý ngoại lệ nếu có lỗi khi đọc ảnh
                e.printStackTrace();
            }
        }
        // Tạo ô ghi dữ liệu với danh sách ảnh
        WriteCellData writeCellData = new WriteCellData();
        writeCellData.setImageDataList(imageDataList);
        writeCellData.setType(CellDataTypeEnum.STRING); // Đặt kiểu dữ liệu là chuỗi
        return writeCellData;
    }
}

3. Định nghĩa thuộc tính cho trường ảnh

Sử dụng annotation `@ExcelProperty` để chỉ định tên cột và sử dụng `ImageUrlConverter` đã tạo để xử lý dữ liệu ảnh.

@ExcelProperty(value = "Ảnh hiện trường (Góc nhìn robot mô phỏng)", converter = ImageUrlConverter.class)
private List<String> sceneImages;

4. Bộ ghi đè xử lý ghi dữ liệu ảnh vào ô

Tạo một lớp `CellWriteHandler` để can thiệp vào quá trình ghi dữ liệu vào ô. Lớp này sẽ lấy dữ liệu ảnh, tính toán độ rộng cột dựa trên số lượng ảnh lớn nhất trong một cột, và chèn ảnh vào ô.

import cn.hutool.core.util.ObjUtil;
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.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.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.util.Units;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Bộ ghi đè xử lý việc chèn ảnh vào ô và điều chỉnh độ rộng cột.
 */
public class ImageCellHandler implements CellWriteHandler {

    // Lưu trữ dữ liệu ảnh theo tọa độ ô (hàng_cột)
    private final Map<String, List<ImageData>> imageDataStorage = new HashMap<>();

    // Theo dõi số lượng ảnh tối đa trong một cột để điều chỉnh độ rộng
    private final AtomicReference<Integer> maxImageCountInColumn = new AtomicReference<>(0);

    /**
     * Xử lý sau khi dữ liệu ô được chuyển đổi.
     * Lấy dữ liệu ảnh và xóa khỏi ô để xử lý sau.
     */
    @Override
    public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, WriteCellData<?> cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        // Bỏ qua nếu là tiêu đề hoặc ô không có dữ liệu ảnh
        if (isHead || ObjUtil.isEmpty(cellData.getImageDataList())) {
            return;
        }

        // Lưu trữ dữ liệu ảnh và làm trống ô dữ liệu
        String cellKey = cell.getRowIndex() + "_" + cell.getColumnIndex();
        imageDataStorage.put(cellKey, cellData.getImageDataList());
        cellData.setType(CellDataTypeEnum.EMPTY); // Đặt kiểu ô thành trống
        cellData.setImageDataList(new ArrayList<>()); // Xóa danh sách ảnh khỏi ô
    }

    /**
     * Xử lý sau khi ô đã được xử lý.
     * Chèn ảnh vào ô và điều chỉnh độ rộng cột.
     */
    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        // Bỏ qua nếu là tiêu đề hoặc không có dữ liệu để xử lý
        if (isHead || ObjUtil.isEmpty(cellDataList)) {
            return;
        }

        String cellKey = cell.getRowIndex() + "_" + cell.getColumnIndex();
        List<ImageData> currentImageDataList = imageDataStorage.get(cellKey);

        // Nếu không có dữ liệu ảnh cho ô này, bỏ qua
        if (ObjUtil.isEmpty(currentImageDataList)) {
            return;
        }

        // Cập nhật số lượng ảnh tối đa nếu cần
        if (currentImageDataList.size() > maxImageCountInColumn.get()) {
            maxImageCountInColumn.set(currentImageDataList.size());
        }

        Sheet sheet = cell.getSheet();
        // Đặt chiều cao hàng (có thể điều chỉnh theo yêu cầu)
        sheet.getRow(cell.getRowIndex()).setHeight((short) 900);

        // Đặt chiều rộng cột dựa trên số lượng ảnh tối đa
        // Giá trị 3493 là một hệ số ước lượng, có thể cần điều chỉnh
        int columnWidth = maxImageCountInColumn.get() > 0 ? 3493 * maxImageCountInColumn.get() : 3493;
        sheet.setColumnWidth(cell.getColumnIndex(), columnWidth);

        // Chèn các ảnh vào ô
        for (int i = 0; i < currentImageDataList.size(); i++) {
            ImageData imageData = currentImageDataList.get(i);
            if (ObjUtil.isEmpty(imageData) || ObjUtil.isEmpty(imageData.getImage())) {
                continue;
            }
            byte[] pictureBytes = imageData.getImage();
            insertImageIntoCell(sheet, cell, pictureBytes, i);
        }

        // Xóa dữ liệu ảnh đã xử lý khỏi bộ nhớ đệm
        imageDataStorage.remove(cellKey);
    }

    /**
     * Chèn một ảnh vào một ô cụ thể.
     * @param sheet Trang tính hiện tại.
     * @param cell Ô để chèn ảnh.
     * @param pictureData Dữ liệu byte của ảnh.
     * @param imageIndex Chỉ số của ảnh trong danh sách (để đặt vị trí).
     */
    private void insertImageIntoCell(Sheet sheet, Cell cell, byte[] pictureData, int imageIndex) {
        // Xác định kích thước ảnh (ví dụ: 100 pixel)
        int pictureWidthInEMU = Units.pixelToEMU(100);

        // Thêm ảnh vào workbook và lấy chỉ số của ảnh
        int pictureIndex = sheet.getWorkbook().addPicture(pictureData, HSSFWorkbook.PICTURE_TYPE_PNG);

        // Tạo đối tượng Drawing nếu chưa có
        Drawing<?> drawing = sheet.getDrawingPatriarch();
        if (ObjUtil.isEmpty(drawing)) {
            drawing = sheet.createDrawingPatriarch();
        }

        // Tạo Anchor để xác định vị trí và kích thước ảnh
        CreationHelper helper = sheet.getWorkbook().getCreationHelper();
        ClientAnchor anchor = helper.createClientAnchor();

        // Đặt ô chứa ảnh
        anchor.setCol1(cell.getColumnIndex());
        anchor.setCol2(cell.getColumnIndex());
        anchor.setRow1(cell.getRowIndex());
        anchor.setRow2(cell.getRowIndex() + 1); // Cho phép ảnh tràn xuống hàng tiếp theo nếu cần

        // Đặt vị trí tương đối của ảnh trong ô
        // Dx1, Dx2: vị trí ngang, Dy1, Dy2: vị trí dọc
        anchor.setDx1(pictureWidthInEMU * imageIndex); // Dịch sang phải theo số ảnh
        anchor.setDx2(pictureWidthInEMU + pictureWidthInEMU * imageIndex); // Kéo dài ảnh tới vị trí mới
        anchor.setDy1(0);
        anchor.setDy2(0);

        // Cho phép ảnh di chuyển và thay đổi kích thước cùng với ô
        anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE);

        // Tạo đối tượng ảnh và thêm vào Drawing
        drawing.createPicture(anchor, pictureIndex);
    }
}

Mã cốt lõi để điều chỉnh độ rộng cột:

// Đặt chiều rộng cột dựa trên số lượng ảnh tối đa
int columnWidth = maxImageCountInColumn.get() > 0 ? 3493 * maxImageCountInColumn.get() : 3493;
sheet.setColumnWidth(cell.getColumnIndex(), columnWidth);

5. Cách sử dụng bộ ghi đè khi ghi tệp Excel

Đăng ký `ImageCellHandler` khi thực hiện ghi tệp Excel để các xử lý tùy chỉnh được áp dụng.

EasyExcel.write("Tên_Tệp_Excel.xlsx", DemoData.class)
        .sheet("Trang_Tính_Thử_Nghiệm")
        .registerWriteHandler(new ImageCellHandler()) // Đăng ký bộ ghi đè tùy chỉnh
        .doWrite(List.of(new DemoData())); // Dữ liệu để ghi

Môi trường phát triển

  • JDK: 1.8.0_202
  • Spring Boot: 2.7.17
  • EasyExcel: 3.3.2

Thẻ: easyexcel Java Excel Image cell width

Đăng vào ngày 16 tháng 7 lúc 07:06