Android對圖片常用(拍照,相簿,拖拉,壓縮上傳)操作
本篇部落格內容:
android app拍照功能
部分手機上拍照圖片旋轉問題
android相簿獲取圖片功能
檢視圖片,進行縮放,拖拉功能
壓縮圖片,上傳功能
Android 拍照功能:
一般的拍照功能是呼叫系統中相機來實現,即通過Intent來呼叫其他運用程式(相機運用程式)來實現。眾所周知,通過Intent呼叫手機上的其他運用程式,都應該先檢查手機上是否裝有能Intent開啟的其他程式。程式碼如下:
//MediaStore.ACTION_IMAGE_CAPTURE開啟手機中相機
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//檢查手機上是否裝有Intent對應開啟的程式
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, requestCode);
}
}
從程式碼可知,呼叫開啟相機是通過startActivityForResult(), 那返回的是什麼呢?
一個壓縮後的Bitmap,還是一個圖片的path. ?以上程式碼產生的是一個系統壓縮後返回的Bitmap。在onActivityResult()中Intent#Bundle,Bundle中data(key)對應的值(value).
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
Bundle extras = data.getExtras();
Bitmap imageBitmap = (Bitmap) extras.get("data");
mImageView.setImageBitmap(imageBitmap);
}
}
一個系統壓縮後的Bitmap實際作用不大,適合做一個圖示。 若是需要按尺寸來載入圖片,然後將圖片壓縮上傳,利用以上程式碼是完全行不通的。 這時候,便需要知道相機拍照產生的原始圖片的Path,根據path來實現專案需求。
實際開發中,會指定圖片的路徑,即圖片產生到指定檔案下,且指定圖片格式為.png 。將圖片儲存路徑通過Intent#putExtra()告訴系統。
private String imagePath1;
/**
* 開啟相機
*/
public void openCamera(int requestCode) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(getPackageManager()) != null) {
File bitmapCacheFile = BitmapUtils.getBitmapDiskFile(BaseApplication.getAppContext());
if (bitmapCacheFile != null) {
imagePath1 = bitmapCacheFile.getAbsolutePath();
//指定拍照儲存路徑
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(bitmapCacheFile));
startActivityForResult(intent, requestCode);
}
}
}
/**
* 獲得儲存bitmap的檔案
* getExternalFilesDir()提供的是私有的目錄,在app解除安裝後會被刪除
*
* @param context
* @param
* @return
*/
public static File getBitmapDiskFile(Context context) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment
.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES).getPath();
} else {
cachePath = context.getFilesDir().getPath();
}
return new File(cachePath + File.separator + getBitmapFileName());
}
public static final String bitmapFormat = ".png";
/**
* 生成bitmap的檔名:日期,md5加密
*
* @return
*/
public static String getBitmapFileName() {
StringBuilder stringBuilder = new StringBuilder();
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
String currentDate = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
mDigest.update(currentDate.getBytes("utf-8"));
byte[] b = mDigest.digest();
for (int i = 0; i < b.length; ++i) {
String hex = Integer.toHexString(0xFF & b[i]);
if (hex.length() == 1) {
stringBuilder.append('0');
}
stringBuilder.append(hex);
}
} catch (Exception e) {
e.printStackTrace();
}
String fileName = stringBuilder.toString() + bitmapFormat;
return fileName;
}
向sd-card寫入資訊,許可權是少不了的:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
在onActivityResult中獲取拍照成功的標示,這裡系統將不會返回有任何資料,這時候需要自己開啟非同步執行緒去按適屏計算載入圖片:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CAREMA) {
if (resultCode == RESULT_OK) {
//開啟工作執行緒去載入,拍照產生的圖片
new Thread(loadBitmapRunnable).start();
}
}
}
/**
* 根據path路徑,找到file,從而生成bitmap
*/
private Runnable loadBitmapRunnable = new Runnable() {
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
try {
Thread.sleep(500);
Bitmap bitmap = BitmapUtils.decodeFileCreateBitmap(imagePath1
, iv.getWidth(), iv.getHeight());
handler.obtainMessage(CAREMA, bitmap).sendToTarget();
} catch (Exception e) {
e.printStackTrace();
}
}
};
public synchronized static Bitmap decodeFileCreateBitmap(String path, int targetWith, int targerHeight) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
options.inSampleSize = calculateScaleSize(options, targetWith, targerHeight);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
return getNormalBitamp(bitmap, path);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 採用向上取整的方式,計算壓縮尺寸
*
* @param options
* @param targetWith
* @param targerHeight
* @return
*/
private static int calculateScaleSize(BitmapFactory.Options options, int targetWith, int targerHeight) {
int simpleSize;
if (targetWith > 0&&targerHeight>0) {
int scaleWith = (int) Math.ceil((options.outWidth * 1.0f) / targetWith);
int scaleHeight = (int) Math.ceil((options.outHeight * 1.0f) / targerHeight);
simpleSize = Math.max(scaleWith, scaleHeight);
} else {
simpleSize = 1;
}
return simpleSize;
}
部分手機上回遇到圖片旋轉問題,這時候需要使用到ExifInterface這類,手動回覆圖片原本方向:
/**
* 根據儲存的bitamp中旋轉角度,來建立正常的bitamp
*
* @param bitmap
* @param path
* @return
*/
public static Bitmap getNormalBitamp(Bitmap bitmap, String path) {
int rotate = getBitmapRotate(path);
Bitmap normalBitmap=null;
switch (rotate) {
case 90:
case 180:
case 270:
try {
Matrix matrix = new Matrix();
matrix.postRotate(rotate);
normalBitmap = bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth()
, bitmap.getHeight(), matrix, true);
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
} catch (Exception e) {
e.printStackTrace();
normalBitmap=bitmap;
}
break;
default:
normalBitmap=bitmap;
break;
}
return normalBitmap;
}
/**
* ExifInterface :這個類為jpeg檔案記錄一些image 的標記
* 這裡,獲取圖片的旋轉角度
*
* @param path
* @return
*/
public static int getBitmapRotate(String path) {
int degree = 0;
try {
ExifInterface exifInterface = new ExifInterface(path);
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION
, ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
}
} catch (Exception e) {
e.printStackTrace();
}
return degree;
}
最後建立一個主執行緒捆綁的Handler,進行載入適屏處理後的bitmap:
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == CAREMA) {
if (msg.obj instanceof Bitmap) {
iv.setImageBitmap((Bitmap) msg.obj);
}
}
return false;
}
});
拍照效果如下:
1.選擇拍照功能:
2.相機運用進行拍照:
3.載入拍照產生的圖片:
android相簿獲取圖片功能
獲取相簿中相片,也是通過Intent來開啟相簿運用來實現的。
/**
* 開啟相簿
*/
public void openMapStorage(int requestCode) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, requestCode);
}
}
在 onActivityResult中接收相簿返回的資訊,這裡是返回一個Uri。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PHOTO) {
if (resultCode == RESULT_OK) {
uri = data.getData();
new Thread(loadUriCreateBitmapRunnable).start();
}
}
}
根據Uri來查詢相簿的資料庫,找到圖片路徑,從而載入圖片:
/**
* 根據Uri,查詢到path,找到對應的file,生成bitmap
*/
private Runnable loadUriCreateBitmapRunnable = new Runnable() {
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Cursor cursor = null;
try {
Thread.sleep(500);
if (uri != null) {
cursor = getContentResolver().query(uri,
new String[]{MediaStore.Images.Media.DATA}, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
imagePath1 = cursor.getString(
cursor.getColumnIndex(MediaStore.Images.Media.DATA));
if (imagePath1 != null) {
Bitmap bitmap = BitmapUtils.decodeFileCreateBitmap(imagePath1,
iv.getWidth(), iv.getHeight());
handler.obtainMessage(CAREMA, bitmap).sendToTarget();
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
}
};
最後建立一個主執行緒捆綁的Handler,進行載入相簿選中的bitmap:
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == CAREMA) {
if (msg.obj instanceof Bitmap) {
iv.setImageBitmap((Bitmap) msg.obj);
}
}
return false;
}
});
效果如下:
1.在相簿中選中圖片:
2.載入選中的圖片 :
android 檢視圖片,進行縮放,拖拉功能
1.首先載入適屏的圖片,通過設定Matrix 來,放置到中間:
private Runnable loadBitmapRunnable = new Runnable() {
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
try {
Thread.sleep(500);
Bitmap bitmap = BitmapUtils.decodeFileCreateBitmap(path
, toucher_iv.getWidth(), toucher_iv.getHeight());
handler.obtainMessage(0, bitmap).sendToTarget();
} catch (Exception e) {
e.printStackTrace();
}
}
};
//記錄當前圖片的Matrix
private Matrix beforeMatrix = new Matrix();
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 0) {
bitmap = (Bitmap) msg.obj;
if (bitmap == null) {
return false;
}
float y = ((getPhoneMetrics(BaseApplication.getAppContext()).heightPixels -
bitmap.getHeight()) / 2);
if (y > 0) {
beforeMatrix.setTranslate(0, y);
toucher_iv.setImageMatrix(beforeMatrix);
}
toucher_iv.setImageBitmap(bitmap);
}
return false;
}
});
2.進行設定縮放,和拖拉的功能程式碼:
先實現 View.OnTouchListener類,複寫onTouch(View v, MotionEvent event)方法。
//記錄當前圖片的Matrix
private Matrix beforeMatrix = new Matrix();
//改變狀態後的Matrix
private Matrix changeMatrix = new Matrix();
//記錄當前圖片處於的狀態
private int currentState = 0;//當前狀態
private final static int MOVE = 1;//拖動
private final static int SCALLE = 2;//縮放
//記錄兩個座標(float型別)
private PointF before_PointF = new PointF();
//記錄下一開兩個手指間的距離:
private float before_Distance;
//記錄下開始時,兩個手指間的中心點
private PointF midPointf;
/**
* 1.拖動分析:
* 在原本Matrix基礎上新增移動的x,y距離,實現拖動
* 做法:先記錄開始的Matrix1,
* 然後計算移動的x,y,
* 建立一個以Matrix1為基礎的新的Matrix2,新增移動的x,y
* 圖片設定新的Matrix2
* <p/>
* <p/>
* 2.縮放分析:
* 先記錄中心點(縮放,旋轉都需要用到),原本的Matrix,原本手指間距離,通過距離比率來設定
*
* @param v
* @param event
* @return
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
//手指壓下螢幕
case MotionEvent.ACTION_DOWN:
currentState = MOVE;
beforeMatrix.set(toucher_iv.getImageMatrix());
before_PointF.set(event.getX(), event.getY());
break;
//手指在滑動
case MotionEvent.ACTION_MOVE:
if (currentState == MOVE) {//拖動狀態
float distance_x = event.getX() - before_PointF.x;
float ditance_y = event.getY() - before_PointF.y;
changeMatrix.set(beforeMatrix);
changeMatrix.postTranslate(distance_x, ditance_y);
} else if (currentState == SCALLE) {//縮放狀態
float endDistance = distance(event);
if (endDistance > 10f) {
float scale = endDistance / before_Distance;
changeMatrix.set(beforeMatrix);
changeMatrix.postScale(scale, scale, midPointf.x, midPointf.y);
}
}
break;
//手指離開觸控
case MotionEvent.ACTION_UP:
currentState = 0;
break;
//多一個手指在觸控
case MotionEvent.ACTION_POINTER_DOWN:
currentState = SCALLE;
//開始時,兩個手指間距離
before_Distance = distance(event);
if (before_Distance > 10f) {
midPointf = mid(event);
beforeMatrix.set(toucher_iv.getImageMatrix());
}
break;
//還有手指在觸控
case MotionEvent.ACTION_POINTER_UP:
currentState = 0;
break;
}
toucher_iv.setImageMatrix(changeMatrix);
return true;
}
/**
* 計算兩個手指間的距離
*/
private float distance(MotionEvent event) {
float dx = event.getX(1) - event.getX(0);
float dy = event.getY(1) - event.getY(0);
/** 使用勾股定理返回兩點之間的距離 */
return (float) Math.sqrt(dx * dx + dy * dy);
}
/**
* 計算兩個手指間的中間點
*/
private PointF mid(MotionEvent event) {
float midX = (event.getX(1) + event.getX(0)) / 2;
float midY = (event.getY(1) + event.getY(0)) / 2;
return new PointF(midX, midY);
}
效果如下:
這裡沒有製作gif來顯示效果,感興趣的可以下載專案來體驗效果:
android 壓縮圖片
現在的手機拍照產生的相片都了幾M,甚至可能是5M多,上傳到伺服器是不會上傳那麼大的圖片的。這邊需要用到壓縮。
壓縮圖片一般通過Bitmap#compress()來實現的。 一般都是通過for迴圈來計算最小壓縮體積:
這裡是將Bitmap按最小體積量來壓縮成byte[]來上傳的。
public static byte[] bitmapCompressToByteArray(Bitmap bitmap, int bitmapSize, boolean isRecycle) {
int quality = 100;//範圍0~100
ByteArrayOutputStream byteArrayOutputStream = null;
try {
byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG,quality,byteArrayOutputStream);
while (byteArrayOutputStream.toByteArray().length/1024>bitmapSize){
if(quality<=10){
break;
}
byteArrayOutputStream.reset();
quality-=10;
bitmap.compress(Bitmap.CompressFormat.PNG,quality,byteArrayOutputStream);
}
byte[] b = byteArrayOutputStream.toByteArray();
if (isRecycle) {
bitmap.recycle();
}
return b;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (byteArrayOutputStream != null) {
byteArrayOutputStream.close();
}
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
經過測試發現,若是一個幾M的圖片進行這樣的壓縮處理,時間花費是很久的,甚至可能是幾分鐘時間。
這裡的的處理方式是:按上傳的體積量來計算出圖片的解析度,按這個解析度來載入適合的圖片,然後將這個圖片再來編碼成byte[]
/**
* 將bitmap編碼成byte[]
*/
private Runnable bitmapEndecodeByteRunnable = new Runnable() {
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
try {
//這裡是250*250大小的圖片
byte[] b = BitmapUtils.bitmapCompressToByteArray(
BitmapUtils.decodeFileCreateBitmap(imagePath1
, 250, 250), 200, true);
handler.obtainMessage(FILEUPLOAD, b).sendToTarget();
} catch (Exception e) {
e.printStackTrace();
}
}
};
最後將圖片對應的byte[]通過volley中自定義的MultiPartRequest上傳:
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == FILEUPLOAD) {
byte[] b = (byte[]) msg.obj;
if (b != null) {
sendFileUploadRequest(b);
}
}
return false;
}
});
/**
* 將bitmap編碼成byte[],然後上傳到伺服器
*
* @param bitmap
*/
public void sendFileUploadRequest(byte[] bitmap) {
System.out.print("開始上傳");
MultiPartRequest<JsonBean> request = new MultiPartRequest<>(Request.Method.POST, "http://192.168.1.102:8080/SSMProject/file/fileUpload", JsonBean.class, new Response.Listener<JsonBean>() {
@Override
public void onResponse(JsonBean jsonBean) {
path_tv.setText("bitmap儲存在:"+jsonBean.path);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
Toast.makeText(MultiPartRequestActivity.this, volleyError.getMessage(), Toast.LENGTH_LONG).show();
}
});
request.addFile(bitmap);
request.setRetryPolicy(new DefaultRetryPolicy(50000,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
request.setTag(TAG);
VolleySingleton.getInstance().addToRequestQueue(request);
}
相關知識點: