easyexcel不建立對象導出圖片

前些天項目中要使用easyexcel,研究了一下,須要導出圖片java

官方文檔:https://www.yuque.com/easyexcel/doc/write#cb1b271f數據庫

官方的文檔中,導出圖片的樣例很簡單,是經過對象來導出的,並支持五種類型,File,InputStream,byte[],URL,Stringapache

 

可是在項目中,不少時候,咱們導出的excel列多是不固定的,咱們沒法經過對象來導出,因此須要經過不建立對象的方式來導出ide

 

一.導出單個圖片測試

導出單個圖片很簡單,即返回的數據中設置一下對應的類型,File,InputStream,byte[],URL均可以ui

@Test
    public void noModelWrite() {
        // 寫法1
        String fileName = TestFileUtil.getPath() + "noModelWrite" + System.currentTimeMillis() + ".xlsx";
        // 這裏 須要指定寫用哪一個class去寫,而後寫到第一個sheet,名字爲模板 而後文件流會自動關閉
        EasyExcel.write(fileName).head(head()).sheet("sheet1").doWrite(dataList());
    }

//動態生成列頭
private List<List<String>> head() {
        List<List<String>> list = new ArrayList<>();
        for (int i = 0; i <10 ; i++) {
            List<String> head0 = new ArrayList<>();
            head0.add("標題"+i);
            list.add(head0);
        }
        return list;
    }

private List<List<Object>> data(){
        List<List<Object>> datas = new ArrayList<>();
        for (int i = 0; i <10 ; i++) {
            List<Object> list = new ArrayList<>();
            for (int j = 0; j <10 ; j++) {
                if((i==1&&j==5)||(i==2&&j==0)||(i==3&&j==7)){
                        URL image = null;
                        try {
                            image = new URL("https://dss3.baidu.com/-rVXeDTa2gU2pMbgoY3K/it/u=1542006958,16092133&fm=202&src=903&pairwise&mola=new&crop=v1");
                        } catch (MalformedURLException e) {
                            e.printStackTrace();
                        }
            //這裏能夠根據需求傳入File,InputStream,byte[],URL各類類型,若是要傳String類型的話,就須要考慮和普通字符串的區分了 list.add(images); }
else { list.add("內容"+i+"-"+j); } } datas.add(list); } return datas; }

二 導出多個圖片到一個單元格中this

對比獲取數據的方法,須要返回一個List<List<Object>>類型的數據,第一個list好理解,是行,第二個list對應的就是列數據,也就是說,導出的excel每一個單元格的數據是一個Object對象,因此天然想到,我往這個Object對象裏塞一個List<URL>這樣的對象是否是就好了呢,傳入以後一運行,發現報錯了,緣由是找不到對應的Converter ,既然找不到,那就建立一個converter就是咯,因而就有了以下的實驗:url

package cn.weaver.ebuilder.teams.excel.style;

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.util.IoUtils;

import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * @author niexh
 * @description
 * @date 2021/1/18 14:01
 */
public class ArrayListURLConverter implements Converter<ArrayList> {


    @Override
    public Class supportJavaTypeKey() {
        return ArrayList.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.IMAGE;
    }

    @Override
    public ArrayList convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        return null;
    }

    @Override
    public CellData convertToExcelData(ArrayList value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        List<CellData> list  = new ArrayList<>();
        for (Object o : value) {
            if(o instanceof URL){
                URL url = (URL) o;
                InputStream inputStream = null;
                try {
                    inputStream = url.openStream();
                    byte[] bytes = IoUtils.toByteArray(inputStream);
                    list.add(new CellData(bytes));
                } finally {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                }
            }
        }
        CellData cellData = new CellData(list);
        cellData.setType(CellDataTypeEnum.IMAGE);
        return cellData ;
    }
}

可是注意標紅的地方,咱們會發現這個convertToExcelData的實現方法,返回值是一個CellData,它並不能識別並返回一個List<CellData>對象,實際上咱們看CellWriteHandler接口的實現方法afterCellDispose中,是有一個參數是List<CellData> cellDataList,spa

