一.背景
最近业务需求需要导出Execl,最终做出的效果如下,中间牵扯到大量的数据计算。
二.疑难问题分析
问题1:跨单元格处理及边框设置
问题2:自定义背景颜色添加
问题3:单元格中部分文字设置颜色
问题4:高度自适应处理
三.问题解决
在处理整个Excel导出中总结了很多。
整个开发过程使用的是Apache POI
pom.xml
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.8</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-scratchpad</artifactId> <version>3.8</version> </dependency>
3.1 HSSFworkbook,XSSFworkbook选哪个
最开始我沿用的是之前开发用的,HSSFworkbook最后发现,HSSFworkbook在处理,自定义单元格背景颜色比较复杂,最后换成了XSSFworkbook。
HSSFWorkbook:是操作Excel2003以前(包括2003)的版本,扩展名是.xls;
XSSFWorkbook:是操作Excel2007后的版本,扩展名是.xlsx;
所以在这里我推荐使用XSSFWorkbook
3.2跨单元格及边框设置
//创建第一行,索引是从0开始的 row = sheet.createRow(0); //创建第一个单元格 XSSFCell cell0 = row.createCell(0); //设置单元格文字 cell0.setCellValue("姓名"); //设置单元格样式 cell0.setCellStyle(cellStyleHead); //跨单元格设置 //参数为 firstRow, lastRow, firstCol, lastCol CellRangeAddress cellRange1 = new CellRangeAddress(0, 1, 0, 0); sheet.addMergedRegion(cellRange1); //注意如果直接在下面写设置边框的样式,可能会出现边框覆盖不全的情况,可能是样式覆盖问题 //所以应该在数据渲染完成之后,在代码的最后写跨单元格边框设置,这是非常重要的
调用设置边框
//在数据渲染完成,调用封装的边框设置方法 setRegionStyle(wb, sheet, cellRange1);
设置边框方法
/** * 合并单元格之后设置边框 * * @param wb XSSFWorkbook对象 * @param sheet sheet * @param region region */ static void setRegionStyle(XSSFWorkbook wb, XSSFSheet sheet, CellRangeAddress region) { RegionUtil.setBorderTop(1, region, sheet, wb); RegionUtil.setBorderBottom(1, region, sheet, wb); RegionUtil.setBorderLeft(1, region, sheet, wb); RegionUtil.setBorderRight(1, region, sheet, wb); }
3.3自定义背景颜色设置
因为poi自带的颜色索引可能不满足我们开发的需求,需要自定义样色
//创建单元格样式 XSSFCellStyle cellStyleContent = wb.createCellStyle(); //创建背景颜色 226, 239, 218 对应的就是RGB颜色 红绿蓝 cellStyleContent.setFillForegroundColor(new XSSFColor(new java.awt.Color(226, 239, 218))); //填充m cellStyleContent.setFillPattern(CellStyle.SOLID_FOREGROUND);
3.4设置单元格中部分字体颜色
XSSFRichTextString ts = new XSSFRichTextString("123456\r\n789"); XSSFFont font2 = wb.createFont(); //字体高度 font2.setFontHeightInPoints((short) 10); // 字体 font2.setFontName("宋体"); //字体颜色 font2.setColor(HSSFColor.GREEN.index); //那些字体要设置颜色, //int startIndex 开始索引 //int endIndex 结束索引 // Font font 字体 ts.applyFont(5, ts.length(), font2);
3.5高度自适应
封装的工具类如下,需要在数据渲染完的每行,调用如下工具类
//高度自适应 //XSSFRow row; ExcelUtil.calcAndSetRowHeigt(row);
import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.xssf.usermodel.*; import java.util.HashMap; import java.util.Map; /** * @author Created by niugang on 2020/3/13/13:34 */ public class ExcelUtil { private ExcelUtil() { throw new IllegalStateException("Utility class"); } /** * 根据行内容重新计算行高 * * @param sourceRow sourceRow */ public static void calcAndSetRowHeigt(XSSFRow sourceRow) { for (int cellIndex = sourceRow.getFirstCellNum(); cellIndex <= sourceRow.getPhysicalNumberOfCells(); cellIndex++) { //行高 double maxHeight = sourceRow.getHeight(); XSSFCell sourceCell = sourceRow.getCell(cellIndex); //单元格的内容 String cellContent = getCellContentAsString(sourceCell); if (null == cellContent || "".equals(cellContent)) { continue; } //单元格的宽高及单元格信息 Map<String, Object> cellInfoMap = getCellInfo(sourceCell); Integer cellWidth = (Integer) cellInfoMap.get("width"); Integer cellHeight = (Integer) cellInfoMap.get("height"); if (cellHeight > maxHeight) { maxHeight = cellHeight; } XSSFCellStyle cellStyle = sourceCell.getCellStyle(); //sourceRow.getSheet().getWorkbook() XSSFFont font = cellStyle.getFont(); //字体的高度 short fontHeight = font.getFontHeight(); //cell内容字符串总宽度 double cellContentWidth = cellContent.getBytes().length * 2 * 256; //字符串需要的行数 不做四舍五入之类的操作 double stringNeedsRows = cellContentWidth / cellWidth; //小于一行补足一行 if (stringNeedsRows < 1.0) { stringNeedsRows = 1.0; } //需要的高度 (Math.floor(stringNeedsRows) - 1) * 40 为两行之间空白高度 double stringNeedsHeight = (double) fontHeight * stringNeedsRows; //需要重设行高 if (stringNeedsHeight > maxHeight) { maxHeight = stringNeedsHeight; //超过原行高三倍 则为5倍 实际应用中可做参数配置 if (maxHeight / cellHeight > 5) { maxHeight = 5 * cellHeight; } //最后取天花板防止高度不够 maxHeight = Math.ceil(maxHeight); //重新设置行高 同时处理多行合并单元格的情况 Boolean isPartOfRowsRegion = (Boolean) cellInfoMap.get("isPartOfRowsRegion"); if (isPartOfRowsRegion.equals(Boolean.TRUE)) { Integer firstRow = (Integer) cellInfoMap.get("firstRow"); Integer lastRow = (Integer) cellInfoMap.get("lastRow"); //平均每行需要增加的行高 double addHeight = (maxHeight - cellHeight) / (lastRow - firstRow + 1); for (int i = firstRow; i <= lastRow; i++) { double rowsRegionHeight = sourceRow.getSheet().getRow(i).getHeight() + addHeight; rowsRegionHeight=rowsRegionHeight+10; sourceRow.getSheet().getRow(i).setHeight((short) rowsRegionHeight); } } else { maxHeight=maxHeight+10; sourceRow.setHeight((short) maxHeight); } } } } /** * 解析一个单元格得到数据 * * @param cell cell * @return String */ private static String getCellContentAsString(XSSFCell cell) { final String strZero =".0"; if (null == cell) { return ""; } String result = ""; switch (cell.getCellType()) { case Cell.CELL_TYPE_NUMERIC: String s = String.valueOf(cell.getNumericCellValue()); if (s != null) { if (s.endsWith(strZero)) { s = s.substring(0, s.length() - 2); } } result = s; break; case Cell.CELL_TYPE_STRING: result = String.valueOf(cell.getStringCellValue()).trim(); break; case Cell.CELL_TYPE_BLANK: break; case Cell.CELL_TYPE_BOOLEAN: result = String.valueOf(cell.getBooleanCellValue()); break; case Cell.CELL_TYPE_ERROR: break; default: break; } return result; } /** * 获取单元格及合并单元格的宽度 * * @param cell cell * @return Map<String , Object> */ private static Map<String, Object> getCellInfo(XSSFCell cell) { XSSFSheet sheet = cell.getSheet(); int rowIndex = cell.getRowIndex(); int columnIndex = cell.getColumnIndex(); boolean isPartOfRegion = false; int firstColumn = 0; int lastColumn = 0; int firstRow = 0; int lastRow = 0; int sheetMergeCount = sheet.getNumMergedRegions(); for (int i = 0; i < sheetMergeCount; i++) { CellRangeAddress ca = sheet.getMergedRegion(i); firstColumn = ca.getFirstColumn(); lastColumn = ca.getLastColumn(); firstRow = ca.getFirstRow(); lastRow = ca.getLastRow(); if (rowIndex >= firstRow && rowIndex <= lastRow) { if (columnIndex >= firstColumn && columnIndex <= lastColumn) { isPartOfRegion = true; break; } } } Map<String, Object> map = new HashMap<>(16); int width = 0; int height = 0; boolean isPartOfRowsRegion = false; if (isPartOfRegion) { for (int i = firstColumn; i <= lastColumn; i++) { width += sheet.getColumnWidth(i); } for (int i = firstRow; i <= lastRow; i++) { height += sheet.getRow(i).getHeight(); } if (lastRow > firstRow) { isPartOfRowsRegion = true; } } else { width = sheet.getColumnWidth(columnIndex); height += cell.getRow().getHeight(); } map.put("isPartOfRowsRegion", isPartOfRowsRegion); map.put("firstRow", firstRow); map.put("lastRow", lastRow); map.put("width", width); map.put("height", height); return map; } }
到此这篇关于Java导出Execl疑难点处理的实现的文章就介绍到这了,更多相关Java导出Execl内容请搜索自学编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持自学编程网!
- 本文固定链接: https://zxbcw.cn/post/184895/
- 转载请注明:必须在正文中标注并保留原文链接
- QQ群: PHP高手阵营官方总群(344148542)
- QQ群: Yii2.0开发(304864863)