本文介绍在 Java 语言环境下,使用 POI 为 Excel 打水印的解决方案,具体的代码编写以及相关的注意事项。
需求描述:
要求通过系统下载的 Excel 都带上公司的水印,列宽调整为合适的宽度,并且设置为不可编辑,即只读。
即:
1:加水印;
2:调整列宽将单元格内容显示全;
3:设置只读;
解决方案思路介绍:
三点需求比较来说,以第一点比较复杂,同时网上关于 POI 为 Excel 加水印的资料非常少,而这些资料又多数是相互之间 Copy 得来,干货较少。
一:使用模版 Excel 的形式设置水印:
目前网上关于 POI 为 Excel 打水印,主要是通过模版的形式来实现,即先准备一份打了水印的模版 Excel,说白了就是在这个模版 Excel 中的一个 Sheet 中添加一些艺术字,调整下透明度还有字体,角度等,模仿水印,然后加载该模版,再将内容输出到该模版中,以达到为 Excel 添加水印的目的,详细可参考下面这份博客(不追溯该博客是否是原始版本):http://jsonchar.blog.163.com/blog/static/17601614120106135519213/
但这种方式有如下问题:
1:通过手动添加水印,水印数量固定:通过人工在一个 Sheet 中添加水印,个数必然固定,当实际内容行数不定,列数不定,会导致水印不能覆盖内容;
2:当无法确定下载的 Excel 会有多少个 Sheet 的时候,无法保证输出的多个 Sheet 都有水印:产生这种情况是因为,这种实现方式的根本原理是读取已经存在的模版 Excel 内的 Sheet,并将内容输出到该 Sheet 中,也就是说如果模版内的 Sheet 内有水印则输出内容有水印,如果该 Sheet 没有水印则输出内容没有水印。毕竟通过手工添加水印,准备模版的方式,你不能准备无限多个模版 Sheet,准备一个两个也就到头了。
3:你无法通过程序拷贝模版 Sheet:你可能会问了,那我准备一个模版 Sheet,在其中尽量多的打上水印,麻烦一点横向纵向多大点,然后再通过程序拷贝该模版 Sheet,想其中输出内容的形式不行吗?很遗憾,不行,翻查 POI 的 API 你就会发现创建 Sheet 的方式就那么两个,即通过 workbook 进行创建,虽然 sheet(无论类型)的构造函数是 public 的,但是你无法将你构造好的 sheet 添加到 workbook 中。当然,workbook 也提供了 cloneSheet 的 api,但是如果你的模版水印是通过艺术字或者图片的形式打上去的,那么也将无法拷贝。
所以这种方案,适合于输出的内容 Sheet 页个数固定,行数可控的形式,因为只有这些固定下来了,在提供模版 Excel 的时候才能做出合适的模版,在固定的 sheet 页,大致的行上打上水印。
二:使用程序将水印图片动态打到 Sheet 上:
因为上面方案的问题,我选择通过程序将提前准备好的水印图片打印到 Sheet 上的方案,这种方案的主要步骤为:
1:将需要输出的内容正常输出到 Excel 中,输出的 Excel 假定将存在多个 Sheet,每个 Sheet 中内容的行数也不一定;
2:获取 workbook 对象,提取其中的多个 sheet;
3:调用工具类,传入 workbook 和每一个 sheet,根据提供好的水印图片将睡衣图片打到每一个 sheet 上,水印图片在 sheet 上的位置,个数等可配置;
要求:
1:水印图片是 png 格式,无背景,且水印主体颜色要尽可能浅,且细,避免遮挡 excel 上的信息;
2:使用的 POI 的版本是 3.9,pom 文件配置如下:
- <!-- poi -->
- <dependency>
- <groupId>
- org.apache.poi
- </groupId>
- <artifactId>
- poi
- </artifactId>
- <version>
- 3.9
- </version>
- </dependency>
- <dependency>
- <groupId>
- org.apache.poi
- </groupId>
- <artifactId>
- poi-ooxml
- </artifactId>
- <version>
- 3.9
- </version>
- </dependency>
步骤 1 和步骤 2 不提供代码,网上有很多,直接贴步骤三的代码,如下:
- 1 import java.awt.image.BufferedImage;
- 2 import java.io.ByteArrayOutputStream;
- 3 import java.io.IOException;
- 4 import java.io.InputStream;
- 5 6 import javax.imageio.ImageIO;
- 7 8 import org.apache.poi.ss.usermodel.ClientAnchor;
- 9 import org.apache.poi.ss.usermodel.Drawing;
- 10 import org.apache.poi.ss.usermodel.Picture;
- 11 import org.apache.poi.ss.usermodel.Sheet;
- 12 import org.apache.poi.ss.usermodel.Workbook;
- 13 14
- /**
- 15 * Excel水印工具类
- 16 * @author gang.wang
- 17 * 2017年4月23日
- 18 */
- 19 public class ExcelWaterRemarkUtils {
- 20 21
- /**
- 22 * 为Excel打上水印工具函数
- 23 * 请自行确保参数值,以保证水印图片之间不会覆盖。
- 24 * 在计算水印的位置的时候,并没有考虑到单元格合并的情况,请注意
- 25 * @param wb Excel Workbook
- 26 * @param sheet 需要打水印的Excel
- 27 * @param waterRemarkPath 水印地址,classPath,目前只支持png格式的图片,
- 28 * 因为非png格式的图片打到Excel上后可能会有图片变红的问题,且不容易做出透明效果。
- 29 * 同时请注意传入的地址格式,应该为类似:"\\excelTemplate\\test.png"
- 30 * @param startXCol 水印起始列
- 31 * @param startYRow 水印起始行
- 32 * @param betweenXCol 水印横向之间间隔多少列
- 33 * @param betweenYRow 水印纵向之间间隔多少行
- 34 * @param XCount 横向共有水印多少个
- 35 * @param YCount 纵向共有水印多少个
- 36 * @param waterRemarkWidth 水印图片宽度为多少列
- 37 * @param waterRemarkHeight 水印图片高度为多少行
- 38 * @throws IOException
- 39 */
- 40 public static void putWaterRemarkToExcel(Workbook wb, Sheet sheet, String waterRemarkPath, int startXCol, int startYRow, 41 int betweenXCol, int betweenYRow, int XCount, int YCount, 42 int waterRemarkWidth, int waterRemarkHeight) throws IOException {
- 43 44 //校验传入的水印图片格式
- 45
- if (!waterRemarkPath.endsWith("png") && !waterRemarkPath.endsWith("PNG")) {
- 46
- throw new RuntimeException("向Excel上面打印水印,目前支持png格式的图片。");
- 47
- }
- 48 49 //加载图片
- 50 ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
- 51 InputStream imageIn = Thread.currentThread().getContextClassLoader().getResourceAsStream(waterRemarkPath);
- 52
- if (null == imageIn || imageIn.available() < 1) {
- 53
- throw new RuntimeException("向Excel上面打印水印,读取水印图片失败(1)。");
- 54
- }
- 55 BufferedImage bufferImg = ImageIO.read(imageIn);
- 56
- if (null == bufferImg) {
- 57
- throw new RuntimeException("向Excel上面打印水印,读取水印图片失败(2)。");
- 58
- }
- 59 ImageIO.write(bufferImg, "png", byteArrayOut);
- 60 61 //开始打水印
- 62 Drawing drawing = sheet.createDrawingPatriarch();
- 63 64 //按照共需打印多少行水印进行循环
- 65
- for (int yCount = 0; yCount) {
- 66 //按照每行需要打印多少个水印进行循环
- 67
- for (int xCount = 0; xCount) {
- 68 //创建水印图片位置
- 69 int xIndexInteger = startXCol + (xCount * waterRemarkWidth) + (xCount * betweenXCol);
- 70 int yIndexInteger = startYRow + (yCount * waterRemarkHeight) + (yCount * betweenYRow);
- 71 72
- /*
- 73 * 参数定义:
- 74 * 第一个参数是(x轴的开始节点);
- 75 * 第二个参数是(是y轴的开始节点);
- 76 * 第三个参数是(是x轴的结束节点);
- 77 * 第四个参数是(是y轴的结束节点);
- 78 * 第五个参数是(是从Excel的第几列开始插入图片,从0开始计数);
- 79 * 第六个参数是(是从excel的第几行开始插入图片,从0开始计数);
- 80 * 第七个参数是(图片宽度,共多少列);
- 81 * 第8个参数是(图片高度,共多少行);
- 82 */
- 83 ClientAnchor anchor = drawing.createAnchor(0, 0, Short.MAX_VALUE, Integer.MAX_VALUE, xIndexInteger, yIndexInteger, waterRemarkWidth, waterRemarkHeight);
- 84 Picture pic = drawing.createPicture(anchor, wb.addPicture(byteArrayOut.toByteArray(), Workbook.PICTURE_TYPE_PNG));
- 85 pic.resize();
- 86
- }
- 87
- }
- 88
- }
- 89
- }
函数参数的定义介绍的很详细了,不再过多介绍,说说需要注意的地方:
1:一定要先向 Excel 中写内容,然后再打日志,要不然图片有可能会拉伸导致失真;
2:最好使用 Png 格式的图片,避免造成打上的图片背景变红的问题,网上有这种问题的解决方案,这里不再赘述;
3:该函数默认是将图片放在了项目的 classpath 目录下;
至此,使用 poi 向 Excel 上面打水印的功能就完成了!!!可能会有写遮挡,但也实在无法避免了,目前还没有发现类似于设置图片透明度,文字环绕效果,图片置底的 api~~
接下来再说两个小点:
1:怎样实现调整列宽:
Sheet 提供了默认的自动设置列宽的 api,即根据 cell 中内容的宽度,自动调整列宽(sheet.autoSizeColumnt),但是经过测试,在 SXSSFWorkbook 和 SXSSFSheet 中,会遇到个别列特别窄的问题,就是计算的行宽不太准,而且遇到了公式,数字也有可能计算的不太准,因此我是自己计算的列宽,并设置的。
首先在程序循环向 Excel 中写内容的时候,自行设置一个 Map 存储每一列的最大列宽,具体的函数很简单,就是一个比较大小的函数,举例如下:
- 1 private void getMaxColLength(Map colMaxLength, Integer colIndex, Integer length) {
- 2 Integer oriLength = 0;
- 3
- if (colMaxLength.containsKey(colIndex)) {
- 4 oriLength = colMaxLength.get(colIndex);
- 5
- }
- 6 7
- if (length > oriLength) {
- 8 colMaxLength.put(colIndex, length);
- 9
- } else {
- 10 colMaxLength.put(colIndex, oriLength);
- 11
- }
- 12
- }
得到的每一列的最大宽度(调用字符串的 length() 函数),需要和 Excel 中的宽度进行换算,换算的公式是宽度 * 256,即程序求得的每一列的宽度乘以 256,在实际使用中,大多数情况需要多乘以一些,因为实测中,如果只乘以 256,会使得列正正好好的匹配内容宽度,遇到汉字还有可能遮挡一部分,因此最好将这个基数扩大些:
- sheet.setColumnWidth(colIndex, length * 480);
来源: http://www.cnblogs.com/wanggangblog/p/6767481.html