官方問題處理樣例:https://www.yuque.com/easyexcel/faq/wpedtd上給的註釋是 :.net

// cellDataList 是list的緣由是 填充的狀況下 可能會多個寫到一個單元格 可是若是普通寫入 必定只有一個

因此那個參數多是填充狀況纔有的,找了半天,這種方式並不能返回一個List<CellData> cellDataList,因此只好經過CellData cellData = new CellData(list);將這個list對象塞到返回值CellData對象的data屬性中,下面再用

爲了調整圖片大小位置等,通常會實現CellWriteHandler 的afterCellDataConverted 方法,將 CellData 的 type 設置成 EMPTY ,這樣 easyexcel 不會幫忙填充該單元格的數據,而後實現 CellWriteHandler 的 afterCellDispose 方法,將將圖片信息填充上去。

package cn.weaver.ebuilder.teams.excel.style;

import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
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.*;
import org.apache.poi.util.Units;

import java.util.ArrayList;
import java.util.List;

/**
 * @author niexh
 * @description
 * @date 2021/1/4 18:27
 */
public class CustomImageCellWriteHandler implements CellWriteHandler {
    private List<String> repeats = new ArrayList<>();
    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {

    }

    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {

    }

    @Override
    public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        if (isHead) {
            return;
        }
     //這裏能夠自由寫,將要插入圖片的單元格的type設置爲空,下面再填充圖片
if(cellData.getImageValue()!=null||cellData.getData() instanceof ArrayList){ cellData.setType(CellDataTypeEnum.EMPTY); } } @Override public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { Sheet sheet = cell.getSheet(); if (isHead||cellDataList==null) { return; } boolean islist= false; ArrayList datas = null;
     //這就是上面ArrayListURLConverter的返回值,被塞到了cellDataList.get(0).getData中
if(cellDataList.get(0).getData() instanceof ArrayList){ datas = (ArrayList) cellDataList.get(0).getData(); if(datas.get(0) instanceof CellData){ CellData c= (CellData) datas.get(0); if(c.getImageValue()==null){ return; }else{ islist=true; } } } if(!islist&&cellDataList.get(0).getImageValue()==null){ return; } String key = cell.getRowIndex()+"_"+cell.getColumnIndex(); if (repeats.contains(key)){//afterCellDispose好像會被重複調用 return; } repeats.add(key);
     //這裏默認要導出的圖片大小爲60*60px,60px的行高大約是900,60px列寬大概是248*8,這個能夠根據需求來調 sheet.getRow(cell.getRowIndex()).setHeight((
short) 900); sheet.setColumnWidth(cell.getColumnIndex(),islist?240*8*datas.size():240*8); if(islist){ for (int i = 0; i < datas.size(); i++) { CellData cellData= (CellData) datas.get(i); if(cellData.getImageValue()==null){ continue; } this.insertImage(sheet,cell,cellData.getImageValue(),i); } }else{ // cellDataList 是list的緣由是 填充的狀況下 可能會多個寫到一個單元格 可是若是普通寫入 必定只有一個 this.insertImage(sheet,cell,cellDataList.get(0).getImageValue(),0); } } private void insertImage(Sheet sheet,Cell cell,byte[] pictureData,int i){ int picWidth = Units.pixelToEMU(60); int index = sheet.getWorkbook().addPicture(pictureData, HSSFWorkbook.PICTURE_TYPE_PNG); Drawing drawing = sheet.getDrawingPatriarch(); if (drawing == null) { drawing = sheet.createDrawingPatriarch(); } CreationHelper helper = sheet.getWorkbook().getCreationHelper(); ClientAnchor anchor = helper.createClientAnchor(); // 設置圖片座標 anchor.setDx1(picWidth*i); anchor.setDx2(picWidth+picWidth*i); anchor.setDy1(0); anchor.setDy2(0); //設置圖片位置 anchor.setCol1(cell.getColumnIndex()); anchor.setCol2(cell.getColumnIndex()); anchor.setRow1(cell.getRowIndex()); anchor.setRow2(cell.getRowIndex() + 1); // 設置圖片能夠隨着單元格移動 anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE); drawing.createPicture(anchor, index); } }

下面是測試方法:  

 @Test    
public void simpleWrite() {       
EasyExcelUtil easyExcelUtil =new EasyExcelUtil();

// 寫法1 String fileName = "C:\\Users\\Administrator\\Desktop"+File.separator + "demo2.xlsx"; // 這裏 須要指定寫用哪一個class去寫,而後寫到第一個sheet,名字爲模板 而後文件流會自動關閉 // 若是這裏想使用03 則 傳入excelType參數便可     // easyExcelUtil.formExportBuild(EasyExcel.write(fileName),getHeadStyles(),getContentStyles()).head(head()).     // sheet("sheet1").doWrite(data()); ExcelWriter writer = null; try {
       //這塊是本身封裝的一個簡單樣式設置,能夠不用管,直接按官方文檔上的來 EasyExcel.write(fileName); writer
=easyExcelUtil.formExportBuild(EasyExcel.write(fileName),getHeadStyles(), getContentStyles())
            //將本身的寫的轉化器和攔截器註冊進去
            .registerWriteHandler(
new CustomImageCellWriteHandler()).registerConverter(new ArrayListURLConverter()).build(); for (int i = 0; i < 5; i++) { // 每次都要建立writeSheet 這裏注意必須指定sheetNo 並且sheetName必須不同 WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).head(head()).build(); for (int j = 0; j <1 ; j++) { // 分頁去數據庫查詢數據 這裏能夠去數據庫查詢每一頁的數據 writer.write(data(), writeSheet); } } }catch (Exception e){ e.printStackTrace(); }finally { // 千萬別忘記finish 會幫忙關閉流 if (writer != null) { writer.finish(); } } }


 
private List<List<Object>> data(){
List<List<Object>> datas = new ArrayList<>();
for (int i = 0; i <10 ; i++) {
List<Object> list = new ArrayList<>();
for (int j = 0; j <10 ; j++) {
if((i==1&&j==5)||(i==2&&j==0)||(i==3&&j==7)){
List<URL> images = new ArrayList<>();
for (int k = 0; k <i ; k++) {
URL image = null;
try {
image = new URL("https://dss3.baidu.com/-rVXeDTa2gU2pMbgoY3K/it/u=1542006958,16092133&fm=202&src=903&pairwise&mola=new&crop=v1");
images.add(image);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
list.add(images);
}else {
list.add("內容"+i+"-"+j);
}
}
datas.add(list);
}
return datas;
}
 

這樣就能夠導出以下的excel

 

 

實際上,即便不重寫Converter也是能夠導出多張圖片到一個單元格中的,在獲取data()方法中,咱們設置圖片數據時,依然設置成字符串,好比數據庫中存的fileid,或者圖片地址等等,多個的話用逗號或者特定的符號來分隔,下面存在的問題就是,如何區分普通的字符串和這種圖片類型的字符串,這個能夠根據業務邏輯來本身設置,提供一種:

經過傳入參數來指定圖片所在的列

package cn.weaver.ebuilder.teams.excel.style;

import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.util.IoUtils;
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.*;
import org.apache.poi.util.Units;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * @author niexh
 * @description
 * @description 泛微協同商務系統, 版權全部.
 * @date 2021/1/4 18:27
 */
public class CustomImageCellWriteHandler implements CellWriteHandler {
    /**
     * 圖片字段在excel中的列索引
     */
    private List<Integer> imageColumnIndexs = new ArrayList<>();

    public CustomImageCellWriteHandler(List<Integer> imageColumnIndexs) {
        this.imageColumnIndexs = imageColumnIndexs;
    }

    private List<String> repeats = new ArrayList<>();
    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {

    }

    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {

    }

    @Override
    public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        if (isHead) {
            return;
        }
        if(imageColumnIndexs.contains(cell.getColumnIndex())){
            cellData.setType(CellDataTypeEnum.EMPTY);
        }
    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        Sheet sheet = cell.getSheet();
        if (isHead||cellDataList==null) {
            return;
        }
        if(!imageColumnIndexs.contains(cell.getColumnIndex())){
            return;
        }
        String key = cell.getRowIndex()+"_"+cell.getColumnIndex();
        if (repeats.contains(key)){//afterCellDispose好像會被重複調用
            return;
        }
        repeats.add(key);
        CellData cellData = cellDataList.get(0);
        String fieldids = cellData.getStringValue();
        if("".equals(fieldids)){
            return;
        }
        String[] fieldidArr = fieldids.split(",");
        sheet.getRow(cell.getRowIndex()).setHeight((short) 900);
        sheet.setColumnWidth(cell.getColumnIndex(),240*8*fieldidArr.length);
        for (int i = 0; i < fieldidArr.length; i++) {
            this.insertImage(sheet,cell,fieldidArr[i],i);
        }
    }

    private void insertImage(Sheet sheet,Cell cell,String fieldid,int i){
        //導出每張圖片要求固定大小60*60px,因此這裏偏移量也是60px
        int picWidth = Units.pixelToEMU(60);
        int index = sheet.getWorkbook().addPicture(getImage(fieldid), HSSFWorkbook.PICTURE_TYPE_PNG);
        Drawing drawing = sheet.getDrawingPatriarch();
        if (drawing == null) {
            drawing = sheet.createDrawingPatriarch();
        }
        CreationHelper helper = sheet.getWorkbook().getCreationHelper();
        ClientAnchor anchor = helper.createClientAnchor();
        // 設置圖片座標
        anchor.setDx1(picWidth*i);
        anchor.setDx2(picWidth+picWidth*i);
        anchor.setDy1(0);
        anchor.setDy2(0);
        //設置圖片位置
        anchor.setCol1(cell.getColumnIndex());
        anchor.setCol2(cell.getColumnIndex());
        anchor.setRow1(cell.getRowIndex());
        anchor.setRow2(cell.getRowIndex() + 1);
        // 設置圖片能夠隨着單元格移動
        anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE);
        drawing.createPicture(anchor, index);
    }

    private byte[] getImage(String fieldid){
        InputStream inputStream = null;
        byte[] bytes=null;
        try {
            //測試數據,隨便寫死的的,實際業務中能夠根據fieldid或者是圖片地址去取等等...
            URL url =new URL("https://dss3.baidu.com/-rVXeDTa2gU2pMbgoY3K/it/u=1542006958,16092133&fm=202&src=903&pairwise&mola=new&crop=v1");
            inputStream = url.openStream();
            bytes= IoUtils.toByteArray(inputStream);
            return bytes;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return bytes;
    }
}

而後在調用的時候傳入imageColumnIndexs便可

 
 
EasyExcelUtil easyExcelUtil =new EasyExcelUtil();
// 寫法1
String fileName = "C:\\Users\\Administrator\\Desktop"+File.separator + "demo.xlsx";
List<Integer> imageColumnIndexs = new ArrayList<>();
        imageColumnIndexs.add(0);
        imageColumnIndexs.add(4);
        imageColumnIndexs.add(8);
        easyExcelUtil.formTemplateBuild(EasyExcel.write(fileName),getHeadStyles())
                .registerWriteHandler(new CustomImageCellWriteHandler(imageColumnIndexs)).head(head()).
//                registerWriteHandler(new CommentWriteHandler()).
        sheet("sheet1").doWrite(getImageData());

導出以下:

 

 

 實際上,只要掌握了方法,具體的寫法就能夠本身發揮了,根據實際的業務邏輯來定

友情提示,注意CellWriteHandler,SheetWriteHandler等WriteHandler接口,裏面是能夠取到原生的poi對象的,因此能夠在裏面作不少自由發揮,好比說要實現導出excel,首行凍結,就能夠這麼作:

package cn.weaver.ebuilder.teams.excel.style;

import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;

/**
 * @author niexh
 * @description
 * @date 2021/1/5 13:15
 */
public class CustomSheetWriteHandler implements SheetWriteHandler {
    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

    }

    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        //首行凍結
        writeSheetHolder.getSheet().createFreezePane( 0, 1, 0, 1 );
    }
}
相關文章
相關標籤/搜索