
該圖片由birgl在Pixabay上發布
你好,我是看山,
前文 聊了 EasyExcel 的內容匯出,本文主要說一下匯出檔案的格式化,格式化包括作業表/單元格樣式和內容格式化,畢竟,有時候還是要看臉,
內容比較多,文內只會列出關鍵代碼,想要完整原始碼,可以關注公號「看山的小屋」回復“easyexcel”獲取,
注解格式
通過注解定義格式是 EasyExcel 封裝的高級功能,可以讓我們很方便的定義格式,
格式化內容
先定義一個使用注解格式化內容的物體類:
@Data
public class FormatContentItem {
@ExcelProperty(value = "字串標題", converter = TitleFormatConverter.class)
private String string;
@DateTimeFormat("yyyy 年 MM 月 dd 日 HH 時 mm 分 ss 秒")
@ExcelProperty(value = "日期標題")
private Date date;
@NumberFormat("0.000%")
@ExcelProperty("數字標題")
private Double doubleData;
}
其中DateTimeFormat和NumberFormat兩個注解都是自帶的注解,用于格式化時間和數字,
DateTimeFormat注解有兩個屬性,一個屬性是value,用來定義時間格式,可以參考java.text.SimpleDateFormat;另一個屬性是use1904windowing,表示使用時間使用 1904 時間系統還是 1900 時間系統,默認是否,
NumberFormat注解有兩個屬性,一個屬性是value,用來定義數字格式,可以參考java.text.DecimalFormat;另一個屬性是roundingMode,用來定義保留小數的方式,使用的是java.math.RoundingMode列舉,
想要格式化字串,可以借助ExcelProperty的 converter 屬性,這個屬性傳入實作Converter的類,比如示例中的TitleFormatConverter,代碼如下:
public class TitleFormatConverter implements Converter<String> {
@Override
public Class<?> supportJavaTypeKey() {
return String.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
@Override
public WriteCellData<?> convertToExcelData(String value, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return new WriteCellData<>(String.format("標題:%s(自定義)", value));
}
}
結果為:

定義行高、列寬
使用注解定義行高的話,可以使用HeadRowHeight定義表頭高度,使用ContentRowHeight定義表體高度,這個注解定義之后,所有表體高度都是相同的,列寬可以使用ColumnWidth注解定義,這個注解可以定義在類上,表示整個表格的列都一樣寬,也可以定義的屬性上,表示指定列的寬度,
@Data
@HeadRowHeight(20)
@ContentRowHeight(10)
@ColumnWidth(25)
public class FormatCellItem {
@ExcelProperty("字串標題")
private String string;
@ExcelProperty("日期標題")
private Date date;
@ColumnWidth(50)
@ExcelProperty("數字標題")
private Double doubleData;
}
結果為:

單元格定義樣式
控制單元格樣式有四個注解:HeadStyle、HeadFontStyle、ContentStyle、ContentFontStyle,這四個注解可以定義在類上作為全域表格的樣式,也可以定義在欄位上,作為當前列的樣式,下面分別說一下這幾個注解中比較常用的配置,
- *Style:分為
HeadStyle和ContentStyle,分別定義表頭和表體樣式- dataFormat:表頭格式化,short 格式,是
org.apache.poi.ss.usermodel.BuiltinFormats類中已定義格式的小標 - border*:分別是 borderLeft、borderRight、borderTop、borderBottom 四個屬性,型別是
com.alibaba.excel.enums.poi.BorderStyleEnum列舉,用來定義表頭單元格邊框樣式,邊框的顏色也可以定義,使用、*BorderColor 定義即可, - fillPatternType:填充型別,型別是
com.alibaba.excel.enums.poi.FillPatternTypeEnum列舉,如果想要填充背景色,這個屬性需要設定為SOLID_FOREGROUND, - fillForegroundColor:前景色,型別是 short,值卻是使用的
org.apache.poi.ss.usermodel.IndexedColors列舉的 idx 值,只不過,兩個型別不一致,一個是 short,一個是 int,沒有辦法直接參考,可見 java 中的依賴之間,還是有很多坑的, - fillBackgroundColor:背景色,同
fillForegroundColor, - rotation:內容旋轉角度
- dataFormat:表頭格式化,short 格式,是
- *FontStyle:有
HeadFontStyle和ContentFontStyle,分別定義表頭和表體的字體樣式,- fontName:定義字體名稱,型別字串
- fontHeightInPoints:字號大小,型別是 short
- italic:是否斜體,型別是
com.alibaba.excel.enums.BooleanEnum - bold:是否加粗,型別是
com.alibaba.excel.enums.BooleanEnum - strikeout:是否使用洗掉線(這個詞本意是三振出局的意思,應該是與棒球有關)
- color:文本顏色,值使用的是
org.apache.poi.ss.usermodel.IndexedColors,依然有型別不一致的情況 - underline:下劃線,型別是 byte,可以直接使用
Font.U_NONE、Font.U_SINGLE、Font.U_DOUBLE、Font.U_SINGLE_ACCOUNTING、Font.U_DOUBLE_ACCOUNTING,
我們可以這么定義:
@Data
// 頭背景設定成紅色 IndexedColors.RED.getIndex()
@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 10)
// 頭字體設定成 20
@HeadFontStyle(fontHeightInPoints = 20)
// 內容的背景設定成綠色 IndexedColors.GREEN.getIndex()
@ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 17)
// 內容字體設定成 20
@ContentFontStyle(fontHeightInPoints = 20)
public class FormatStyleCellItem {
// 字串的頭背景設定成粉紅 IndexedColors.PINK.getIndex()
@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 14)
// 字串的頭字體設定成 20
@HeadFontStyle(fontHeightInPoints = 30)
// 字串的內容的背景設定成天藍 IndexedColors.SKY_BLUE.getIndex()
@ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 40)
// 字串的內容字體設定成 20
@ContentFontStyle(fontHeightInPoints = 30)
@ExcelProperty("字串標題")
private String string;
@ExcelProperty("日期標題")
private Date date;
@ExcelProperty("數字標題")
private Double doubleData;
}
結果為:

