一.背景
最近業務需求需要導出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;
}
}
微信公衆號