Android開發基礎 呼叫相機 系統相簿(並對圖片進行壓縮處理)
前言:做了好久的安卓開發了,一直想寫點東西分享下。但是又總覺得自己學的還不夠好,說出來有可能會誤導人,所以一直都沒有發. 最近在專案中遇到了最多的問題就是關於圖片的問題,應該算是比較簡單的了,拿出來跟大家分享下。(第一次寫部落格,希望給位大神能夠多提意見^_^)
最近做的專案需要上傳手機相簿的圖片或者通過相機拍照上傳,但是以前從網上找的方法需要在拍照或選擇完圖片後再進行擷取,不太符合現在的需求,於是從網上找了一些方法,但是莫名其妙的出了各種問題,於是各種百度,從網上找了一些方法,拿出來分享下,並方便自己以後的使用。
開啟相機
/**
* 開啟相機
* @param actionCode 請求碼
* /
private void getImageFromCamera(int actionCode) {
//這種方法是我們最常見的方法,但是這種方法獲取到的圖片時是進行壓縮後的圖片,有可能不是我們想要的
//可能壓縮後的檔案會非常模糊
Intent getImageByCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(getImageByCamera , actionCode);
}
上面的那種方法可能獲得的壓縮圖片不是我們想要,可能我們會想要高清大圖,這時我們可以使用下面的方法,在
Intent getImageByCamera = new Intent(“android.media.action.IMAGE_CAPTURE”);
之後我們直接將檔案先儲存到指定的路徑filepath,然後直接在
onActivityResult(int requestCode, int resultCode, Intent data)
中把filepath傳遞過去就行了。方法如下
//定義一個成員變數,用於儲存相機拍照的圖片
private String capturePath = null;
/**
* 開啟相機
* @param actionCode 請求碼
*/
protected void getImageFromCamera(int actionCode) {
String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_MOUNTED)) {
Intent getImageByCamera = new Intent("android.media.action.IMAGE_CAPTURE");
//獲取儲存的路徑
String out_file_path = getSDPath();
File dir = new File(out_file_path);
//給成員變數賦值
capturePath = getSDPath() + "/" + System.currentTimeMillis() + ".jpg";
if (!dir.exists()) {
dir.mkdirs();
}
getImageByCamera.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(capturePath)));
getImageByCamera.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
startActivityForResult(getImageByCamera, actionCode);
} else {
Toast.makeText(getApplicationContext(), "請確認已經插入SD卡", Toast.LENGTH_LONG).show();
}
}
/**
* 獲取儲存路徑,可以寫在FileUtils中
*/
public String getSDPath() {
File sdDir = null;
boolean sdCardExist = Environment.getExternalStorageState()
.equals(android.os.Environment.MEDIA_MOUNTED);//判斷sd卡是否存在
if (sdCardExist) {
sdDir = Environment.getExternalStorageDirectory();//獲取跟目錄
}
return sdDir.toString();
}
開啟相簿
/**
* 獲取相簿的圖片
*/
protected void getImageFromAlbum(int actionCode) {
//就是利用Intent呼叫系統的相簿
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");//相片型別
startActivityForResult(intent, actionCode);
}
好了做好以上的工作以後就可以選擇圖片或者拍攝圖片了,當確認好了以後就會再次跳到當前的頁面來,這時我們在onActivityResult()方法中進行資料的處理就行了。
接收從相簿或相機中返回的資料
1.接收從相機中返回的資料
<1>當開啟相機使用的方法為簡單的Intent直接呼叫時,我們在onActivityResult()方法中需要對返回的Intent進行判斷處理。原因是因為android把拍攝的圖片封裝到bundle中傳遞回來,但是根據不同的機器獲得相片的方式不太一樣,可能有的相機能夠通過 inten.getData()獲取到uri然後再根據uri獲取資料的路徑,在封裝成bitmap,但有時候有的相機獲取到的是null的,這時候我們該怎麼辦呢?其實這時候我們就應該從bundle中獲取資料,通過(Bitmap) bundle.get(“data”)獲取到相機圖片的bitmap資料。
為了能夠同時適應上述兩種情況,我們這時候就應該在獲取圖片時做判斷了。我們可以在響應的時候做一個判斷:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_PICK_IMAGE) {
Uri uri = data.getData();
//to do find the path of pic by uri
} else if (requestCode == REQUEST_CODE_CAPTURE_CAMEIA ) {
Uri uri = data.getData();
if(uri == null){
//use bundle to get data
Bundle bundle = data.getExtras();
if (bundle != null) {
//可以通過獲取到的這個Bitmap物件設定到ImageView控制元件中,讓他顯示出來
Bitmap photo = (Bitmap) bundle.get("data"); //get bitmap
//spath :生成圖片取個名字和路徑包含型別
saveImage(photo, spath);
} else {
Toast.makeText(getApplicationContext(), "err****", Toast.LENGTH_LONG).show();
return;
}
}else{
//to do find the path of pic by uri
}
}
}
public static boolean saveImage(Bitmap photo, String spath) {
try {
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(spath, false));
photo.compress(Bitmap.CompressFormat.JPEG, 100, bos);
bos.flush();
bos.close();
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
//以上方法引用的是網上的一段程式碼
<2>好了說完了直接簡單的使用Intent方法呼叫相機,就該說說我們的重頭戲了,那就是不要縮高清無碼的大圖了^_^。
這個時候在onActivityResult方法中就不需要進行對Intent進行判斷處理了,只需將獲取到的Bitmap物件進行處理,因為當前的地址肯定不會是空的。廢話不多說,直接上程式碼。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Bitmap bm = null;
if (requestCode == REQUEST_CODE_PICK_IMAGE) {
} else if (requestCode == REQUEST_CODE_CAPTURE_CAMEIA) {
//通過圖片路徑直接獲取Bitmap物件(注意這個時候的Bitmap物件有可能會很大需要進行壓縮處理)
bm = getBitmap(capturePath);
//對Bitmap物件進行壓縮處理
bm = BitmapCompressUtils.imageZoom(bm, 310.00);
if (bm==null){
//設定預設的圖片
}else{
img.setImageBitmap(bm);
}
//將bitmap放至陣列中,意在bitmap的大小(與實際讀取的原檔案要大)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] b = baos.toByteArray();
//將位元組換成KB
double mid = b.length / 1024;
tv.setText("儲存路徑" + capturePath + "\n圖片大小" + mid);
}
}
/**
* 通過路徑獲取Bitmap物件
*
* @param path
* @return
*/
public static Bitmap getBitmap(String path) {
Bitmap bm = null;
InputStream is = null;
try {
File outFilePath = new File(path);
//判斷如果當前的檔案不存在時,建立該檔案一般不會不存在
if (!outFilePath.exists()) {
boolean flag = false;
try {
flag = outFilePath.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("---建立檔案結果----" + flag);
}
is = new FileInputStream(outFilePath);
bm = BitmapFactory.decodeStream(is);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return bm;
}
然後就是壓縮圖片的工具類
首先需要說明的是,對圖片壓縮處理的方法大體上分為三種,我現在用的是最耗時的一個不太建議使用。另外的幾種先介紹下,日後再附上程式碼。
第一種是BitmapFactory和BitmapFactory.Options。
首先,BitmapFactory.Options有幾個Fields很有用:
inJustDecodeBounds:If set to true, the decoder will return null (no bitmap), but the out…
也就是說,當inJustDecodeBounds設成true時,bitmap並不載入到記憶體,這樣效率很高哦。而這時,你可以獲得bitmap的高、寬等資訊。
outHeight:The resulting height of the bitmap, set independent of the state of inJustDecodeBounds.
outWidth:The resulting width of the bitmap, set independent of the state of inJustDecodeBounds.
看到了吧,上面3個變數是相關聯的哦。
inSampleSize : If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory.
這就是用來做縮放比的。這裡有個技巧:
inSampleSize=(outHeight/Height+outWidth/Width)/2
實踐證明,這樣縮放出來的圖片還是很好的。
最後用BitmapFactory.decodeFile(path, options)生成。
由於只是對bitmap載入到記憶體一次,所以效率比較高。解析速度快。
第二種是使用Bitmap加Matrix來縮放。
首先要獲得原bitmap,再從原bitmap的基礎上生成新圖片。這樣效率很低。
第三種是用2.2新加的類ThumbnailUtils來做。
讓我們新看看這個類,從API中來看,此類就三個靜態方法:createVideoThumbnail、extractThumbnail(Bitmap source, int width, int height, int options)、extractThumbnail(Bitmap source, int width, int height)。
是上面我們用到的BitmapFactory.Options和Matrix等經過人家一陣加工而成。效率好像比第二種方法高一點點。
/**
* Created by l_zp on 2016/1/20.
* 這是一個將圖片進行壓縮的工具類
*/
public class BitmapCompressUtils {
/**
* 壓縮圖片
*
* @param bitMap 要壓縮的bitmap物件
* @param maxSize 壓縮的大小(kb)不是很準確大約比輸入值大於100k是因為比例決定的
* @return
*/
public static Bitmap imageZoom(Bitmap bitMap, double maxSize) {
if (bitMap != null) {
//將bitmap放至陣列中,意在bitmap的大小(與實際讀取的原檔案要大)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitMap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] b = baos.toByteArray();
//將位元組換成KB
double mid = b.length / 1024;
//判斷bitmap佔用空間是否大於允許最大空間 如果大於則壓縮 小於則不壓縮
if (mid > maxSize) {
//獲取bitmap大小 是允許最大大小的多少倍
double i = mid / maxSize;
//開始壓縮 此處用到平方根 將寬頻和高度壓縮掉對應的平方根倍 (1.保持刻度和高度和原bitmap比率一致,壓縮後也達到了最大大小佔用空間的大小)
bitMap = zoomImage(bitMap, bitMap.getWidth() / Math.sqrt(i),
bitMap.getHeight() / Math.sqrt(i));
}
}
return bitMap;
}
/***
* 圖片的縮放方法
*
* @param bgimage :源圖片資源
* @param newWidth :縮放後寬度
* @param newHeight :縮放後高度
* @return
*/
public static Bitmap zoomImage(Bitmap bgimage, double newWidth,
double newHeight) {
// 獲取這個圖片的寬和高
float width = bgimage.getWidth();
float height = bgimage.getHeight();
// 建立操作圖片用的matrix物件
Matrix matrix = new Matrix();
// 計算寬高縮放率
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// 縮放圖片動作
matrix.postScale(scaleWidth, scaleHeight);
Bitmap bitmap = Bitmap.createBitmap(bgimage, 0, 0, (int) width,
(int) height, matrix, true);
return bitmap;
}
另外附上從Uri中獲取絕對路徑的工具類
/**
* Created by l_zp on 2016/3/10.
*
* 檔案工具類
*/
public class FileUtils {
/**
* 從Uri中獲取絕對路徑
*
* @param context
* @param uri
* @return the file path or null
*/
public static String getRealFilePath( final Context context, final Uri uri ) {
if ( null == uri ) return null;
final String scheme = uri.getScheme();
String data = null;
if ( scheme == null )
data = uri.getPath();
else if ( ContentResolver.SCHEME_FILE.equals( scheme ) ) {
data = uri.getPath();
} else if ( ContentResolver.SCHEME_CONTENT.equals( scheme ) ) {
Cursor cursor = context.getContentResolver().query( uri, new String[] { MediaStore.Images.ImageColumns.DATA }, null, null, null );
if ( null != cursor ) {
if ( cursor.moveToFirst() ) {
int index = cursor.getColumnIndex( MediaStore.Images.ImageColumns.DATA );
if ( index > -1 ) {
data = cursor.getString( index );
}
}
cursor.close();
}
}
return data;
}
}
2.獲取從相簿中返回的資料
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Bitmap bm = null;
if (requestCode == REQUEST_CODE_PICK_IMAGE) {
//這裡判斷data是否為空是為了防止使用者開啟完相簿後,沒有選擇就返回
if (data != null) {
//外界的程式訪問ContentProvider所提供資料 可以通過ContentResolver介面
Uri originalUri = data.getData();
//to do find the path of pic by uri
try {
//顯得到bitmap圖片這裡開始的第二部分,獲取圖片的路徑:
String path = FileUtils.getRealFilePath(MainActivity.this, originalUri);
bm = getBitmap(path);
System.out.println("----畫素密度值---" + bm.getDensity());
bm = BitmapCompressUtils.imageZoom(bm, 210.00);
img.setImageBitmap(bm);
//將bitmap放至陣列中,意在bitmap的大小(與實際讀取的原檔案要大)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] b = baos.toByteArray();
//將位元組換成KB
double mid = b.length / 1024;
tv.setText("儲存路徑" + path + "\n圖片大小" + mid);
} catch (Exception e) {
e.printStackTrace();
}
}
} else if (requestCode == REQUEST_CODE_CAPTURE_CAMEIA) {
}
}