前言
實現一個類似於微信的圖片界面,包括拍照和相冊,拍照包括裁剪,相冊包括預覽,可以選中指定張數的圖片,將圖片轉換爲base64上傳到服務器。可以從服務器將已經上傳的圖片資源,通過base64字符串下載,然後將base64轉換爲圖片,在界面顯示,具體效果圖:
這個類似於微信的效果,是我在網上找了個demo,這篇文章主要是談base64和圖片轉換遇到的問題,實現效果可以自行下載這個demo,仿微信選擇圖片demo,(不知道爲什麼,現在csdn上傳資源,不能設置分數,直接默認5積分,本來想免費分享的,因爲這個demo是別人的成果。。。)可以自行修改需要的樣式。涉及到圖片上傳,就會涉及到圖片壓縮,網上現在流行的幾種壓縮庫,本來使用的是Compressor,發現壓縮.png文件會報錯,問題可以查看這個ExifInterface got an unsupported image,最後用了LuBan,Curzibn/Luban
實現
首先,根據以上demo,可以對照片進行處理,相冊可以預覽,拍照可以裁剪,現在就進行對圖片處理
先貼幾個對圖片進行處理的方法
/**
* 將文件中的數據讀取到Byte數組中
*
* @param file
* @return
*/
public static byte[] readRealFileToByte(File file)
{
Long filelength = file.length(); // 獲取文件長度
byte[] filecontent = new byte[filelength.intValue()];
try
{
FileInputStream in = new FileInputStream(file);
in.read(filecontent);
in.close();
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
return filecontent; // 返回文件內容,默認編碼
}
public static String getEncoded64ImageStringFromByte(byte[] bytes)
{
if (bytes == null || bytes.length == 0)
return null;
String imgString = Base64.encodeToString(bytes, Base64.DEFAULT);
return imgString;
}
/**
* base64字符串轉化成圖片
*/
public static String GenerateImage(String base64Code, String savePathKey)
{
//對字節數組字符串進行Base64解碼並生成圖片
if (base64Code == null)
{ //圖像數據爲空
return "";
}
try
{
byte[] buffer = Base64.decode(base64Code, Base64.DEFAULT);
// 新生成的jpg圖片
// 新圖片的文件夾, 如果沒有, 就創建
File fileDir = new File(dirPath);
if (!fileDir.exists())
{
fileDir.mkdirs();
}
// 文件夾現在存在了, 可以在此文件夾下創建圖片了
String imgFilePath = dirPath + savePathKey + ".jpg";
File file = new File(imgFilePath);
if (!file.exists())
{
file.createNewFile();
}
OutputStream out = new FileOutputStream(imgFilePath);
out.write(buffer);
out.flush();
out.close();
return imgFilePath;
}
catch (Exception e)
{
return "";
}
}
/**
* 刪除指定目錄下文件及目錄
*/
public static void deleteFolderFile(String filePath)
{
if (!TextUtils.isEmpty(filePath))
{
try
{
File file = new File(filePath);
if (file.isDirectory())
{// 處理目錄
File files[] = file.listFiles();
for (int i = 0; i < files.length; i++)
{
deleteFolderFile(files[i].getAbsolutePath());
}
}
if (!file.isDirectory())
{// 如果是文件,刪除
file.delete();
}
else
{// 目錄
if (file.listFiles().length == 0)
{// 目錄下沒有文件或者目錄,刪除
file.delete();
}
}
}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
圖片上傳
首先,對已經選擇的圖片,進行壓縮,然後轉換爲base64,調用接口上傳,具體代碼
private ArrayList<ImageItem> selImageList; //當前選擇的所有圖片
......
private void processSubmit()
{
showProgressDialog("請稍候", null, null);
//selImageList已選中圖片列表
if (selImageList.size() > 0)
{
List<File> selImageFileList = new ArrayList<>();
ArrayList<String> base64List = new ArrayList<>();
for (ImageItem imageItem : selImageList)
{
try
{
File imageFile = new File(imageItem.path);
selImageFileList.add(imageFile);
}
catch (Exception e)
{
e.printStackTrace();
}
}
Luban.with(this).load(selImageFileList).ignoreBy(100).setCompressListener(new OnCompressListener()
{
@Override
public void onStart()
{
// TODO 壓縮開始前調用,可以在方法內啓動 loading UI
}
@Override
public void onSuccess(File file)
{
// TODO 壓縮成功後調用,返回壓縮後的圖片文件
byte[] bytes = ImageUtil.readRealFileToByte(file);
Log.i("MyImageTest", "After Compress bytes.length= " + ((float) bytes.length / 1024 / 1024) + "MB");
String base64 = ImageUtil.getEncoded64ImageStringFromByte(bytes);
base64List.add(base64);
if (base64List.size() == selImageList.size())
{
uploadImg(base64List);
}
}
@Override
public void onError(Throwable e)
{
// TODO 當壓縮過程出現問題時調用
dismissProgressDialog();
ToastUtil.showShortToast("圖片轉換失敗");
}
}).launch();
}
else
{
uploadImg(null);
}
}
上傳很簡單,選擇圖片–>壓縮–>轉成base64–>調用接口上傳
下載圖片
下載圖片的流程:調用接口–>獲取base64–>轉換爲圖片–>進行顯示
首先,爲了配合上邊說的仿微信的demo,儘量少改動,我這邊是將base64轉成的圖片,保存到sd卡中的一個文件夾下,然後在上傳圖片結束以後,將該文件夾刪除。保存到本地以後,就可以跟其他本地圖片一樣操作了。
踩坑
重點重點:就這個把base64轉成圖片,保存到本地,然後顯示的這樣一個功能,有一個很大的坑,因爲demo裏邊顯示圖片用的是Glide,我保存圖片是保存到sd卡,所以,坑就是:如果我下載下來的圖片,命名爲1.jpg,然後更改過圖片,重新上傳,然後重新下載,保存到本地,也是被命名爲1.jpg,本地sd卡中的圖片已經更改了,但是,Glide的緩存機制,
具體參看Glide v4文檔
所以,Glide會先去緩存中獲取圖片,現在緩存中是有1.jpg的,所以根本不會去sd卡里獲取,所以顯示的還是老圖片,這樣就是一個問題,因爲其他功能也在使用Glide,所以不敢修改Glide的緩存配置,現在的方法就是,將每次下載的base64轉換成的圖片的命名,設置爲唯一的,這樣內存中就不會有相同姓名的圖片,只能到sd卡里讀取,我這邊命名是根據時間加索引,本來只是時間,結果有可能同一秒內可以處理多張圖片,也會出現問題,所以加上索引就是唯一了
下載圖片實現
if (data.getRstData().size() > 0)
{
//圖片實體類
uploadImgBean = data.getRstData().get(0);
//多個圖片通過§字符分割
if (uploadImgBean.getYWIMG().contains("§"))
{
String[] base64s = uploadImgBean.getYWIMG().split("§");
for (int i = 0; i < base64s.length; i++)
{
//將接口獲取的圖片臨時保存到本地,命名必須唯一,如果緩存中有相同名稱的圖片,會優先獲取緩存中的,不會度本地sd中的,容易造成顯示不正確的bug
ImageItem imageItem = new ImageItem();
imageItem.path = ImageUtil.GenerateImage(base64s[i], DateUtil.getNowDateHHmmssString() + i);
selImageList.add(imageItem);
}
}
else
{
if(!uploadImgBean.getYWIMG().equals("")){
ImageItem imageItem = new ImageItem();
imageItem.path = ImageUtil.GenerateImage(uploadImgBean.getYWIMG(), DateUtil.getNowDateHHmmssString() + "10");
selImageList.add(imageItem);
}
}
adapter.setImages(selImageList);
// adapter.notifyDataSetChanged();
isButtonCanClick(false);
dismissProgressDialog();
@Override
protected void onDestroy()
{
super.onDestroy();
try
{
ImageUtil.deleteFolderFile(ImageUtil.dirPath);
}
catch (Exception e)
{
e.printStackTrace();
}
}
/**
* 獲得當前時間
* <p/>
* 格式:10:38:53
*
* @return
*/
public static String getNowDateHHmmssString()
{
SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
Date currentTime = new Date();
String dateString = formatter.format(currentTime);
return dateString;
}