類物件定義格式
這種方式可以說是純手工組裝資料了,使用的是com.alibaba.excel.metadata.data.WriteCellData類,這個類相當于是單元格的定義,通過設定com.alibaba.excel.enums.CellDataTypeEnum列舉型別的 type 屬性,可以指明當前單元格格式,
守恒定律一直存在,這種方式靈活度很高,可以精細到具體的單元格格式,但是繁瑣程度也增加了,
超鏈接
超鏈接使用的是com.alibaba.excel.metadata.data.HyperlinkData類,需要設定地址、超鏈型別(com.alibaba.excel.metadata.data.HyperlinkData.HyperlinkType列舉),然后將值寫入到WriteCellData物件的hyperlinkData屬性即可,
// 設定超鏈接
HyperlinkData hyperlinkData = new HyperlinkData();
hyperlinkData.setAddress("https://www.howardliu.cn");
hyperlinkData.setHyperlinkType(HyperlinkType.URL);
WriteCellData<String> hyperlink = new WriteCellData<>("網站");
hyperlink.setHyperlinkData(hyperlinkData);
備注
備注使用的是com.alibaba.excel.metadata.data.CommentData類,需要設定作者、備注內容(com.alibaba.excel.metadata.data.RichTextStringData型別),因為備注的默認大小是單元格大小,如果感覺太小,還可以設定相對高度和寬度,
// 設定備注
CommentData commentData = new CommentData();
commentData.setAuthor("Howard Liu");
commentData.setRichTextStringData(new RichTextStringData("這是一個備注"));
// 備注的默認大小是按照單元格的大小 這里想調整到 4 個單元格那么大 所以向后 向下 各額外占用了一個單元格
commentData.setRelativeLastColumnIndex(1);
commentData.setRelativeLastRowIndex(1);
WriteCellData<String> comment = new WriteCellData<>("備注的單元格資訊");
comment.setCommentData(commentData);
公式
公式使用的是com.alibaba.excel.metadata.data.FormulaData類,可以直接設定formulaValue公式,不過官方不太推薦使用公式,
// 設定公式
FormulaData formulaData = new FormulaData();
// 將 123456789 中的第一個數字替換成 2
// 這里只是例子 如果真的涉及到公式 能記憶體算好盡量記憶體算好 公式能不用盡量不用
formulaData.setFormulaValue("REPLACE(123456789,1,1,2)");
WriteCellData<String> formula = new WriteCellData<>();
formula.setFormulaData(formulaData);
單元格格式
通過類定義單元格格式,與通過注解定義本質是一樣的,所以與注解HeadStyle、HeadFontStyle、ContentStyle、ContentFontStyle對應,設定單元格格式的類是WriteCellStyle,設定字體的類是WriteFont,其中這些類的屬性與注解的也是類似,不再贅述太多,直接上例子,(其實我覺得使用類定義格式的場景不多,真的碰到了,看看類定義就明白了)
// 設定單個單元格的樣式 當然樣式 很多的話 也可以用注解等方式,
WriteCellStyle writeCellStyleData = new WriteCellStyle();
// 這里需要指定 FillPatternType 為 FillPatternType.SOLID_FOREGROUND 不然無法顯示背景顏色,
writeCellStyleData.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
// 背景綠色
writeCellStyleData.setFillForegroundColor(IndexedColors.GREEN.getIndex());
WriteCellData<String> writeCellStyle = new WriteCellData<>("單元格樣式");
writeCellStyle.setWriteCellStyle(writeCellStyleData);
writeCellStyle.setType(CellDataTypeEnum.STRING);
// 設定單個單元格多種樣式
RichTextStringData richTextStringData = new RichTextStringData();
richTextStringData.setTextString("紅色綠色默認");
// 前 2 個字紅色
WriteFont writeFont = new WriteFont();
writeFont.setColor(IndexedColors.RED.getIndex());
richTextStringData.applyFont(0, 2, writeFont);
// 接下來 2 個字綠色
writeFont = new WriteFont();
writeFont.setColor(IndexedColors.GREEN.getIndex());
richTextStringData.applyFont(2, 4, writeFont);
WriteCellData<String> richTest = new WriteCellData<>();
richTest.setType(CellDataTypeEnum.RICH_TEXT_STRING);
richTest.setRichTextStringDataValue(richTextStringData);
結果為:

攔截器定義格式
除了直接使用類定義格式,我們還可以借助攔截器實作,(這里在名稱上會有一些歧義,所用的類物件命名都是 xxxStrategy,翻譯過來就是 xxx 策略,但是官方對其命名為攔截器)
已有攔截器
前面示例中使用WriteCellStyle、WriteFont可以實作單元格的樣式,如果想要實作整行資料都是相同的格式,可以借助com.alibaba.excel.write.style.HorizontalCellStyleStrategy攔截器,
/**
* 使用已有策略實作自定義樣式
*
* <ul>
* <li>HorizontalCellStyleStrategy 每一行的樣式都一樣 或者隔行一樣</li>
* <li>AbstractVerticalCellStyleStrategy 每一列的樣式都一樣 需要自己回呼每一頁</li>
* </ul>
*/
private static void writeByCellStyleStrategy() {
String fileName = defaultFileName("writeByCellStyleStrategy");
// 表頭策略
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
// 背景設定為紅色
headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontHeightInPoints((short) 40);
headWriteCellStyle.setWriteFont(headWriteFont);
// 表體策略
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
// 這里需要指定 FillPatternType 為 FillPatternType.SOLID_FOREGROUND 不然無法顯示背景顏色,表頭默認了 FillPatternType 所以可以不指定
contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
// 背景綠色
contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());
WriteFont contentWriteFont = new WriteFont();
// 字體大小
contentWriteFont.setFontHeightInPoints((short) 20);
contentWriteCellStyle.setWriteFont(contentWriteFont);
// 這個策略是 頭是頭的樣式 內容是內容的樣式 其他的策略可以自己實作
HorizontalCellStyleStrategy horizontalCellStyleStrategy =
new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
// 這里 需要指定寫用哪個 class 去寫,然后寫到第一個 sheet,名字為模板 然后檔案流會自動關閉
EasyExcelFactory.write(fileName)
.head(Item.class)
.registerWriteHandler(horizontalCellStyleStrategy)
.sheet()
.doWrite(sampleItems());
}
結果為:

正如上面的結果,如果我們某個單元格資料比較長,可能會有遮擋,這個時候我們可以使用com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy實作自動列寬調整,不過這個不太精確,但聊勝于無,
private static void writeUseLongestMatchColumnWidthStyleStrategy() {
String fileName = defaultFileName("writeUseLongestMatchColumnWidthStyleStrategy");
EasyExcelFactory.write(fileName)
.head(Item.class)
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.sheet()
.doWrite(sampleItems());
}
結果為:

可以看到,確實不夠精確,
自定義攔截器
上面展示的攔截器,都是實作了com.alibaba.excel.write.handler.WriteHandler介面,然后使用com.alibaba.excel.write.builder.AbstractExcelWriterParameterBuilder.registerWriteHandler方法注冊到寫函式中,所以,我們也可能根據需要,自己定義需要的攔截器,
這種自定義攔截器屬于低級功能,需要了解很多底層設計和 API,鑒于篇幅,本文沒有辦法覆寫,這里只給出例子,如果有需要,可以留言溝通,
比如,我們需要某些單元格設定資料驗證,展現形式就是下拉選單,我們可以這樣寫:
public class ColumnValidationWriteHandler implements SheetWriteHandler {
@Override
public void afterSheetCreate(SheetWriteHandlerContext context) {
// 區間設定 第一列第一行和第二行的資料,由于第一行是頭,所以第一、二行的資料實際上是第二三行
CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(1, 2, 0, 0);
DataValidationHelper helper = context.getWriteSheetHolder().getSheet().getDataValidationHelper();
DataValidationConstraint constraint = helper.createExplicitListConstraint(new String[] {"測驗 1", "測驗 2"});
DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList);
context.getWriteSheetHolder().getSheet().addValidationData(dataValidation);
}
}
如果我們需要將某個單元格的格式設定為超鏈,也可以使用攔截器:
public class CellStyleWriteHandler implements CellWriteHandler {
@Override
public void afterCellDispose(CellWriteHandlerContext context) {
Cell cell = context.getCell();
// 這里可以對 cell 進行任何操作
if (BooleanUtils.isTrue(context.getHead()) && cell.getColumnIndex() == 0) {
CreationHelper createHelper = context.getWriteSheetHolder().getSheet().getWorkbook().getCreationHelper();
Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.URL);
hyperlink.setAddress("https://www.howardliu.cn");
cell.setHyperlink(hyperlink);
}
}
}
結果為:

合并單元格
EasyExcel 提供的合并單元格功能比較簡單,有兩種方式:基于注解的合并、基于攔截器的合并,
注解
基于注解的合并單元格提供了兩個注解:
OnceAbsoluteMerge注解實作指定位置的合并ContentLoopMerge這個是內容的回圈合并,指定某一列每幾行合并,
// 將第 6-7 行的 2-3 列合并成一個單元格
@OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)
@Data
public class MergeCellItem {
@ContentLoopMerge(eachRow = 2)
@ExcelProperty("字串標題")
private String string;
@ExcelProperty("日期標題")
private Date date;
@ExcelProperty("數字標題")
private Double doubleData;
}
結果為:

攔截器
攔截器合并也是有兩種,對應著注解:
OnceAbsoluteMergeStrategy,相對位置合并LoopMergeStrategy回圈合并
private static void writeMergeCellCustom() {
String fileName = defaultFileName("writeMergeCellCustom");
// 每隔 2 行會合并
// 把 eachColumn 設定成 3 也就是我們資料的長度,所以就第一列會合并,當然其他合并策略也可以自己寫
LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
EasyExcelFactory.write(fileName)
.head(Item.class)
.registerWriteHandler(loopMergeStrategy)
.sheet()
.doWrite(sampleItems());
}
結果為:

文末總結
本文從實戰角度說了一下 EasyExcel 如果實作寫出好看的表格,EasyExcel中提供了很多用于格式化的注解、攔截器,可以時薪通用的格式化輸出,如果還有更加個性化的格式要求,也可以自定義攔截器實作,接下來聊一下如何填充模板,
推薦閱讀
- 阿里開源的這個庫,讓 Excel 匯出不再復雜(簡簡單單的寫)
- 阿里開源的這個庫,讓 Excel 匯出不再復雜(既要能寫,還要寫的好看)
你好,我是看山,游于碼界,戲享人生,如果文章對您有幫助,請點贊、收藏、關注,我還整理了一些精品學習資料,關注公眾號「看山的小屋」,回復“資料”即可獲得,
個人主頁:https://www.howardliu.cn
個人博文:阿里開源的這個庫,讓 Excel 匯出不再復雜(既要能寫,還要寫的好看)
CSDN 主頁:https://kanshan.blog.csdn.net/
CSDN 博文:阿里開源的這個庫,讓 Excel 匯出不再復雜(既要能寫,還要寫的好看)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/306231.html
標籤:java
